Browse Source

* Bump to fatfree 3.7.2

Giancarlo 6 years ago
parent
commit
bf6e468d99

+ 56 - 0
lib/f3/CHANGELOG.md

@@ -1,5 +1,61 @@
 CHANGELOG
 
+3.7.2 (28 May 2020)
+*   CHANGED, View->sandbox: disable escaping when rendering as text/plain, bcosca/fatfree#654
+*   update HTTP protocol checks, #bcosca/fatfree#1190
+*   Base->clear: close vulnerability on variable compilation, bcosca/fatfree#1191
+*   DB\SQL\Mapper: fix empty ID after insert, bcosca/fatfree#1175
+*   DB\SQL\Mapper: fix using correct key variable for grouped sql pagination sets
+*   Fix return type of 'count' in Cursor->paginate() (bcosca/fatfree#1187)
+*   Bug fix, Web->minify: fix minification of ES6 template literals, bcosca/fatfree#1178
+*   Bug fix, config: refactoring custom section parser regex, bcosca/fatfree#1149
+*   Bug fix: token resolve on non-alias reroute paths, ref. 221f0c930f8664565c9825faeb9ed9af0f7a01c8
+*   Websocket: Improved event handler usage
+*   optimized internal get calls
+*   only use cached lexicon when a $ttl was given
+*   only use money_format up until php7.4, fixes bcosca/fatfree#1174
+
+3.7.1 (30. December 2019)
+*   Base->build: Add support for brace-enclosed route tokens
+*   Base->reroute, fix duplicate fragment issue on non-alias routes
+*   DB\SQL\Mapper: fix empty check for pkey when reloading after insert
+*   Web->minify: fix minification with multiple files, [bcosca/fatfree#1152](https://github.com/bcosca/fatfree/issues/1152), [#bcosca/fatfree#1169](https://github.com/bcosca/fatfree/issues/1169)
+
+3.7.0 (26. November 2019)
+*   NEW: Matrix, added select and walk methods for array processing and validation tools
+*   NEW: Added configurable file locking via LOCK var
+*   NEW: json support for dictionary files
+*   NEW: $die parameter on ONREROUTE hook
+*   NEW: Added SameSite cookie support for php7.3+ (JAR.samesite), [bcosca/fatfree#1165](https://github.com/bcosca/fatfree/issues/1165)
+*   NEW, DB\SQL\Mapper: added updateAll method to batch-update multiple records at once
+*   CHANGED, DB\SQL\Mapper: Throw error on update/erase if the table has no primary key, [#285](https://github.com/bcosca/fatfree-core/issues/285)
+*   Cache, Redis: Added ability to set a Redis password, [#287](https://github.com/bcosca/fatfree-core/issues/287)
+*   DB\SQL\Session: make datatype of data column configurable, [bcosca/fatfree#1130](https://github.com/bcosca/fatfree/issues/1130)
+*   DB\SQL\Mapper: only add adhoc fields in count queries that are used for grouping
+*   DB\SQL\Mapper: fixed inserting an already loaded record again (duplicating), [bcosca/fatfree#1093](https://github.com/bcosca/fatfree/issues/1093)
+*   Magic (Mappers): fix isset check on existing properties
+*   SMTP: added support for Bounce mail recipient ("Sender" header)
+*   OAuth2: make query string encode type configurable, [#268](https://github.com/bcosca/fatfree-core/issues/268) [#269](https://github.com/bcosca/fatfree-core/issues/269)
+*   Web: Added more cyrillic letters to diacritics, [bcosca/fatfree#1158](https://github.com/bcosca/fatfree/issues/1158)
+*   Web: Fixed url string falsely detected as comment section [9ac8e615](https://github.com/bcosca/fatfree-core/commit/9ac8e615ccaf750b49497a3c86161331b24e637f)
+*   Web: added file inspection for mime-type detection, [#270](https://github.com/bcosca/fatfree-core/issues/270), [bcosca/fatfree#1138](https://github.com/bcosca/fatfree/issues/1138)
+*   WS: Fixed processing all queued data frames inside the buffer, [#277](https://github.com/bcosca/fatfree-core/issues/277)
+*   WS: Allow packet size override
+*   Markdown: Support mixed `strong` and `italic` elements, [#276](https://github.com/bcosca/fatfree-core/issues/276)
+*   Markdown: Keep spaces around `=` sign in ini code blocks 
+*   Added route alias key name validation, [#243](https://github.com/bcosca/fatfree-core/issues/243)
+*   Added fragment argument to alias method, [#282](https://github.com/bcosca/fatfree-core/issues/282)
+*   Allow adding fragment to reroute, [#1156](https://github.com/bcosca/fatfree/issues/1156)
+*   Added additional HTTP status codes, [#283](https://github.com/bcosca/fatfree-core/issues/283)
+*   Added X-Forwarded-For IP to log entries, [bcosca/fatfree#1042](https://github.com/bcosca/fatfree/issues/1042)
+*   Bug fix: broken custom date/time formatting, [bcosca/fatfree#1147](https://github.com/bcosca/fatfree/issues/1147)
+*   Bug fix: duplicate UI path rendering edge-case in Views and minify, [bcosca/fatfree#1152](https://github.com/bcosca/fatfree/issues/1152)
+*   Bug fix: unicode chars in custom config section keys, [bcosca/fatfree#1149](https://github.com/bcosca/fatfree/issues/1149)
+*   Bug fix: ensure valid reroute path in location header, [bcosca/fatfree#1140](https://github.com/bcosca/fatfree/issues/1140)
+*   Bug fix: use dictionary path for lexicon caching-hash
+*   Bug fix, php7.3: number format ternary, [bcosca/fatfree#1142](https://github.com/bcosca/fatfree/issues/1142)
+*   fix PHPdoc and variable inspection, [bcosca/fatfree#865](https://github.com/bcosca/fatfree/issues/865), [bcosca/fatfree#1128](https://github.com/bcosca/fatfree/issues/1128)
+
 3.6.5 (24 December 2018)
 *	NEW: Log, added timestamp to each line
 *	NEW: Auth, added support for custom compare method, [#116](https://github.com/bcosca/fatfree-core/issues/116)

+ 1 - 1
lib/f3/audit.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/auth.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2018 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 155 - 67
lib/f3/base.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -29,7 +29,7 @@ abstract class Prefab {
 	**/
 	static function instance() {
 		if (!Registry::exists($class=get_called_class())) {
-			$ref=new Reflectionclass($class);
+			$ref=new ReflectionClass($class);
 			$args=func_get_args();
 			Registry::set($class,
 				$args?$ref->newinstanceargs($args):new $class);
@@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess {
 	//@{ Framework details
 	const
 		PACKAGE='Fat-Free Framework',
-		VERSION='3.6.5-Release';
+		VERSION='3.7.2-Release';
 	//@}
 
 	//@{ HTTP status codes (RFC 2616)
@@ -67,6 +67,7 @@ final class Base extends Prefab implements ArrayAccess {
 		HTTP_304='Not Modified',
 		HTTP_305='Use Proxy',
 		HTTP_307='Temporary Redirect',
+		HTTP_308='Permanent Redirect',
 		HTTP_400='Bad Request',
 		HTTP_401='Unauthorized',
 		HTTP_402='Payment Required',
@@ -85,12 +86,19 @@ final class Base extends Prefab implements ArrayAccess {
 		HTTP_415='Unsupported Media Type',
 		HTTP_416='Requested Range Not Satisfiable',
 		HTTP_417='Expectation Failed',
+		HTTP_421='Misdirected Request',
+		HTTP_422='Unprocessable Entity',
+		HTTP_423='Locked',
+		HTTP_429='Too Many Requests',
+		HTTP_451='Unavailable For Legal Reasons',
 		HTTP_500='Internal Server Error',
 		HTTP_501='Not Implemented',
 		HTTP_502='Bad Gateway',
 		HTTP_503='Service Unavailable',
 		HTTP_504='Gateway Timeout',
-		HTTP_505='HTTP Version Not Supported';
+		HTTP_505='HTTP Version Not Supported',
+		HTTP_507='Insufficient Storage',
+		HTTP_511='Network Authentication Required';
 	//@}
 
 	const
@@ -114,6 +122,7 @@ final class Base extends Prefab implements ArrayAccess {
 	const
 		E_Pattern='Invalid routing pattern: %s',
 		E_Named='Named route does not exist: %s',
+		E_Alias='Invalid named route alias: %s',
 		E_Fatal='Fatal error: %s',
 		E_Open='Unable to open %s',
 		E_Routes='No routes specified',
@@ -168,17 +177,17 @@ final class Base extends Prefab implements ArrayAccess {
 			}
 		else {
 			$i=0;
-			$url=preg_replace_callback('/@(\w+)|(\*)/',
+			$url=preg_replace_callback('/(\{)?@(\w+)(?(1)\})|(\*)/',
 				function($match) use(&$i,$args) {
-					if (isset($match[1]) &&
-						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]];
+						array_key_exists($match[2],$args))
+						return $args[$match[2]];
+					if (isset($match[3]) &&
+						array_key_exists($match[3],$args)) {
+						if (!is_array($args[$match[3]]))
+							return $args[$match[3]];
 						$i++;
-						return $args[$match[2]][$i-1];
+						return $args[$match[3]][$i-1];
 					}
 					return $match[0];
 				},$url);
@@ -226,9 +235,31 @@ final class Base extends Prefab implements ArrayAccess {
 	*	Convert JS-style token to PHP expression
 	*	@return string
 	*	@param $str string
-	**/
-	function compile($str) {
-		return preg_replace_callback(
+	*	@param $evaluate bool compile expressions as well or only convert variable access
+	**/
+	function compile($str, $evaluate=TRUE) {
+		return (!$evaluate)
+			? preg_replace_callback(
+				'/^@(\w+)((?:\..+|\[(?:(?:[^\[\]]*|(?R))*)\])*)/',
+				function($expr) {
+					$str='$'.$expr[1];
+					if (isset($expr[2]))
+						$str.=preg_replace_callback(
+							'/\.([^.\[\]]+)|\[((?:[^\[\]\'"]*|(?R))*)\]/',
+							function($sub) {
+								$val=isset($sub[2]) ? $sub[2] : $sub[1];
+								if (ctype_digit($val))
+									$val=(int)$val;
+								$out='['.$this->export($val).']';
+								return $out;
+							},
+							$expr[2]
+						);
+					return $str;
+				},
+				$str
+			)
+			: preg_replace_callback(
 			'/(?<!\w)@(\w+(?:(?:\->|::)\w+)?)'.
 			'((?:\.\w+|\[(?:(?:[^\[\]]*|(?R))*)\]|(?:\->|::)\w+|\()*)/',
 			function($expr) {
@@ -292,7 +323,7 @@ final class Base extends Prefab implements ArrayAccess {
 			elseif ($obj) {
 				$obj=FALSE;
 				if (!is_object($var))
-					$var=new stdclass;
+					$var=new stdClass;
 				if ($add || property_exists($var,$part))
 					$var=&$var->$part;
 				else {
@@ -355,12 +386,22 @@ final class Base extends Prefab implements ArrayAccess {
 				$parts=$this->cut($key);
 				$jar=$this->unserialize($this->serialize($this->hive['JAR']));
 				unset($jar['lifetime']);
-				if (isset($_COOKIE[$parts[1]]))
-					call_user_func_array('setcookie',
-						array_merge([$parts[1],NULL],['expire'=>0]+$jar));
-				if ($ttl)
-					$jar['expire']=$time+$ttl;
-				call_user_func_array('setcookie',[$parts[1],$val]+$jar);
+				if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
+					unset($jar['expire']);
+					if (isset($_COOKIE[$parts[1]]))
+						setcookie($parts[1],NULL,['expires'=>0]+$jar);
+					if ($ttl)
+						$jar['expires']=$time+$ttl;
+					setcookie($parts[1],$val,$jar);
+				} else {
+					unset($jar['samesite']);
+					if (isset($_COOKIE[$parts[1]]))
+						call_user_func_array('setcookie',
+							array_merge([$parts[1],NULL],['expire'=>0]+$jar));
+					if ($ttl)
+						$jar['expire']=$time+$ttl;
+					call_user_func_array('setcookie',[$parts[1],$val]+$jar);
+				}
 				$_COOKIE[$parts[1]]=$val;
 				return $val;
 			}
@@ -405,7 +446,12 @@ final class Base extends Prefab implements ArrayAccess {
 				$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 (version_compare(PHP_VERSION, '7.3.0') >= 0)
+						session_set_cookie_params($jar);
+					else {
+						unset($jar['samesite']);
+						call_user_func_array('session_set_cookie_params',$jar);
+					}
 			}
 		}
 		if ($ttl)
@@ -436,7 +482,6 @@ final class Base extends Prefab implements ArrayAccess {
 
 	/**
 	*	Unset hive key
-	*	@return NULL
 	*	@param $key string
 	**/
 	function clear($key) {
@@ -453,8 +498,15 @@ final class Base extends Prefab implements ArrayAccess {
 				$jar=$this->hive['JAR'];
 				unset($jar['lifetime']);
 				$jar['expire']=0;
-				call_user_func_array('setcookie',
-					array_merge([$parts[1],NULL],$jar));
+				if (version_compare(PHP_VERSION, '7.3.0') >= 0) {
+					$jar['expires']=$jar['expire'];
+					unset($jar['expire']);
+					setcookie($parts[1],NULL,$jar);
+				} else {
+					unset($jar['samesite']);
+					call_user_func_array('setcookie',
+						array_merge([$parts[1],NULL],$jar));
+				}
 				unset($_COOKIE[$parts[1]]);
 			}
 		}
@@ -473,7 +525,9 @@ final class Base extends Prefab implements ArrayAccess {
 			// Reset global to default value
 			$this->hive[$parts[0]]=$this->init[$parts[0]];
 		else {
-			eval('unset('.$this->compile('@this->hive.'.$key).');');
+			$val=preg_replace('/^(\$hive)/','$this->hive',
+				$this->compile('@hive.'.$key, FALSE));
+			eval('unset('.$val.');');
 			if ($parts[0]=='SESSION') {
 				session_commit();
 				session_start();
@@ -512,7 +566,6 @@ final class Base extends Prefab implements ArrayAccess {
 
 	/**
 	*	Multi-variable assignment using associative array
-	*	@return NULL
 	*	@param $vars array
 	*	@param $prefix string
 	*	@param $ttl int
@@ -886,7 +939,22 @@ final class Base extends Prefab implements ArrayAccess {
 			'(?:,\s*(?P<mod>(?:\w+(?:\s*\{.+?\}\s*,?\s*)?)*)'.
 			'(?:,\s*(?P<prop>.+?))?)?)?\s*\}/',
 			function($expr) use($args,$conv) {
+				/**
+				 * @var string $pos
+				 * @var string $mod
+				 * @var string $type
+				 * @var string $prop
+				 */
 				extract($expr);
+				/**
+				 * @var string $thousands_sep
+				 * @var string $negative_sign
+				 * @var string $positive_sign
+				 * @var string $frac_digits
+				 * @var string $decimal_point
+				 * @var string $int_curr_symbol
+				 * @var string $currency_symbol
+				 */
 				extract($conv);
 				if (!array_key_exists($pos,$args))
 					return $expr[0];
@@ -907,6 +975,8 @@ final class Base extends Prefab implements ArrayAccess {
 								$mod,$matches,PREG_SET_ORDER);
 							$ord=['zero','one','two'];
 							foreach ($matches as $match) {
+								/** @var string $tag */
+								/** @var string $data */
 								extract($match);
 								if (isset($ord[$args[$pos]]) &&
 									$tag==$ord[$args[$pos]] || $tag=='other')
@@ -924,7 +994,8 @@ final class Base extends Prefab implements ArrayAccess {
 											$cstm=!$int=($prop=='int'))
 											$currency_symbol=$prop;
 										if (!$cstm &&
-											function_exists('money_format'))
+											function_exists('money_format') &&
+											version_compare(PHP_VERSION,'7.4.0')<0)
 											return money_format(
 												'%'.($int?'i':'n'),$args[$pos]);
 										$fmt=[
@@ -977,19 +1048,21 @@ final class Base extends Prefab implements ArrayAccess {
 								$args[$pos],
 								isset($prop)?
 									$prop:
-									$frac?strlen($frac)-2:0,
+									($frac?strlen($frac)-2:0),
 								$decimal_point,$thousands_sep);
 						case 'date':
-							$prop='%d %B %Y';
 							if (empty($mod) || $mod=='short')
 								$prop='%x';
 							elseif ($mod=='full')
-								$prop='%A, '.$prop;
+								$prop='%A, %d %B %Y';
+							elseif ($mod!='custom')
+								$prop='%d %B %Y';
 							return strftime($prop,$args[$pos]);
 						case 'time':
-							$prop='%r';
 							if (empty($mod) || $mod=='short')
 								$prop='%X';
+							elseif ($mod!='custom')
+								$prop='%r';
 							return strftime($prop,$args[$pos]);
 						default:
 							return $expr[0];
@@ -1058,8 +1131,8 @@ final class Base extends Prefab implements ArrayAccess {
 	function lexicon($path,$ttl=0) {
 		$languages=$this->languages?:explode(',',$this->fallback);
 		$cache=Cache::instance();
-		if ($cache->exists(
-			$hash=$this->hash(implode(',',$languages)).'.dic',$lex))
+		if ($ttl && $cache->exists(
+			$hash=$this->hash(implode(',',$languages).$path).'.dic',$lex))
 			return $lex;
 		$lex=[];
 		foreach ($languages as $lang)
@@ -1068,6 +1141,9 @@ final class Base extends Prefab implements ArrayAccess {
 					is_file($file=$base.'.php')) &&
 					is_array($dict=require($file)))
 					$lex+=$dict;
+				elseif (is_file($file=$base.'.json') &&
+					is_array($dict=json_decode(file_get_contents($file), true)))
+					$lex+=$dict;
 				elseif (is_file($file=$base.'.ini')) {
 					preg_match_all(
 						'/(?<=^|\n)(?:'.
@@ -1134,7 +1210,6 @@ final class Base extends Prefab implements ArrayAccess {
 
 	/**
 	*	Send cache metadata to HTTP client
-	*	@return NULL
 	*	@param $secs int
 	**/
 	function expire($secs=0) {
@@ -1248,7 +1323,6 @@ final class Base extends Prefab implements ArrayAccess {
 	*	Log error; Execute ONERROR handler if defined, else display
 	*	default error page (HTML for synchronous requests, JSON string
 	*	for AJAX requests)
-	*	@return NULL
 	*	@param $code int
 	*	@param $text string
 	*	@param $trace array
@@ -1373,8 +1447,9 @@ final class Base extends Prefab implements ArrayAccess {
 	*	@param $name string
 	*	@param $params array|string
 	*	@param $query string|array
+	*	@param $fragment string
 	**/
-	function alias($name,$params=[],$query=NULL) {
+	function alias($name,$params=[],$query=NULL,$fragment=NULL) {
 		if (!is_array($params))
 			$params=$this->parse($params);
 		if (empty($this->hive['ALIASES'][$name]))
@@ -1382,7 +1457,7 @@ final class Base extends Prefab implements ArrayAccess {
 		$url=$this->build($this->hive['ALIASES'][$name],$params);
 		if (is_array($query))
 			$query=http_build_query($query);
-		return $url.($query?('?'.$query):'');
+		return $url.($query?('?'.$query):'').($fragment?'#'.$fragment:'');
 	}
 
 	/**
@@ -1403,8 +1478,11 @@ final class Base extends Prefab implements ArrayAccess {
 		}
 		preg_match('/([\|\w]+)\h+(?:(?:@?(.+?)\h*:\h*)?(@(\w+)|[^\h]+))'.
 			'(?:\h+\[('.implode('|',$types).')\])?/u',$pattern,$parts);
-		if (isset($parts[2]) && $parts[2])
+		if (isset($parts[2]) && $parts[2]) {
+			if (!preg_match('/^\w+$/',$parts[2]))
+				user_error(sprintf(self::E_Alias,$parts[2]),E_USER_ERROR);
 			$this->hive['ALIASES'][$alias=$parts[2]]=$parts[3];
+		}
 		elseif (!empty($parts[4])) {
 			if (empty($this->hive['ALIASES'][$parts[4]]))
 				user_error(sprintf(self::E_Named,$parts[4]),E_USER_ERROR);
@@ -1433,15 +1511,18 @@ final class Base extends Prefab implements ArrayAccess {
 			$url=$this->hive['REALM'];
 		if (is_array($url))
 			$url=call_user_func_array([$this,'alias'],$url);
-		elseif (preg_match('/^(?:@?([^\/()?]+)(?:(\(.+?)\))*(\?.+)*)/',
-			$url,$parts) &&
-			isset($this->hive['ALIASES'][$parts[1]]))
-			$url=$this->hive['ALIASES'][$parts[1]];
-		$url=$this->build($url,isset($parts[2])?$this->parse($parts[2]):[]).
-			(isset($parts[3])?$parts[3]:'');
+		elseif (preg_match('/^(?:@([^\/()?#]+)(?:\((.+?)\))*(\?[^#]+)*(#.+)*)/',
+			$url,$parts) && isset($this->hive['ALIASES'][$parts[1]]))
+			$url=$this->build($this->hive['ALIASES'][$parts[1]],
+					isset($parts[2])?$this->parse($parts[2]):[]).
+				(isset($parts[3])?$parts[3]:'').(isset($parts[4])?$parts[4]:'');
+		else
+			$url=$this->build($url);
 		if (($handler=$this->hive['ONREROUTE']) &&
-			$this->call($handler,[$url,$permanent])!==FALSE)
+			$this->call($handler,[$url,$permanent,$die])!==FALSE)
 			return;
+		if ($url[0]!='/' && !preg_match('/^\w+:\/\//i',$url))
+			$url='/'.$url;
 		if ($url[0]=='/' && (empty($url[1]) || $url[1]!='/')) {
 			$port=$this->hive['PORT'];
 			$port=in_array($port,[80,443])?'':(':'.$port);
@@ -1802,7 +1883,8 @@ 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')) {
+				elseif (isset($this->hive['CONTAINER'])) {
+					$container=$this->hive['CONTAINER'];
 					if (is_object($container) && is_callable([$container,'has'])
 						&& $container->has($parts[1])) // PSR11
 						$parts[1]=call_user_func([$container,'get'],$parts[1]);
@@ -1937,11 +2019,11 @@ final class Base extends Prefab implements ArrayAccess {
 						$sec=$match['section'];
 						if (preg_match(
 							'/^(?!(?:global|config|route|map|redirect)s\b)'.
-							'((?:\.?\w)+)/i',$sec,$msec) &&
-							!$this->exists($msec[0]))
-							$this->set($msec[0],NULL);
+							'(.*?)(?:\s*[:>])/i',$sec,$msec) &&
+							!$this->exists($msec[1]))
+							$this->set($msec[1],NULL);
 						preg_match('/^(config|route|map|redirect)s\b|'.
-							'^((?:\.?\w)+)\s*\>\s*(.*)/i',$sec,$cmd);
+							'^(.+?)\s*\>\s*(.*)/i',$sec,$cmd);
 						continue;
 					}
 					if ($allow)
@@ -2021,7 +2103,7 @@ final class Base extends Prefab implements ArrayAccess {
 			mkdir($tmp,self::MODE,TRUE);
 		// Use filesystem lock
 		if (is_file($lock=$tmp.
-			$this->get('SEED').'.'.$this->hash($id).'.lock') &&
+			$this->hive['SEED'].'.'.$this->hash($id).'.lock') &&
 			filemtime($lock)+ini_get('max_execution_time')<microtime(TRUE))
 			// Stale lock
 			@unlink($lock);
@@ -2054,7 +2136,7 @@ final class Base extends Prefab implements ArrayAccess {
 	*	@param $append bool
 	**/
 	function write($file,$data,$append=FALSE) {
-		return file_put_contents($file,$data,LOCK_EX|($append?FILE_APPEND:0));
+		return file_put_contents($file,$data,$this->hive['LOCK']|($append?FILE_APPEND:0));
 	}
 
 	/**
@@ -2086,7 +2168,6 @@ final class Base extends Prefab implements ArrayAccess {
 
 	/**
 	*	Dump expression with syntax highlighting
-	*	@return NULL
 	*	@param $expr mixed
 	**/
 	function dump($expr) {
@@ -2110,6 +2191,7 @@ final class Base extends Prefab implements ArrayAccess {
 	**/
 	protected function autoload($class) {
 		$class=$this->fixslashes(ltrim($class,'\\'));
+		/** @var callable $func */
 		$func=NULL;
 		if (is_array($path=$this->hive['AUTOLOAD']) &&
 			isset($path[1]) && is_callable($path[1]))
@@ -2155,7 +2237,7 @@ final class Base extends Prefab implements ArrayAccess {
 	*	Convenience method for assigning hive value
 	*	@return mixed
 	*	@param $key string
-	*	@param $val scalar
+	*	@param $val mixed
 	**/
 	function offsetset($key,$val) {
 		return $this->set($key,$val);
@@ -2173,7 +2255,6 @@ final class Base extends Prefab implements ArrayAccess {
 
 	/**
 	*	Convenience method for removing hive key
-	*	@return NULL
 	*	@param $key string
 	**/
 	function offsetunset($key) {
@@ -2211,7 +2292,6 @@ final class Base extends Prefab implements ArrayAccess {
 
 	/**
 	*	Alias for offsetunset()
-	*	@return mixed
 	*	@param $key string
 	**/
 	function __unset($key) {
@@ -2248,6 +2328,7 @@ final class Base extends Prefab implements ArrayAccess {
 		$check=error_reporting((E_ALL|E_STRICT)&~(E_NOTICE|E_USER_NOTICE));
 		set_exception_handler(
 			function($obj) {
+				/** @var Exception $obj */
 				$this->hive['EXCEPTION']=$obj;
 				$this->error(500,
 					$obj->getmessage().' '.
@@ -2348,7 +2429,8 @@ final class Base extends Prefab implements ArrayAccess {
 				!filter_var($_SERVER['SERVER_NAME'],FILTER_VALIDATE_IP)?
 				$_SERVER['SERVER_NAME']:'',
 			'secure'=>($scheme=='https'),
-			'httponly'=>TRUE
+			'httponly'=>TRUE,
+			'samesite'=>'Lax',
 		];
 		$port=80;
 		if (isset($headers['X-Forwarded-Port']))
@@ -2396,6 +2478,7 @@ final class Base extends Prefab implements ArrayAccess {
 				$this->language($headers['Accept-Language']):
 				$this->fallback,
 			'LOCALES'=>'./',
+			'LOCK'=>LOCK_EX,
 			'LOGGABLE'=>'*',
 			'LOGS'=>'./',
 			'MB'=>extension_loaded('mbstring'),
@@ -2435,7 +2518,12 @@ final class Base extends Prefab implements ArrayAccess {
 		if (!headers_sent() && session_status()!=PHP_SESSION_ACTIVE) {
 			unset($jar['expire']);
 			session_cache_limiter('');
-			call_user_func_array('session_set_cookie_params',$jar);
+			if (version_compare(PHP_VERSION, '7.3.0') >= 0)
+				session_set_cookie_params($jar);
+			else {
+				unset($jar['samesite']);
+				call_user_func_array('session_set_cookie_params',$jar);
+			}
 		}
 		if (PHP_SAPI=='cli-server' &&
 			preg_match('/^'.preg_quote($base,'/').'$/',$this->hive['URI']))
@@ -2682,10 +2770,12 @@ class Cache extends Prefab {
 		if ($dsn=trim($dsn)) {
 			if (preg_match('/^redis=(.+)/',$dsn,$parts) &&
 				extension_loaded('redis')) {
-				list($host,$port,$db)=explode(':',$parts[1])+[1=>6379,2=>NULL];
+				list($host,$port,$db,$password)=explode(':',$parts[1])+[1=>6379,2=>NULL,3=>NULL];
 				$this->ref=new Redis;
 				if(!$this->ref->connect($host,$port,2))
 					$this->ref=NULL;
+				if(!empty($password))
+					$this->ref->auth($password);
 				if(isset($db))
 					$this->ref->select($db);
 			}
@@ -2798,7 +2888,8 @@ class View extends Prefab {
 				!preg_grep ('/^Content-Type:/',headers_list()))
 				header('Content-Type: '.$mime.'; '.
 					'charset='.$fw->ENCODING);
-			if ($fw->ESCAPE)
+			if ($fw->ESCAPE && (!$mime ||
+					preg_match('/^(text\/html|(application|text)\/(.+\+)?xml)$/i',$mime)))
 				$hive=$this->esc($hive);
 			if (isset($hive['ALIASES']))
 				$hive['ALIASES']=$fw->build($hive['ALIASES']);
@@ -2901,13 +2992,11 @@ class Preview extends View {
 	*	@param $str string
 	**/
 	function token($str) {
-		$fw=$this->fw;
-		$str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
-			$fw->compile($str)));
+		$str=trim(preg_replace('/\{\{(.+?)\}\}/s','\1',$this->fw->compile($str)));
 		if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
 			$str,$parts)) {
 			$str=trim($parts[1]);
-			foreach ($fw->split(trim($parts[2],"\xC2\xA0")) as $func)
+			foreach ($this->fw->split(trim($parts[2],"\xC2\xA0")) as $func)
 				$str=((empty($this->filter[$cmd=$func]) &&
 					function_exists($cmd)) ||
 					is_string($cmd=$this->filter($func)))?
@@ -3475,7 +3564,6 @@ final class Registry {
 
 	/**
 	*	Delete object from catalog
-	*	@return NULL
 	*	@param $key string
 	**/
 	static function clear($key) {

+ 1 - 1
lib/f3/basket.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/bcrypt.php

@@ -1,7 +1,7 @@
 <?php
 
 /**
-*	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+*	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 *
 *	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 *

+ 66 - 70
lib/f3/cli/ws.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -70,7 +70,7 @@ class WS {
 		$verb=NULL;
 		$uri=NULL;
 		foreach (explode($EOL,trim($buf)) as $line)
-			if (preg_match('/^(\w+)\s(.+)\sHTTP\/1\.\d$/',
+			if (preg_match('/^(\w+)\s(.+)\sHTTP\/[\d.]{1,3}$/',
 				trim($line),$match)) {
 				$verb=$match[1];
 				$uri=$match[2];
@@ -138,11 +138,13 @@ class WS {
 	*	Read from stream socket
 	*	@return string|FALSE
 	*	@param $socket resource
+	*	@param $len int
 	**/
-	function read($socket) {
-		if (is_string($buf=@fread($socket,WS::Packet)) &&
-			strlen($buf) &&
-			strlen($buf)<WS::Packet)
+	function read($socket,$len=0) {
+		if (!$len)
+			$len=WS::Packet;
+		if (is_string($buf=@fread($socket,$len)) &&
+			strlen($buf) && strlen($buf)<$len)
 			return $buf;
 		if (isset($this->events['error']) &&
 			is_callable($func=$this->events['error']))
@@ -179,6 +181,10 @@ class WS {
 	function agents($uri=NULL) {
 		return array_filter(
 			$this->agents,
+			/**
+			 * @var $val Agent
+			 * @return bool
+			 */
 			function($val) use($uri) {
 				return $uri?($val->uri()==$uri):TRUE;
 			}
@@ -206,19 +212,15 @@ class WS {
 
 	/**
 	*	Terminate server
-	*	@return NULL
-	*	@param $signal int
 	**/
-	function kill($signal) {
+	function kill() {
 		die;
 	}
 
 	/**
 	*	Execute the server process
-	*	@return object
 	**/
 	function run() {
-		$fw=\Base::instance();
 		// Assign signal handlers
 		declare(ticks=1);
 		pcntl_signal(SIGINT,[$this,'kill']);
@@ -303,8 +305,6 @@ class WS {
 	}
 
 	/**
-	*	Instantiate object
-	*	@return object
 	*	@param $addr string
 	*	@param $ctx resource
 	*	@param $wait int
@@ -328,12 +328,11 @@ class Agent {
 		$flag,
 		$verb,
 		$uri,
-		$headers,
-		$events;
+		$headers;
 
 	/**
 	*	Return server instance
-	*	@return object
+	*	@return WS
 	**/
 	function server() {
 		return $this->server;
@@ -349,7 +348,7 @@ class Agent {
 
 	/**
 	*	Return socket
-	*	@return object
+	*	@return resource
 	**/
 	function socket() {
 		return $this->socket;
@@ -373,7 +372,7 @@ class Agent {
 
 	/**
 	*	Return socket headers
-	*	@return string
+	*	@return array
 	**/
 	function headers() {
 		return $this->headers;
@@ -382,9 +381,8 @@ class Agent {
 	/**
 	*	Frame and transmit payload
 	*	@return string|FALSE
-	*	@param $socket resource
 	*	@param $op int
-	*	@param $payload string
+	*	@param $data string
 	**/
 	function send($op,$data='') {
 		$server=$this->server;
@@ -393,8 +391,7 @@ class Agent {
 		$buf='';
 		if ($len>0xffff)
 			$buf=pack('CCNN',$mask,0x7f,$len);
-		else
-		if ($len>0x7d)
+		elseif ($len>0x7d)
 			$buf=pack('CCn',$mask,0x7e,$len);
 		else
 			$buf=pack('CC',$mask,$len);
@@ -402,74 +399,73 @@ class Agent {
 		if (is_bool($server->write($this->socket,$buf)))
 			return FALSE;
 		if (!in_array($op,[WS::Pong,WS::Close]) &&
-			isset($this->events['send']) &&
-			is_callable($func=$this->events['send']))
+			isset($this->server->events['send']) &&
+			is_callable($func=$this->server->events['send']))
 			$func($this,$op,$data);
 		return $data;
 	}
 
 	/**
 	*	Retrieve and unmask payload
-	*	@return array|FALSE
+	*	@return bool|NULL
 	**/
 	function fetch() {
 		// Unmask payload
 		$server=$this->server;
 		if (is_bool($buf=$server->read($this->socket)))
 			return FALSE;
-		$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]);
-		// Dispatch
-		switch ($op & WS::OpCode) {
-		case WS::Ping:
-			$this->send(WS::Pong);
-			break;
-		case WS::Close:
-			$server->close($this->socket);
-			break;
-		case WS::Text:
-			$data=trim($data);
-		case WS::Binary:
-			if (isset($this->events['receive']) &&
-				is_callable($func=$this->events['receive']))
-				$func($this,$op,$data);
-			break;
+		while($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]);
+			// Dispatch
+			switch ($op & WS::OpCode) {
+			case WS::Ping:
+				$this->send(WS::Pong);
+				break;
+			case WS::Close:
+				$server->close($this->socket);
+				break;
+			case WS::Text:
+				$data=trim($data);
+			case WS::Binary:
+				if (isset($this->server->events['receive']) &&
+					is_callable($func=$this->server->events['receive']))
+					$func($this,$op,$data);
+				break;
+			}
+			$buf = substr($buf, $len+$pos);
 		}
-		return [$op,$data];
 	}
 
 	/**
 	*	Destroy object
-	*	@return NULL
 	**/
 	function __destruct() {
-		if (isset($this->events['disconnect']) &&
-			is_callable($func=$this->events['disconnect']))
+		if (isset($this->server->events['disconnect']) &&
+			is_callable($func=$this->server->events['disconnect']))
 			$func($this);
 	}
 
 	/**
-	*	Instantiate object
-	*	@return object
-	*	@param $server object
+	*	@param $server WS
 	*	@param $socket resource
 	*	@param $verb string
 	*	@param $uri string
@@ -482,9 +478,9 @@ class Agent {
 		$this->verb=$verb;
 		$this->uri=$uri;
 		$this->headers=$hdrs;
-		$this->events=$server->events();
-		if (isset($this->events['connect']) &&
-			is_callable($func=$this->events['connect']))
+
+		if (isset($server->events['connect']) &&
+			is_callable($func=$server->events['connect']))
 			$func($this);
 	}
 

+ 2 - 2
lib/f3/db/cursor.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -148,7 +148,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
 	function paginate(
 		$pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0,$bounce=TRUE) {
 		$total=$this->count($filter,$options,$ttl);
-		$count=ceil($total/$size);
+		$count=(int)ceil($total/$size);
 		if ($bounce)
 			$pos=max(0,min($pos,$count-1));
 		return [

+ 2 - 2
lib/f3/db/jig.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -90,7 +90,7 @@ class Jig {
 				$out=$fw->serialize($data);
 				break;
 		}
-		return $fw->write($this->dir.'/'.$file,$out);
+		return $fw->write($this->dir.$file,$out);
 	}
 
 	/**

+ 1 - 1
lib/f3/db/jig/mapper.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/db/jig/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/db/mongo.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/db/mongo/mapper.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/db/mongo/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/db/sql.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 77 - 18
lib/f3/db/sql/mapper.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -25,6 +25,11 @@ namespace DB\SQL;
 //! SQL data mapper
 class Mapper extends \DB\Cursor {
 
+	//@{ Error messages
+	const
+		E_PKey='Table %s does not have a primary key';
+	//@}
+
 	protected
 		//! PDO wrapper
 		$db,
@@ -353,21 +358,25 @@ class Mapper extends \DB\Cursor {
 	*	@param $ttl int|array
 	**/
 	function count($filter=NULL,array $options=NULL,$ttl=0) {
-		if (!($subquery_mode=($options && !empty($options['group']))))
-			$this->adhoc['_rows']=['expr'=>'COUNT(*)','value'=>NULL];
 		$adhoc=[];
-		foreach ($this->adhoc as $key=>$field)
-			// Add all adhoc fields
-			// (make them available for grouping, sorting, having)
-			$adhoc[]=$field['expr'].' AS '.$this->db->quotekey($key);
-		$fields=implode(',',$adhoc);
-		if ($subquery_mode) {
+		// with grouping involved, we need to wrap the actualy query and count the results
+		if ($subquery_mode=($options && !empty($options['group']))) {
+			$group_string=preg_replace('/HAVING.+$/i','',$options['group']);
+			$group_fields=array_flip(array_map('trim',explode(',',$group_string)));
+			foreach ($this->adhoc as $key=>$field)
+				// add adhoc fields that are used for grouping
+				if (isset($group_fields[$key]))
+					$adhoc[]=$field['expr'].' AS '.$this->db->quotekey($key);
+			$fields=implode(',',$adhoc);
 			if (empty($fields))
 				// Select at least one field, ideally the grouping fields
 				// or sqlsrv fails
-				$fields=preg_replace('/HAVING.+$/i','',$options['group']);
+				$fields=$group_string;
 			if (preg_match('/mssql|dblib|sqlsrv/',$this->engine))
 				$fields='TOP 100 PERCENT '.$fields;
+		} else {
+			// for simple count just add a new adhoc counter
+			$fields='COUNT(*) AS '.$this->db->quotekey('_rows');
 		}
 		list($sql,$args)=$this->stringify($fields,$filter,$options);
 		if ($subquery_mode)
@@ -425,11 +434,21 @@ class Mapper extends \DB\Cursor {
 			\Base::instance()->call($this->trigger['beforeinsert'],
 				[$this,$pkeys])===FALSE)
 			return $this;
+		if ($this->valid())
+			// duplicate record
+			foreach ($this->fields as $key=>&$field) {
+				$field['changed']=true;
+				if ($field['pkey'] && !$inc && $field['pdo_type']==\PDO::PARAM_INT
+					&& !$field['nullable'])
+					$inc=$key;
+				unset($field);
+			}
 		foreach ($this->fields as $key=>&$field) {
 			if ($field['pkey']) {
 				$field['previous']=$field['value'];
 				if (!$inc && $field['pdo_type']==\PDO::PARAM_INT &&
-					empty($field['value']) && !$field['nullable'])
+					empty($field['value']) && !$field['nullable'] &&
+					is_null($field['default']))
 					$inc=$key;
 				$filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
 				$nkeys[$nctr+1]=[$field['value'],$field['pdo_type']];
@@ -442,6 +461,7 @@ class Mapper extends \DB\Cursor {
 				$actr++;
 				$ckeys[]=$key;
 			}
+			unset($field);
 		}
 		if ($fields) {
 			$add=$aik='';
@@ -489,7 +509,6 @@ class Mapper extends \DB\Cursor {
 		$args=[];
 		$ctr=0;
 		$pairs='';
-		$filter='';
 		$pkeys=[];
 		foreach ($this->fields as $key=>$field)
 			if ($field['pkey'])
@@ -503,13 +522,16 @@ class Mapper extends \DB\Cursor {
 				$pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
 				$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]=[$field['previous'],$field['pdo_type']];
-			}
 		if ($pairs) {
+			$filter='';
+			foreach ($this->fields as $key=>$field)
+				if ($field['pkey']) {
+					$filter.=($filter?' AND ':' WHERE ').
+						$this->db->quotekey($key).'=?';
+					$args[++$ctr]=[$field['previous'],$field['pdo_type']];
+				}
+			if (!$filter)
+				user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR);
 			$sql='UPDATE '.$this->table.' SET '.$pairs.$filter;
 			$this->db->exec($sql,$args);
 		}
@@ -525,6 +547,41 @@ class Mapper extends \DB\Cursor {
 		return $this;
 	}
 
+	/**
+	 * batch-update multiple records at once
+	 * @param string|array $filter
+	 * @return int
+	 */
+	function updateAll($filter=NULL) {
+		$args=[];
+		$ctr=$out=0;
+		$pairs='';
+		foreach ($this->fields as $key=>$field)
+			if ($field['changed']) {
+				$pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
+				$args[++$ctr]=[$field['value'],$field['pdo_type']];
+			}
+		if ($filter)
+			if (is_array($filter)) {
+				$cond=array_shift($filter);
+				$args=array_merge($args,$filter);
+				$filter=' WHERE '.$cond;
+			} else
+				$filter=' WHERE '.$filter;
+		if ($pairs) {
+			$sql='UPDATE '.$this->table.' SET '.$pairs.$filter;
+			$out = $this->db->exec($sql,$args);
+		}
+		// reset changed flag after calling afterupdate
+		foreach ($this->fields as $key=>&$field) {
+			$field['changed']=FALSE;
+			$field['initial']=$field['value'];
+			unset($field);
+		}
+		return $out;
+	}
+
+
 	/**
 	*	Delete current record
 	*	@return int
@@ -568,6 +625,8 @@ class Mapper extends \DB\Cursor {
 				$field['previous']=NULL;
 			unset($field);
 		}
+		if (!$filter)
+			user_error(sprintf(self::E_PKey,$this->source),E_USER_ERROR);
 		foreach ($this->adhoc as &$field) {
 			$field['value']=NULL;
 			unset($field);

+ 4 - 3
lib/f3/db/sql/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -165,8 +165,9 @@ class Session extends Mapper {
 	*	@param $force bool
 	*	@param $onsuspect callback
 	*	@param $key string
+	*	@param $type string, column type for data field
 	**/
-	function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$key=NULL) {
+	function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$key=NULL,$type='TEXT') {
 		if ($force) {
 			$eol="\n";
 			$tab="\t";
@@ -182,7 +183,7 @@ class Session extends Mapper {
 				$db->quotekey($table,FALSE).' ('.$eol.
 					($sqlsrv?$tab.$db->quotekey('id').' INT IDENTITY,'.$eol:'').
 					$tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol.
-					$tab.$db->quotekey('data').' TEXT,'.$eol.
+					$tab.$db->quotekey('data').' '.$type.','.$eol.
 					$tab.$db->quotekey('ip').' VARCHAR(45),'.$eol.
 					$tab.$db->quotekey('agent').' VARCHAR(300),'.$eol.
 					$tab.$db->quotekey('stamp').' INTEGER,'.$eol.

+ 1 - 1
lib/f3/f3.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/image.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 6 - 3
lib/f3/log.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -39,8 +39,11 @@ class Log {
 			$fw->write(
 				$this->file,
 				date($format).
-					(isset($_SERVER['REMOTE_ADDR'])?
-						(' ['.$_SERVER['REMOTE_ADDR'].']'):'').' '.
+				(isset($_SERVER['REMOTE_ADDR'])?
+					(' ['.$_SERVER['REMOTE_ADDR'].
+					(($fwd=filter_var($fw->get('HEADERS.X-Forwarded-For'),
+						FILTER_VALIDATE_IP))?(' ('.$fwd.')'):'')
+					.']'):'').' '.
 				trim($line).PHP_EOL,
 				TRUE
 			);

+ 5 - 5
lib/f3/magic.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -59,14 +59,15 @@ abstract class Magic implements ArrayAccess {
 	**/
 	function offsetexists($key) {
 		return Base::instance()->visible($this,$key)?
-			isset($this->$key):$this->exists($key);
+			isset($this->$key):
+			($this->exists($key) && $this->get($key)!==NULL);
 	}
 
 	/**
 	*	Convenience method for assigning property value
 	*	@return mixed
 	*	@param $key string
-	*	@param $val scalar
+	*	@param $val mixed
 	**/
 	function offsetset($key,$val) {
 		return Base::instance()->visible($this,$key)?
@@ -111,7 +112,7 @@ abstract class Magic implements ArrayAccess {
 	*	Alias for offsetset()
 	*	@return mixed
 	*	@param $key string
-	*	@param $val scalar
+	*	@param $val mixed
 	**/
 	function __set($key,$val) {
 		return $this->offsetset($key,$val);
@@ -129,7 +130,6 @@ abstract class Magic implements ArrayAccess {
 
 	/**
 	*	Alias for offsetunset()
-	*	@return NULL
 	*	@param $key string
 	**/
 	function __unset($key) {

+ 13 - 16
lib/f3/markdown.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -141,7 +141,7 @@ class Markdown extends Prefab {
 						'/(?<=^|\n)(?:'.
 						'(;[^\n]*)|(?:<\?php.+?\?>?)|'.
 						'(?:\[(.+?)\])|'.
-						'(.+?)\h*=\h*'.
+						'(.+?)(\h*=\h*)'.
 						'((?:\\\\\h*\r?\n|.+?)*)'.
 						')((?:\r?\n)+|$)/',
 						$str,$matches,PREG_SET_ORDER
@@ -156,14 +156,14 @@ class Markdown extends Prefab {
 								'</span>';
 						elseif ($match[3])
 							$out.='<span class="ini_key">'.$match[3].
-								'</span>='.
-								($match[4]?
+								'</span>'.$match[4].
+								($match[5]?
 									('<span class="ini_value">'.
-										$match[4].'</span>'):'');
+										$match[5].'</span>'):'');
 						else
 							$out.=$match[0];
-						if (isset($match[5]))
-							$out.=$match[5];
+						if (isset($match[6]))
+							$out.=$match[6];
 					}
 					$str='<code>'.$out.'</code>';
 					break;
@@ -321,16 +321,13 @@ class Markdown extends Prefab {
 		$tmp='';
 		while ($str!=$tmp)
 			$str=preg_replace_callback(
-				'/(?<=\s|^)(?<!\\\\)([*_]{1,3})(.*?)(?!\\\\)\1(?=[\s[:punct:]]|$)/',
+				'/(?<=\s|^)(?<!\\\\)([*_])([*_]?)([*_]?)(.*?)(?!\\\\)\3\2\1(?=[\s[:punct:]]|$)/',
 				function($expr) {
-					switch (strlen($expr[1])) {
-						case 1:
-							return '<em>'.$expr[2].'</em>';
-						case 2:
-							return '<strong>'.$expr[2].'</strong>';
-						case 3:
-							return '<strong><em>'.$expr[2].'</em></strong>';
-					}
+					if ($expr[3])
+						return '<strong><em>'.$expr[4].'</em></strong>';
+					if ($expr[2])
+						return '<strong>'.$expr[4].'</strong>';
+					return '<em>'.$expr[4].'</em>';
 				},
 				preg_replace(
 					'/(?<!\\\\)~~(.*?)(?!\\\\)~~(?=[\s[:punct:]]|$)/',

+ 27 - 1
lib/f3/matrix.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -39,6 +39,32 @@ class Matrix extends Prefab {
 		);
 	}
 
+	/**
+	 * select a subset of fields from an input array
+	 * @param string|array $fields splittable string or array
+	 * @param string|array $data hive key or array
+	 * @return array
+	 */
+	function select($fields, $data) {
+		return array_intersect_key(is_array($data) ? $data : \Base::instance()->get($data),
+			array_flip(is_array($fields) ? $fields : \Base::instance()->split($fields)));
+	}
+
+	/**
+	 * walk with a callback function through a subset of fields from an input array
+	 * the callback receives the value, index-key and the full input array as parameters
+	 * set value parameter as reference and you're able to modify the data as well
+	 * @param string|array $fields splittable string or array of fields
+	 * @param string|array $data hive key or input array
+	 * @param callable $callback (mixed &$value, string $key, array $data)
+	 * @return array modified subset data
+	 */
+	function walk($fields, $data, $callback) {
+		$subset=$this->select($fields, $data);
+		array_walk($subset, $callback, $data);
+		return $subset;
+	}
+
 	/**
 	*	Rotate a two-dimensional array variable
 	*	@return NULL

+ 1 - 1
lib/f3/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 4 - 2
lib/f3/smtp.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -264,8 +264,10 @@ class SMTP extends Magic {
 			}
 			unset($val);
 		}
+		$from=isset($headers['Sender'])?$headers['Sender']:strstr($headers['From'],'<');
+		unset($headers['Sender']);
 		// Start message dialog
-		$this->dialog('MAIL FROM: '.strstr($headers['From'],'<'),$log,$mock);
+		$this->dialog('MAIL FROM: '.$from,$log,$mock);
 		foreach ($fw->split($headers['To'].
 			(isset($headers['Cc'])?(';'.$headers['Cc']):'').
 			(isset($headers['Bcc'])?(';'.$headers['Bcc']):'')) as $dst) {

+ 1 - 1
lib/f3/template.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 4 - 2
lib/f3/test.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -34,7 +34,9 @@ class Test {
 		//! Test results
 		$data=[],
 		//! Success indicator
-		$passed=TRUE;
+		$passed=TRUE,
+		//! Reporting level
+		$level;
 
 	/**
 	*	Return test results

+ 1 - 1
lib/f3/utf.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 94 - 9
lib/f3/web.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -33,11 +33,87 @@ class Web extends Prefab {
 		$wrapper;
 
 	/**
-	*	Detect MIME type using file extension
+	*	Detect MIME type using file extension or file inspection
 	*	@return string
 	*	@param $file string
+	*	@param $inspect bool
 	**/
-	function mime($file) {
+	function mime($file, $inspect=FALSE) {
+		if ($inspect) {
+			if (is_file($file) && is_readable($file)) {
+				// physical files
+				if (extension_loaded('fileinfo'))
+					$mime=mime_content_type($file);
+				elseif (preg_match('/Darwin/i',PHP_OS))
+					$mime=trim(exec('file -bI '.escapeshellarg($file)));
+				elseif (!preg_match('/^win/i',PHP_OS))
+					$mime=trim(exec('file -bi '.escapeshellarg($file)));
+				if (isset($mime) && !empty($mime)){
+					// cut charset information if any
+					$exp=explode(';',$mime,2);
+					$mime=$exp[0];
+				}
+			}
+			else {
+				// remote and stream files
+				if (ini_get('allow_url_fopen') && ($fhandle=fopen($file,'rb'))) {
+					// only get head bytes instead of whole file
+					$bytes=fread($fhandle,20);
+					fclose($fhandle);
+				}
+				elseif (($response=$this->request($file,['method' => 'HEAD']))
+					&& preg_grep('/HTTP\/[\d.]{1,3} 200/',$response['headers'])
+					&& ($type = preg_grep('/^Content-Type:/i',$response['headers']))) {
+					// get mime type directly from response header
+					return preg_replace('/^Content-Type:\s*/i','',array_pop($type));
+				}
+				else // load whole file
+					$bytes=file_get_contents($file);
+				if (extension_loaded('fileinfo')) {
+					// get mime from fileinfo
+					$finfo=finfo_open(FILEINFO_MIME_TYPE);
+					$mime=finfo_buffer($finfo,$bytes);
+				}
+				elseif ($bytes) {
+					// magic number header fallback
+					$map=[
+						'\x64\x6E\x73\x2E'=>'audio/basic',
+						'\x52\x49\x46\x46.{4}\x41\x56\x49\x20\x4C\x49\x53\x54'=>'video/avi',
+						'\x42\x4d'=>'image/bmp',
+						'\x42\x5A\x68'=>'application/x-bzip2',
+						'\x07\x64\x74\x32\x64\x64\x74\x64'=>'application/xml-dtd',
+						'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'=>'application/msword',
+						'\x50\x4B\x03\x04\x14\x00\x06\x00'=>'application/msword',
+						'\x0D\x44\x4F\x43'=>'application/msword',
+						'GIF\d+a'=>'image/gif',
+						'\x1F\x8B'=>'application/x-gzip',
+						'\xff\xd8\xff'=>'image/jpeg',
+						'\x49\x46\x00'=>'image/jpeg',
+						'\xFF\xFB'=>'audio/mpeg',
+						'\x49\x44\x33'=>'audio/mpeg',
+						'\x00\x00\x01\xBA'=>'video/mpeg',
+						'\x4F\x67\x67\x53\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00'=>'audio/vorbis',
+						'\x25\x50\x44\x46'=>'application/pdf',
+						'\x89PNG\x0d\x0a'=>'image/png',
+						'.{4}\x6D\x6F\x6F\x76\x'=>'video/quicktime',
+						'\x53\x49\x54\x21\x00'=>'application/x-stuffit',
+						'\x43\x57\x53'=>'application/x-shockwave-flash',
+						'\x1F\x8B\x08'=>'application/x-tar',
+						'\x49\x20\x49'=>'image/tiff',
+						'\x52\x49\x46\x46.{4}\x57\x41\x56\x45\x66\x6D\x74\x20'=>'audio/wav',
+						'\xFD\xFF\xFF\xFF\x20\x00\x00\x00'=>'application/vnd.ms-excel',
+						'\x50\x4B\x03\x04'=>'application/x-zip-compressed',
+						'[ -~]+$'=>'text/plain',
+					];
+					foreach ($map as $key=>$val)
+						if (preg_match('/^'.$key.'/',substr($bytes,0,128)))
+							return $val;
+				}
+			}
+			if (isset($mime) && !empty($mime))
+				return $mime;
+			// Fallback to file extension-based check if no mime was found yet
+		}
 		if (preg_match('/\w+$/',$file,$ext)) {
 			$map=[
 				'au'=>'audio/basic',
@@ -307,7 +383,7 @@ class Web extends Prefab {
 		$body=ob_get_clean();
 		if (!$err &&
 			$options['follow_location'] && $open_basedir &&
-			preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) &&
+			preg_grep('/HTTP\/[\d.]{1,3} 3\d{2}/',$headers) &&
 			preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) {
 			$options['max_redirects']--;
 			if($loc[1][0] == '/') {
@@ -454,7 +530,7 @@ class Web extends Prefab {
 						break;
 				}
 			if ($options['follow_location'] &&
-				preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) &&
+				preg_grep('/HTTP\/[\d.]{1,3} 3\d{2}/',$headers) &&
 				preg_match('/Location: (.+?)'.preg_quote($eol).'/',
 				$html[0],$loc)) {
 				$options['max_redirects']--;
@@ -582,7 +658,7 @@ class Web extends Prefab {
 		}
 		$result=$this->{'_'.$this->wrapper}($url,$options);
 		if ($result && isset($cache)) {
-			if (preg_match('/HTTP\/1\.\d 304/',
+			if (preg_match('/HTTP\/[\d.]{1,3} 304/',
 				implode($eol,$result['headers']))) {
 				$result=$cache->get($hash);
 				$result['cached']=TRUE;
@@ -617,11 +693,12 @@ class Web extends Prefab {
 		$dst='';
 		if (!isset($path))
 			$path=$fw->UI.';./';
-		foreach ($fw->split($path,FALSE) as $dir)
-			foreach ($files as $file)
+		foreach (array_unique($fw->split($path,FALSE)) as $dir)
+			foreach ($files as $i=>$file)
 				if (is_file($save=$fw->fixslashes($dir.$file)) &&
 					is_bool(strpos($save,'../')) &&
 					preg_match('/\.(css|js)$/i',$file)) {
+					unset($files[$i]);
 					if ($fw->CACHE &&
 						($cached=$cache->exists(
 							$hash=$fw->hash($save).'.'.$ext[0],$data)) &&
@@ -642,6 +719,12 @@ class Web extends Prefab {
 								$ptr+=strlen($parts[0]);
 								continue;
 							}
+							if ($ext[0]=='css'&&preg_match('/^url\(([^\'"].*?[^\'"])\)/i',
+									substr($src,$ptr),$parts)) {
+								$data.=$parts[0];
+								$ptr+=strlen($parts[0]);
+								continue;
+							}
 							if ($src[$ptr]=='/') {
 								if ($src[$ptr+1]=='*') {
 									// Multiline comment
@@ -695,7 +778,7 @@ class Web extends Prefab {
 								}
 								continue;
 							}
-							if (in_array($src[$ptr],['\'','"'])) {
+							if (in_array($src[$ptr],['\'','"','`'])) {
 								$match=$src[$ptr];
 								$data.=$match;
 								$ptr++;
@@ -855,6 +938,7 @@ class Web extends Prefab {
 			'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v',
 			'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z',
 			'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>'',
+			'њ'=>'nj','љ'=>'lj','ђ'=>'dj','џ'=>'dz','ћ'=>'c','ј'=>'j',
 			'\''=>'',
 		];
 	}
@@ -915,6 +999,7 @@ if (!function_exists('gzdecode')) {
 	/**
 	*	Decode gzip-compressed string
 	*	@param $str string
+	*	@return string
 	**/
 	function gzdecode($str) {
 		$fw=Base::instance();

+ 1 - 1
lib/f3/web/geo.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/web/google/recaptcha.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/web/google/staticmap.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 15 - 4
lib/f3/web/oauth2.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -27,7 +27,9 @@ class OAuth2 extends \Magic {
 
 	protected
 		//! Scopes and claims
-		$args=[];
+		$args=[],
+		//! Encoding
+		$enc_type = PHP_QUERY_RFC1738;
 
 	/**
 	*	Return OAuth2 authentication URI
@@ -36,7 +38,8 @@ class OAuth2 extends \Magic {
 	*	@param $query bool
 	**/
 	function uri($endpoint,$query=TRUE) {
-		return $endpoint.($query?('?'.http_build_query($this->args)):'');
+		return $endpoint.($query?('?'.
+				http_build_query($this->args,null,'&',$this->enc_type)):'');
 	}
 
 	/**
@@ -50,7 +53,7 @@ class OAuth2 extends \Magic {
 		$web=\Web::instance();
 		$options=[
 			'method'=>$method,
-			'content'=>http_build_query($this->args),
+			'content'=>http_build_query($this->args,null,'&',$this->enc_type),
 			'header'=>['Accept: application/json']
 		];
 		if ($token)
@@ -95,6 +98,14 @@ class OAuth2 extends \Magic {
 		);
 	}
 
+	/**
+	 * change default url encoding type, i.E. PHP_QUERY_RFC3986
+	 * @param $type
+	 */
+	function setEncoding($type) {
+		$this->enc_type = $type;
+	}
+
 	/**
 	*	URL-safe base64 encoding
 	*	@return array

+ 1 - 1
lib/f3/web/openid.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 

+ 1 - 1
lib/f3/web/pingback.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2019 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).