Browse Source

Updating F3 to v3.6.1

Alan Hardman 2 years ago
parent
commit
cd04e75815

+ 57 - 0
lib/CHANGELOG

@@ -1,5 +1,62 @@
 CHANGELOG
 
+3.6.1 (2 April 2017)
+*	NEW: Recaptcha plugin (#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)
+*	NEW: Allow OPTIONS method to return a response body (#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)
+*	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)
+*	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)
+*	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

+ 4 - 4
lib/audit.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -108,7 +108,7 @@ class Audit extends Prefab {
 	**/
 	function isdesktop($agent=NULL) {
 		if (!isset($agent))
-			$agent=Base::instance()->get('AGENT');
+			$agent=Base::instance()->AGENT;
 		return (bool)preg_match('/('.self::UA_Desktop.')/i',$agent) &&
 			!$this->ismobile($agent);
 	}
@@ -120,7 +120,7 @@ class Audit extends Prefab {
 	**/
 	function ismobile($agent=NULL) {
 		if (!isset($agent))
-			$agent=Base::instance()->get('AGENT');
+			$agent=Base::instance()->AGENT;
 		return (bool)preg_match('/('.self::UA_Mobile.')/i',$agent);
 	}
 
@@ -131,7 +131,7 @@ class Audit extends Prefab {
 	**/
 	function isbot($agent=NULL) {
 		if (!isset($agent))
-			$agent=Base::instance()->get('AGENT');
+			$agent=Base::instance()->AGENT;
 		return (bool)preg_match('/('.self::UA_Bot.')/i',$agent);
 	}
 

+ 4 - 4
lib/auth.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -160,12 +160,12 @@ class Auth {
 			stream_set_blocking($socket,TRUE);
 			$dialog();
 			$fw=Base::instance();
-			$dialog('EHLO '.$fw->get('HOST'));
+			$dialog('EHLO '.$fw->HOST);
 			if (strtolower($this->args['scheme'])=='tls') {
 				$dialog('STARTTLS');
 				stream_socket_enable_crypto(
 					$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
-				$dialog('EHLO '.$fw->get('HOST'));
+				$dialog('EHLO '.$fw->HOST);
 			}
 			// Authenticate
 			$dialog('AUTH LOGIN');
@@ -196,7 +196,7 @@ class Auth {
 	**/
 	function basic($func=NULL) {
 		$fw=Base::instance();
-		$realm=$fw->get('REALM');
+		$realm=$fw->REALM;
 		$hdr=NULL;
 		if (isset($_SERVER['HTTP_AUTHORIZATION']))
 			$hdr=$_SERVER['HTTP_AUTHORIZATION'];

File diff suppressed because it is too large
+ 268 - 208
lib/base.php


+ 2 - 2
lib/basket.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -193,7 +193,7 @@ class Basket extends Magic {
 	**/
 	function copyfrom($var) {
 		if (is_string($var))
-			$var=\Base::instance()->get($var);
+			$var=\Base::instance()->$var;
 		foreach ($var as $key=>$val)
 			$this->set($key,$val);
 	}

+ 5 - 6
lib/bcrypt.php

@@ -1,9 +1,7 @@
 <?php
 
 /**
-*	Lightweight password hashing library
-*
-*	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+*	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 *
 *	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 *
@@ -19,9 +17,12 @@
 *	You should have received a copy of the GNU General Public License along
 *	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
 *
-*	@deprecated use http://php.net/manual/en/ref.password.php instead (PHP 5.5+ only)
 **/
 
+/**
+*	Lightweight password hashing library (PHP 5.5+ only)
+*	@deprecated Use http://php.net/manual/en/ref.password.php instead
+**/
 class Bcrypt extends Prefab {
 
 	//@{ Error messages
@@ -52,8 +53,6 @@ class Bcrypt extends Prefab {
 		else {
 			$raw=16;
 			$iv='';
-			if (extension_loaded('mcrypt'))
-				$iv=mcrypt_create_iv($raw,MCRYPT_DEV_URANDOM);
 			if (!$iv && extension_loaded('openssl'))
 				$iv=openssl_random_pseudo_bytes($raw);
 			if (!$iv)

+ 1 - 1
lib/cli/ws.php

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

+ 11 - 8
lib/db/cursor.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -70,9 +70,10 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
 	*	Count records that match criteria
 	*	@return int
 	*	@param $filter array
+	*	@param $options array
 	*	@param $ttl int
 	**/
-	abstract function count($filter=NULL,$ttl=0);
+	abstract function count($filter=NULL,array $options=NULL,$ttl=0);
 
 	/**
 	*	Insert new record
@@ -142,24 +143,26 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
 	*	@param $filter string|array
 	*	@param $options array
 	*	@param $ttl int
+	*	@param $bounce bool
 	**/
 	function paginate(
-		$pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0) {
-		$total=$this->count($filter,$ttl);
+		$pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0,$bounce=TRUE) {
+		$total=$this->count($filter,$options,$ttl);
 		$count=ceil($total/$size);
-		$pos=max(0,min($pos,$count-1));
+		if ($bounce)
+			$pos=max(0,min($pos,$count-1));
 		return [
-			'subset'=>$this->find($filter,
+			'subset'=>($bounce || $pos<$count)?$this->find($filter,
 				array_merge(
 					$options?:[],
 					['limit'=>$size,'offset'=>$pos*$size]
 				),
 				$ttl
-			),
+			):[],
 			'total'=>$total,
 			'limit'=>$size,
 			'count'=>$count,
-			'pos'=>$pos<$count?$pos:0
+			'pos'=>$bounce?($pos<$count?$pos:0):$pos
 		];
 	}
 

+ 2 - 2
lib/db/jig.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -80,7 +80,7 @@ class Jig {
 		$fw=\Base::instance();
 		switch ($this->format) {
 			case self::FORMAT_JSON:
-				$out=json_encode($data,@constant('JSON_PRETTY_PRINT'));
+				$out=json_encode($data,JSON_PRETTY_PRINT);
 				break;
 			case self::FORMAT_Serialized:
 				$out=$fw->serialize($data);

+ 15 - 14
lib/db/jig/mapper.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -120,21 +120,20 @@ class Mapper extends \DB\Cursor {
 	*	@param $str string
 	**/
 	function token($str) {
-		$self=$this;
 		$str=preg_replace_callback(
 			'/(?<!\w)@(\w(?:[\w\.\[\]])*)/',
-			function($token) use($self) {
+			function($token) {
 				// Convert from JS dot notation to PHP array notation
 				return '$'.preg_replace_callback(
 					'/(\.\w+)|\[((?:[^\[\]]*|(?R))*)\]/',
-					function($expr) use($self) {
+					function($expr) {
 						$fw=\Base::instance();
 						return
 							'['.
 							($expr[1]?
 								$fw->stringify(substr($expr[1],1)):
 								(preg_match('/^\w+/',
-									$mix=$self->token($expr[2]))?
+									$mix=$this->token($expr[2]))?
 									$fw->stringify($mix):
 									$mix)).
 							']';
@@ -168,7 +167,7 @@ class Mapper extends \DB\Cursor {
 		$db=$this->db;
 		$now=microtime(TRUE);
 		$data=[];
-		if (!$fw->get('CACHE') || !$ttl || !($cached=$cache->exists(
+		if (!$fw->CACHE || !$ttl || !($cached=$cache->exists(
 			$hash=$fw->hash($this->db->dir().
 				$fw->stringify([$filter,$options])).'.jig',$data)) ||
 			$cached[0]+$ttl<microtime(TRUE)) {
@@ -259,7 +258,7 @@ class Mapper extends \DB\Cursor {
 			}
 			$data=array_slice($data,
 				$options['offset'],$options['limit']?:NULL,TRUE);
-			if ($fw->get('CACHE') && $ttl)
+			if ($fw->CACHE && $ttl)
 				// Save to cache backend
 				$cache->set($hash,$data,$ttl);
 		}
@@ -286,11 +285,12 @@ class Mapper extends \DB\Cursor {
 	*	Count records that match criteria
 	*	@return int
 	*	@param $filter array
+	*	@param $options array
 	*	@param $ttl int
 	**/
-	function count($filter=NULL,$ttl=0) {
+	function count($filter=NULL,array $options=NULL,$ttl=0) {
 		$now=microtime(TRUE);
-		$out=count($this->find($filter,NULL,$ttl,FALSE));
+		$out=count($this->find($filter,$options,$ttl,FALSE));
 		$this->db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
 			$this->file.' [count] '.($filter?json_encode($filter):''));
 		return $out;
@@ -366,15 +366,16 @@ class Mapper extends \DB\Cursor {
 	*	Delete current record
 	*	@return bool
 	*	@param $filter array
+	*	@param $quick bool
 	**/
-	function erase($filter=NULL) {
+	function erase($filter=NULL,$quick=FALSE) {
 		$db=$this->db;
 		$now=microtime(TRUE);
 		$data=&$db->read($this->file);
 		$pkey=['_id'=>$this->id];
 		if ($filter) {
 			foreach ($this->find($filter,NULL,FALSE) as $mapper)
-				if (!$mapper->erase())
+				if (!$mapper->erase(null,$quick))
 					return FALSE;
 			return TRUE;
 		}
@@ -384,7 +385,7 @@ class Mapper extends \DB\Cursor {
 		}
 		else
 			return FALSE;
-		if (isset($this->trigger['beforeerase']) &&
+		if (!$quick && isset($this->trigger['beforeerase']) &&
 			\Base::instance()->call($this->trigger['beforeerase'],
 				[$this,$pkey])===FALSE)
 			return FALSE;
@@ -403,7 +404,7 @@ class Mapper extends \DB\Cursor {
 		$db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
 			$this->file.' [erase] '.
 			($filter?preg_replace($keys,$vals,$filter[0],1):''));
-		if (isset($this->trigger['aftererase']))
+		if (!$quick && isset($this->trigger['aftererase']))
 			\Base::instance()->call($this->trigger['aftererase'],
 				[$this,$pkey]);
 		return TRUE;
@@ -427,7 +428,7 @@ class Mapper extends \DB\Cursor {
 	**/
 	function copyfrom($var,$func=NULL) {
 		if (is_string($var))
-			$var=\Base::instance()->get($var);
+			$var=\Base::instance()->$var;
 		if ($func)
 			$var=call_user_func($func,$var);
 		foreach ($var as $key=>$val)

+ 10 - 9
lib/db/jig/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -59,21 +59,22 @@ class Session extends Mapper {
 
 	/**
 	*	Return session data in serialized format
-	*	@return string|FALSE
+	*	@return string
 	*	@param $id string
 	**/
 	function read($id) {
 		$this->load(['@session_id=?',$this->sid=$id]);
 		if ($this->dry())
-			return FALSE;
+			return '';
 		if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
 			$fw=\Base::instance();
 			if (!isset($this->onsuspect) ||
 				$fw->call($this->onsuspect,[$this,$id])===FALSE) {
-				//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
+				// NB: `session_destroy` can't be called at that stage;
+				// `session_start` not completed
 				$this->destroy($id);
 				$this->close();
-				$fw->clear('COOKIE.'.session_name());
+				unset($fw->{'COOKIE.'.session_name()});
 				$fw->error(403);
 			}
 		}
@@ -178,12 +179,12 @@ class Session extends Mapper {
 		);
 		register_shutdown_function('session_commit');
 		$fw=\Base::instance();
-		$headers=$fw->get('HEADERS');
-		$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
+		$headers=$fw->HEADERS;
+		$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
 		if ($key)
-			$fw->set($key,$this->_csrf);
+			$fw->$key=$this->_csrf;
 		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		$this->_ip=$fw->get('IP');
+		$this->_ip=$fw->IP;
 	}
 
 }

+ 30 - 8
lib/db/mongo.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -37,6 +37,8 @@ class Mongo {
 		$dsn,
 		//! MongoDB object
 		$db,
+		//! Legacy flag
+		$legacy,
 		//! MongoDB log
 		$log;
 
@@ -63,7 +65,7 @@ class Mongo {
 	**/
 	function log($flag=TRUE) {
 		if ($flag) {
-			$cursor=$this->selectcollection('system.profile')->find();
+			$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).' ('.
@@ -76,7 +78,10 @@ class Mongo {
 						PHP_EOL;
 		} else {
 			$this->log=FALSE;
-			$this->setprofilinglevel(-1);
+			if ($this->legacy)
+				$this->db->setprofilinglevel(-1);
+			else
+				$this->db->command(['profile'=>-1]);
 		}
 		return $this->log;
 	}
@@ -87,8 +92,12 @@ class Mongo {
 	**/
 	function drop() {
 		$out=$this->db->drop();
-		if ($this->log!==FALSE)
-			$this->setprofilinglevel(2);
+		if ($this->log!==FALSE) {
+			if ($this->legacy)
+				$this->db->setprofilinglevel(2);
+			else
+				$this->db->command(['profile'=>2]);
+		}
 		return $out;
 	}
 
@@ -102,6 +111,14 @@ class Mongo {
 		return call_user_func_array([$this->db,$func],$args);
 	}
 
+	/**
+	*	Return TRUE if legacy driver is loaded
+	*	@return bool
+	**/
+	function legacy() {
+		return $this->legacy;
+	}
+
 	//! Prohibit cloning
 	private function __clone() {
 	}
@@ -114,9 +131,14 @@ class Mongo {
 	**/
 	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?:[]),$dbname);
-		$this->setprofilinglevel(2);
+		if ($this->legacy=class_exists('\MongoClient')) {
+			$this->db=new \MongoDB(new \MongoClient($dsn,$options?:[]),$dbname);
+			$this->db->setprofilinglevel(2);
+		}
+		else {
+			$this->db=(new \MongoDB\Client($dsn,$options?:[]))->$dbname;
+			$this->db->command(['profile'=>2]);
+		}
 	}
 
 }

+ 59 - 25
lib/db/mongo/mapper.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -28,6 +28,8 @@ class Mapper extends \DB\Cursor {
 	protected
 		//! MongoDB wrapper
 		$db,
+		//! Legacy flag
+		$legacy,
 		//! Mongo collection
 		$collection,
 		//! Mongo document
@@ -144,7 +146,7 @@ class Mapper extends \DB\Cursor {
 					]
 				);
 				$tmp=$this->db->selectcollection(
-					$fw->get('HOST').'.'.$fw->get('BASE').'.'.
+					$fw->HOST.'.'.$fw->BASE.'.'.
 					uniqid(NULL,TRUE).'.tmp'
 				);
 				$tmp->batchinsert($grp['retval'],['w'=>1]);
@@ -155,19 +157,29 @@ class Mapper extends \DB\Cursor {
 				$filter=$filter?:[];
 				$collection=$this->collection;
 			}
-			$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=[];
-			while ($this->cursor->hasnext())
-				$result[]=$this->cursor->getnext();
+			if ($this->legacy) {
+				$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=[];
+				while ($this->cursor->hasnext())
+					$result[]=$this->cursor->getnext();
+			}
+			else {
+				$this->cursor=$collection->find($filter,[
+					'sort'=>$options['order'],
+					'limit'=>$options['limit'],
+					'skip'=>$options['offset']
+				]);
+				$result=$this->cursor->toarray();
+			}
 			if ($options['group'])
 				$tmp->drop();
-			if ($fw->get('CACHE') && $ttl)
+			if ($fw->CACHE && $ttl)
 				// Save to cache backend
 				$cache->set($hash,$result,$ttl);
 		}
@@ -200,16 +212,17 @@ class Mapper extends \DB\Cursor {
 	*	Count records that match criteria
 	*	@return int
 	*	@param $filter array
+	*	@param $options array
 	*	@param $ttl int
 	**/
-	function count($filter=NULL,$ttl=0) {
+	function count($filter=NULL,array $options=NULL,$ttl=0) {
 		$fw=\Base::instance();
 		$cache=\Cache::instance();
 		if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
 			[$filter])).'.mongo',$result)) || !$ttl ||
 			$cached[0]+$ttl<microtime(TRUE)) {
 			$result=$this->collection->count($filter?:[]);
-			if ($fw->get('CACHE') && $ttl)
+			if ($fw->CACHE && $ttl)
 				// Save to cache backend
 				$cache->set($hash,$result,$ttl);
 		}
@@ -240,8 +253,14 @@ class Mapper extends \DB\Cursor {
 			\Base::instance()->call($this->trigger['beforeinsert'],
 				[$this,['_id'=>$this->document['_id']]])===FALSE)
 			return $this->document;
-		$this->collection->insert($this->document);
-		$pkey=['_id'=>$this->document['_id']];
+		if ($this->legacy) {
+			$this->collection->insert($this->document);
+			$pkey=['_id'=>$this->document['_id']];
+		}
+		else {
+			$result=$this->collection->insertone($this->document);
+			$pkey=['_id'=>$result->getinsertedid()];
+		}
 		if (isset($this->trigger['afterinsert']))
 			\Base::instance()->call($this->trigger['afterinsert'],
 				[$this,$pkey]);
@@ -259,8 +278,11 @@ class Mapper extends \DB\Cursor {
 			\Base::instance()->call($this->trigger['beforeupdate'],
 				[$this,$pkey])===FALSE)
 			return $this->document;
-		$this->collection->update(
-			$pkey,$this->document,['upsert'=>TRUE]);
+		$upsert=['upsert'=>TRUE];
+		if ($this->legacy)
+			$this->collection->update($pkey,$this->document,$upsert);
+		else
+			$this->collection->replaceone($pkey,$this->document,$upsert);
 		if (isset($this->trigger['afterupdate']))
 			\Base::instance()->call($this->trigger['afterupdate'],
 				[$this,$pkey]);
@@ -270,18 +292,29 @@ class Mapper extends \DB\Cursor {
 	/**
 	*	Delete current record
 	*	@return bool
+	*	@param $quick bool
 	*	@param $filter array
 	**/
-	function erase($filter=NULL) {
-		if ($filter)
-			return $this->collection->remove($filter);
+	function erase($filter=NULL,$quick=TRUE) {
+		if ($filter) {
+			if (!$quick) {
+				foreach ($this->find($filter) as $mapper)
+					if (!$mapper->erase())
+						return FALSE;
+				return TRUE;
+			}
+			return $this->legacy?
+				$this->collection->remove($filter):
+				$this->collection->deletemany($filter);
+		}
 		$pkey=['_id'=>$this->document['_id']];
 		if (isset($this->trigger['beforeerase']) &&
 			\Base::instance()->call($this->trigger['beforeerase'],
 				[$this,$pkey])===FALSE)
 			return FALSE;
-		$result=$this->collection->
-			remove(['_id'=>$this->document['_id']]);
+		$result=$this->legacy?
+			$this->collection->remove(['_id'=>$this->document['_id']]):
+			$this->collection->deleteone(['_id'=>$this->document['_id']]);
 		parent::erase();
 		if (isset($this->trigger['aftererase']))
 			\Base::instance()->call($this->trigger['aftererase'],
@@ -306,7 +339,7 @@ class Mapper extends \DB\Cursor {
 	**/
 	function copyfrom($var,$func=NULL) {
 		if (is_string($var))
-			$var=\Base::instance()->get($var);
+			$var=\Base::instance()->$var;
 		if ($func)
 			$var=call_user_func($func,$var);
 		foreach ($var as $key=>$val)
@@ -357,6 +390,7 @@ class Mapper extends \DB\Cursor {
 	**/
 	function __construct(\DB\Mongo $db,$collection,$fields=NULL) {
 		$this->db=$db;
+		$this->legacy=$db->legacy();
 		$this->collection=$db->selectcollection($collection);
 		$this->fields=$fields;
 		$this->reset();

+ 10 - 9
lib/db/mongo/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -59,21 +59,22 @@ class Session extends Mapper {
 
 	/**
 	*	Return session data in serialized format
-	*	@return string|FALSE
+	*	@return string
 	*	@param $id string
 	**/
 	function read($id) {
 		$this->load(['session_id'=>$this->sid=$id]);
 		if ($this->dry())
-			return FALSE;
+			return '';
 		if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
 			$fw=\Base::instance();
 			if (!isset($this->onsuspect) ||
 				$fw->call($this->onsuspect,[$this,$id])===FALSE) {
-				//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
+				// NB: `session_destroy` can't be called at that stage;
+				// `session_start` not completed
 				$this->destroy($id);
 				$this->close();
-				$fw->clear('COOKIE.'.session_name());
+				unset($fw->{'COOKIE.'.session_name()});
 				$fw->error(403);
 			}
 		}
@@ -178,12 +179,12 @@ class Session extends Mapper {
 		);
 		register_shutdown_function('session_commit');
 		$fw=\Base::instance();
-		$headers=$fw->get('HEADERS');
-		$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
+		$headers=$fw->HEADERS;
+		$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
 		if ($key)
-			$fw->set($key,$this->_csrf);
+			$fw->$key=$this->_csrf;
 		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		$this->_ip=$fw->get('IP');
+		$this->_ip=$fw->IP;
 	}
 
 }

+ 33 - 24
lib/db/sql.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -113,16 +113,16 @@ class SQL {
 
 	/**
 	*	Cast value to PHP type
-	*	@return scalar
+	*	@return mixed
 	*	@param $type string
-	*	@param $val scalar
+	*	@param $val mixed
 	**/
 	function value($type,$val) {
 		switch ($type) {
 			case self::PARAM_FLOAT:
-				return (float)(is_string($val)
-					? str_replace(',','.',preg_replace('/([.,])(?!\d+$)/','',$val))
-					: $val);
+				if (!is_string($val))
+					$val=str_replace(',','.',$val);
+				return $val;
 			case \PDO::PARAM_NULL:
 				return (unset)$val;
 			case \PDO::PARAM_INT:
@@ -185,7 +185,7 @@ class SQL {
 				continue;
 			$now=microtime(TRUE);
 			$keys=$vals=[];
-			if ($fw->get('CACHE') && $ttl && ($cached=$cache->exists(
+			if ($fw->CACHE && $ttl && ($cached=$cache->exists(
 				$hash=$fw->hash($this->dsn.$cmd.
 				$fw->stringify($arg)).($tag?'.'.$tag:'').'.sql',$result)) &&
 				$cached[0]+$ttl>microtime(TRUE)) {
@@ -246,7 +246,7 @@ class SQL {
 								$result[$pos][trim($key,'\'"[]`')]=$val;
 						}
 					$this->rows=count($result);
-					if ($fw->get('CACHE') && $ttl)
+					if ($fw->CACHE && $ttl)
 						// Save to cache backend
 						$cache->set($hash,$result,$ttl);
 				}
@@ -297,6 +297,13 @@ class SQL {
 	*	@param $ttl int|array
 	**/
 	function schema($table,$fields=NULL,$ttl=0) {
+		$fw=\Base::instance();
+		$cache=\Cache::instance();
+		if ($fw->CACHE && $ttl &&
+			($cached=$cache->exists(
+				$hash=$fw->hash($this->dsn.$table).'.schema',$result)) &&
+			$cached[0]+$ttl>microtime(TRUE))
+			return $result;
 		if (strpos($table,'.'))
 			list($schema,$table)=explode('.',$table);
 		// Supported engines
@@ -354,32 +361,34 @@ class SQL {
 		];
 		if (is_string($fields))
 			$fields=\Base::instance()->split($fields);
+		$conv=[
+			'int\b|integer'=>\PDO::PARAM_INT,
+			'bool'=>\PDO::PARAM_BOOL,
+			'blob|bytea|image|binary'=>\PDO::PARAM_LOB,
+			'float|real|double|decimal|numeric'=>self::PARAM_FLOAT,
+			'.+'=>\PDO::PARAM_STR
+		];
 		foreach ($cmd as $key=>$val)
 			if (preg_match('/'.$key.'/',$this->engine)) {
 				$rows=[];
-				foreach ($this->exec($val[0],NULL,$ttl) as $row) {
-					if (!$fields || in_array($row[$val[1]],$fields))
+				foreach ($this->exec($val[0],NULL) as $row)
+					if (!$fields || in_array($row[$val[1]],$fields)) {
+						foreach ($conv as $regex=>$type)
+							if (preg_match('/'.$regex.'/i',$row[$val[2]]))
+								break;
 						$rows[$row[$val[1]]]=[
 							'type'=>$row[$val[2]],
-							'pdo_type'=>
-								preg_match('/int\b|integer/i',$row[$val[2]])?
-									\PDO::PARAM_INT:
-									(preg_match('/bool/i',$row[$val[2]])?
-										\PDO::PARAM_BOOL:
-										(preg_match(
-											'/blob|bytea|image|binary/i',
-											$row[$val[2]])?\PDO::PARAM_LOB:
-											(preg_match(
-												'/float|decimal|real|numeric|double/i',
-												$row[$val[2]])?self::PARAM_FLOAT:
-												\PDO::PARAM_STR))),
+							'pdo_type'=>$type,
 							'default'=>is_string($row[$val[3]])?
 								preg_replace('/^\s*([\'"])(.*)\1\s*/','\2',
 								$row[$val[3]]):$row[$val[3]],
 							'nullable'=>$row[$val[4]]==$val[5],
 							'pkey'=>$row[$val[6]]==$val[7]
 						];
-				}
+					}
+				if ($fw->CACHE && $ttl)
+					// Save to cache backend
+					$cache->set($hash,$rows,$ttl);
 				return $rows;
 			}
 		user_error(sprintf(self::E_PKey,$table),E_USER_ERROR);
@@ -492,7 +501,7 @@ class SQL {
 			$options=[];
 		if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql')
 			$options+=[\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '.
-				strtolower(str_replace('-','',$fw->get('ENCODING'))).';'];
+				strtolower(str_replace('-','',$fw->ENCODING)).';'];
 		$this->pdo=new \PDO($dsn,$user,$pw,$options);
 		$this->engine=$this->pdo->getattribute(\PDO::ATTR_DRIVER_NAME);
 	}

+ 81 - 61
lib/db/sql/mapper.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -39,7 +39,9 @@ class Mapper extends \DB\Cursor {
 		//! Defined fields
 		$fields,
 		//! Adhoc fields
-		$adhoc=[];
+		$adhoc=[],
+		//! Dynamic properties
+		$props=[];
 
 	/**
 	*	Return database type
@@ -58,15 +60,6 @@ class Mapper extends \DB\Cursor {
 	}
 
 	/**
-	*	Return TRUE if field is defined
-	*	@return bool
-	*	@param $key string
-	**/
-	function exists($key) {
-		return array_key_exists($key,$this->fields+$this->adhoc);
-	}
-
-	/**
 	*	Return TRUE if any/specified field value has changed
 	*	@return bool
 	*	@param $key string
@@ -81,6 +74,15 @@ class Mapper extends \DB\Cursor {
 	}
 
 	/**
+	*	Return TRUE if field is defined
+	*	@return bool
+	*	@param $key string
+	**/
+	function exists($key) {
+		return array_key_exists($key,$this->fields+$this->adhoc);
+	}
+
+	/**
 	*	Assign value to field
 	*	@return scalar
 	*	@param $key string
@@ -95,12 +97,14 @@ class Mapper extends \DB\Cursor {
 				$this->fields[$key]['changed']=TRUE;
 			return $this->fields[$key]['value']=$val;
 		}
-		// adjust result on existing expressions
+		// Adjust result on existing expressions
 		if (isset($this->adhoc[$key]))
 			$this->adhoc[$key]['value']=$val;
-		else
+		elseif (is_string($val))
 			// Parenthesize expression in case it's a subquery
 			$this->adhoc[$key]=['expr'=>'('.$val.')','value'=>NULL];
+		else
+			$this->props[$key]=$val;
 		return $val;
 	}
 
@@ -116,6 +120,8 @@ class Mapper extends \DB\Cursor {
 			return $this->fields[$key]['value'];
 		elseif (array_key_exists($key,$this->adhoc))
 			return $this->adhoc[$key]['value'];
+		elseif (array_key_exists($key,$this->props))
+			return $this->props[$key];
 		user_error(sprintf(self::E_Field,$key),E_USER_ERROR);
 	}
 
@@ -127,26 +133,22 @@ class Mapper extends \DB\Cursor {
 	function clear($key) {
 		if (array_key_exists($key,$this->adhoc))
 			unset($this->adhoc[$key]);
+		else
+			unset($this->props[$key]);
 	}
 
 	/**
-	*	Get PHP type equivalent of PDO constant
-	*	@return string
-	*	@param $pdo string
+	*	Invoke dynamic method
+	*	@return mixed
+	*	@param $func string
+	*	@param $args array
 	**/
-	function type($pdo) {
-		switch ($pdo) {
-			case \PDO::PARAM_NULL:
-				return 'unset';
-			case \PDO::PARAM_INT:
-				return 'int';
-			case \PDO::PARAM_BOOL:
-				return 'bool';
-			case \PDO::PARAM_STR:
-				return 'string';
-			case \DB\SQL::PARAM_FLOAT:
-				return 'float';
-		}
+	function __call($func,$args) {
+		return call_user_func_array(
+			(array_key_exists($func,$this->props)?
+				$this->props[$func]:
+				$this->$func),$args
+		);
 	}
 
 	/**
@@ -192,14 +194,13 @@ class Mapper extends \DB\Cursor {
 	}
 
 	/**
-	*	Build query string and execute
-	*	@return static[]
+	*	Build query string and arguments
+	*	@return array
 	*	@param $fields string
 	*	@param $filter string|array
 	*	@param $options array
-	*	@param $ttl int|array
 	**/
-	function select($fields,$filter=NULL,array $options=NULL,$ttl=0) {
+	function stringify($fields,$filter=NULL,array $options=NULL) {
 		if (!$options)
 			$options=[];
 		$options+=[
@@ -226,7 +227,8 @@ class Mapper extends \DB\Cursor {
 					return preg_replace_callback(
 						'/\b(\w+)\h*(HAVING.+|$)/i',
 						function($parts) use($db) {
-							return $db->quotekey($parts[1]).(isset($parts[2])?(' '.$parts[2]):'');
+							return $db->quotekey($parts[1]).
+								(isset($parts[2])?(' '.$parts[2]):'');
 						},
 						$str
 					);
@@ -275,6 +277,19 @@ class Mapper extends \DB\Cursor {
 			if ($options['offset'])
 				$sql.=' OFFSET '.(int)$options['offset'];
 		}
+		return [$sql,$args];
+	}
+
+	/**
+	*	Build query string and execute
+	*	@return object
+	*	@param $fields string
+	*	@param $filter string|array
+	*	@param $options array
+	*	@param $ttl int|array
+	**/
+	function select($fields,$filter=NULL,array $options=NULL,$ttl=0) {
+		list($sql,$args)=$this->stringify($fields,$filter,$options);
 		$result=$this->db->exec($sql,$args,$ttl);
 		$out=[];
 		foreach ($result as &$row) {
@@ -284,8 +299,6 @@ class Mapper extends \DB\Cursor {
 						$val=$this->db->value(
 							$this->fields[$field]['pdo_type'],$val);
 				}
-				elseif (array_key_exists($field,$this->adhoc))
-					$this->adhoc[$field]['value']=$val;
 				unset($val);
 			}
 			$out[]=$this->factory($row);
@@ -324,24 +337,18 @@ class Mapper extends \DB\Cursor {
 	*	Count records that match criteria
 	*	@return int
 	*	@param $filter string|array
+	*	@param $options array
 	*	@param $ttl int|array
 	**/
-	function count($filter=NULL,$ttl=0) {
-		$sql='SELECT COUNT(*) AS '.
-			$this->db->quotekey('rows').' FROM '.$this->table;
-		$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:[1=>$args];
-				list($filter)=$filter;
-			}
-			$sql.=' WHERE '.$filter;
-		}
+	function count($filter=NULL,array $options=NULL,$ttl=0) {
+		$adhoc='';
+		foreach ($this->adhoc as $key=>$field)
+			$adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
+		list($sql,$args)=$this->stringify('*'.$adhoc,$filter,$options);
+		$sql='SELECT COUNT(*) AS '.$this->db->quotekey('_rows').' '.
+			'FROM ('.$sql.') AS '.$this->db->quotekey('_temp');
 		$result=$this->db->exec($sql,$args,$ttl);
-		return $result[0]['rows'];
+		return $result[0]['_rows'];
 	}
 
 	/**
@@ -409,8 +416,6 @@ class Mapper extends \DB\Cursor {
 				$actr++;
 				$ckeys[]=$key;
 			}
-			$field['changed']=FALSE;
-			unset($field);
 		}
 		if ($fields) {
 			$this->db->exec(
@@ -430,7 +435,7 @@ class Mapper extends \DB\Cursor {
 			if ($this->engine!='oci' && !($this->engine=='pgsql' && !$seq))
 				$this->_id=$this->db->lastinsertid($seq);
 			// Reload to obtain default and auto-increment field values
-			if ($inc || $filter)
+			if ($reload=($inc || $filter))
 				$this->load($inc?
 					[$inc.'=?',$this->db->value(
 						$this->fields[$inc]['pdo_type'],$this->_id)]:
@@ -438,6 +443,13 @@ class Mapper extends \DB\Cursor {
 			if (isset($this->trigger['afterinsert']))
 				\Base::instance()->call($this->trigger['afterinsert'],
 					[$this,$pkeys]);
+			// reset changed flag after calling afterinsert
+			if (!$reload)
+				foreach ($this->fields as $key=>&$field) {
+					$field['changed']=FALSE;
+					$field['initial']=$field['value'];
+					unset($field);
+				}
 		}
 		return $this;
 	}
@@ -473,10 +485,10 @@ class Mapper extends \DB\Cursor {
 		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'],
-					[$this,$pkeys]);
 		}
+		if (isset($this->trigger['afterupdate']))
+			\Base::instance()->call($this->trigger['afterupdate'],
+				[$this,$pkeys]);
 		// reset changed flag after calling afterupdate
 		foreach ($this->fields as $key=>&$field) {
 				$field['changed']=FALSE;
@@ -489,10 +501,17 @@ class Mapper extends \DB\Cursor {
 	/**
 	*	Delete current record
 	*	@return int
+	*	@param $quick bool
 	*	@param $filter string|array
 	**/
-	function erase($filter=NULL) {
-		if ($filter) {
+	function erase($filter=NULL,$quick=TRUE) {
+		if (isset($filter)) {
+			if (!$quick) {
+				$out=0;
+				foreach ($this->find($filter) as $mapper)
+					$out+=$mapper->erase();
+				return $out;
+			}
 			$args=[];
 			if (is_array($filter)) {
 				$args=isset($filter[1]) && is_array($filter[1])?
@@ -502,7 +521,8 @@ class Mapper extends \DB\Cursor {
 				list($filter)=$filter;
 			}
 			return $this->db->
-				exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args);
+				exec('DELETE FROM '.$this->table.
+				($filter?' WHERE '.$filter:'').';',$args);
 		}
 		$args=[];
 		$ctr=0;
@@ -566,7 +586,7 @@ class Mapper extends \DB\Cursor {
 	**/
 	function copyfrom($var,$func=NULL) {
 		if (is_string($var))
-			$var=\Base::instance()->get($var);
+			$var=\Base::instance()->$var;
 		if ($func)
 			$var=call_user_func($func,$var);
 		foreach ($var as $key=>$val)

+ 8 - 8
lib/db/sql/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -59,13 +59,13 @@ class Session extends Mapper {
 
 	/**
 	*	Return session data in serialized format
-	*	@return string|FALSE
+	*	@return string
 	*	@param $id string
 	**/
 	function read($id) {
 		$this->load(['session_id=?',$this->sid=$id]);
 		if ($this->dry())
-			return FALSE;
+			return '';
 		if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
 			$fw=\Base::instance();
 			if (!isset($this->onsuspect) ||
@@ -73,7 +73,7 @@ class Session extends Mapper {
 				//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
 				$this->destroy($id);
 				$this->close();
-				$fw->clear('COOKIE.'.session_name());
+				unset($fw->{'COOKIE.'.session_name()});
 				$fw->error(403);
 			}
 		}
@@ -200,12 +200,12 @@ class Session extends Mapper {
 		);
 		register_shutdown_function('session_commit');
 		$fw=\Base::instance();
-		$headers=$fw->get('HEADERS');
-		$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
+		$headers=$fw->HEADERS;
+		$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
 		if ($key)
-			$fw->set($key,$this->_csrf);
+			$fw->$key=$this->_csrf;
 		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		$this->_ip=$fw->get('IP');
+		$this->_ip=$fw->IP;
 	}
 
 }

+ 1 - 1
lib/f3.php

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

+ 10 - 10
lib/image.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -409,7 +409,7 @@ class Image {
 			return FALSE;
 		}
 		$fw=Base::instance();
-		foreach ($fw->split($path?:$fw->get('UI').';./') as $dir)
+		foreach ($fw->split($path?:$fw->UI.';./') as $dir)
 			if (is_file($path=$dir.$font)) {
 				$seed=strtoupper(substr(
 					$ssl?bin2hex(openssl_random_pseudo_bytes($len)):uniqid(),
@@ -448,7 +448,7 @@ class Image {
 				}
 				imagesavealpha($this->data,TRUE);
 				if ($key)
-					$fw->set($key,$seed);
+					$fw->$key=$seed;
 				return $this->save();
 			}
 		user_error(self::E_Font,E_USER_ERROR);
@@ -480,7 +480,7 @@ class Image {
 		$format=$args?array_shift($args):'png';
 		if (PHP_SAPI!='cli') {
 			header('Content-Type: image/'.$format);
-			header('X-Powered-By: '.Base::instance()->get('PACKAGE'));
+			header('X-Powered-By: '.Base::instance()->PACKAGE);
 		}
 		call_user_func_array(
 			'image'.$format,
@@ -518,10 +518,10 @@ class Image {
 	function save() {
 		$fw=Base::instance();
 		if ($this->flag) {
-			if (!is_dir($dir=$fw->get('TEMP')))
+			if (!is_dir($dir=$fw->TEMP))
 				mkdir($dir,Base::MODE,TRUE);
 			$this->count++;
-			$fw->write($dir.'/'.$fw->get('SEED').'.'.
+			$fw->write($dir.'/'.$fw->SEED.'.'.
 				$fw->hash($this->file).'-'.$this->count.'.png',
 				$this->dump());
 		}
@@ -535,8 +535,8 @@ class Image {
 	**/
 	function restore($state=1) {
 		$fw=Base::instance();
-		if ($this->flag && is_file($file=($path=$fw->get('TEMP').
-			$fw->get('SEED').'.'.$fw->hash($this->file).'-').$state.'.png')) {
+		if ($this->flag && is_file($file=($path=$fw->TEMP.
+			$fw->SEED.'.'.$fw->hash($this->file).'-').$state.'.png')) {
 			if (is_resource($this->data))
 				imagedestroy($this->data);
 			$this->data=imagecreatefromstring($fw->read($file));
@@ -589,7 +589,7 @@ class Image {
 			// Create image from file
 			$this->file=$file;
 			if (!isset($path))
-				$path=$fw->get('UI').';./';
+				$path=$fw->UI.';./';
 			foreach ($fw->split($path,FALSE) as $dir)
 				if (is_file($dir.$file))
 					return $this->load($fw->read($dir.$file));
@@ -605,7 +605,7 @@ class Image {
 		if (is_resource($this->data)) {
 			imagedestroy($this->data);
 			$fw=Base::instance();
-			$path=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash($this->file);
+			$path=$fw->TEMP.$fw->SEED.'.'.$fw->hash($this->file);
 			if ($glob=@glob($path.'*.png',GLOB_NOSORT))
 				foreach ($glob as $match)
 					if (preg_match('/-(\d+)\.png/',$match))

+ 2 - 2
lib/log.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -59,7 +59,7 @@ class Log {
 	**/
 	function __construct($file) {
 		$fw=Base::instance();
-		if (!is_dir($dir=$fw->get('LOGS')))
+		if (!is_dir($dir=$fw->LOGS))
 			mkdir($dir,Base::MODE,TRUE);
 		$this->file=$dir.$file;
 	}

+ 1 - 1
lib/magic.php

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

+ 24 - 30
lib/markdown.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -64,7 +64,7 @@ class Markdown extends Prefab {
 	protected function _fence($hint,$str) {
 		$str=$this->snip($str);
 		$fw=Base::instance();
-		if ($fw->get('HIGHLIGHT')) {
+		if ($fw->HIGHLIGHT) {
 			switch (strtolower($hint)) {
 				case 'php':
 					$str=$fw->highlight($str);
@@ -288,20 +288,19 @@ class Markdown extends Prefab {
 		if (strlen($str)) {
 			if (preg_match('/^(.+?\n)([>#].+)$/s',$str,$parts))
 				return $this->_p($parts[1]).$this->build($parts[2]);
-			$self=$this;
 			$str=preg_replace_callback(
 				'/([^<>\[]+)?(<[\?%].+?[\?%]>|<.+?>|\[.+?\]\s*\(.+?\))|'.
 				'(.+)/s',
-				function($expr) use($self) {
+				function($expr) {
 					$tmp='';
 					if (isset($expr[4]))
-						$tmp.=$self->esc($expr[4]);
+						$tmp.=$this->esc($expr[4]);
 					else {
 						if (isset($expr[1]))
-							$tmp.=$self->esc($expr[1]);
+							$tmp.=$this->esc($expr[1]);
 						$tmp.=$expr[2];
 						if (isset($expr[3]))
-							$tmp.=$self->esc($expr[3]);
+							$tmp.=$this->esc($expr[3]);
 					}
 					return $tmp;
 				},
@@ -347,17 +346,16 @@ class Markdown extends Prefab {
 	*	@param $str string
 	**/
 	protected function _img($str) {
-		$self=$this;
 		return preg_replace_callback(
 			'/!(?:\[(.+?)\])?\h*\(<?(.*?)>?(?:\h*"(.*?)"\h*)?\)/',
-			function($expr) use($self) {
+			function($expr) {
 				return '<img src="'.$expr[2].'"'.
 					(empty($expr[1])?
 						'':
-						(' alt="'.$self->esc($expr[1]).'"')).
+						(' alt="'.$this->esc($expr[1]).'"')).
 					(empty($expr[3])?
 						'':
-						(' title="'.$self->esc($expr[3]).'"')).' />';
+						(' title="'.$this->esc($expr[3]).'"')).' />';
 			},
 			$str
 		);
@@ -369,15 +367,14 @@ class Markdown extends Prefab {
 	*	@param $str string
 	**/
 	protected function _a($str) {
-		$self=$this;
 		return preg_replace_callback(
 			'/(?<!\\\\)\[(.+?)(?!\\\\)\]\h*\(<?(.*?)>?(?:\h*"(.*?)"\h*)?\)/',
-			function($expr) use($self) {
-				return '<a href="'.$self->esc($expr[2]).'"'.
+			function($expr) {
+				return '<a href="'.$this->esc($expr[2]).'"'.
 					(empty($expr[3])?
 						'':
-						(' title="'.$self->esc($expr[3]).'"')).
-					'>'.$self->scan($expr[1]).'</a>';
+						(' title="'.$this->esc($expr[3]).'"')).
+					'>'.$this->scan($expr[1]).'</a>';
 			},
 			$str
 		);
@@ -389,12 +386,11 @@ class Markdown extends Prefab {
 	*	@param $str string
 	**/
 	protected function _auto($str) {
-		$self=$this;
 		return preg_replace_callback(
 			'/`.*?<(.+?)>.*?`|<(.+?)>/',
-			function($expr) use($self) {
+			function($expr) {
 				if (empty($expr[1]) && parse_url($expr[2],PHP_URL_SCHEME)) {
-					$expr[2]=$self->esc($expr[2]);
+					$expr[2]=$this->esc($expr[2]);
 					return '<a href="'.$expr[2].'">'.$expr[2].'</a>';
 				}
 				return $expr[0];
@@ -409,12 +405,11 @@ class Markdown extends Prefab {
 	*	@param $str string
 	**/
 	protected function _code($str) {
-		$self=$this;
 		return preg_replace_callback(
 			'/`` (.+?) ``|(?<!\\\\)`(.+?)(?!\\\\)`/',
-			function($expr) use($self) {
+			function($expr) {
 				return '<code>'.
-					$self->esc(empty($expr[1])?$expr[2]:$expr[1]).'</code>';
+					$this->esc(empty($expr[1])?$expr[2]:$expr[1]).'</code>';
 			},
 			$str
 		);
@@ -436,7 +431,7 @@ class Markdown extends Prefab {
 		foreach ($this->special as $key=>$val)
 			$str=preg_replace('/'.preg_quote($key,'/').'/i',$val,$str);
 		return htmlspecialchars($str,ENT_COMPAT,
-			Base::instance()->get('ENCODING'),FALSE);
+			Base::instance()->ENCODING,FALSE);
 	}
 
 	/**
@@ -488,7 +483,6 @@ class Markdown extends Prefab {
 				'p'=>'/^(.+?(?:\n{2,}|\n*$))/s'
 			];
 		}
-		$self=$this;
 		// Treat lines with nothing but whitespaces as empty lines
 		$str=preg_replace('/\n\h+(?=\n)/',"\n",$str);
 		// Initialize block parser
@@ -509,16 +503,16 @@ class Markdown extends Prefab {
 						'/(?<!\\\\)\[('.$ref.')(?!\\\\)\]\s*\[\]|'.
 						'(!?)(?:\[([^\[\]]+)\]\s*)?'.
 						'(?<!\\\\)\[('.$ref.')(?!\\\\)\]/',
-						function($expr) use($match,$self) {
+						function($expr) use($match) {
 							return (empty($expr[2]))?
 								// Anchor
-								('<a href="'.$self->esc($match[2]).'"'.
+								('<a href="'.$this->esc($match[2]).'"'.
 								(empty($match[3])?
 									'':
 									(' title="'.
-										$self->esc($match[3]).'"')).'>'.
+										$this->esc($match[3]).'"')).'>'.
 								// Link
-								$self->scan(
+								$this->scan(
 									empty($expr[3])?
 										(empty($expr[1])?
 											$expr[4]:
@@ -530,11 +524,11 @@ class Markdown extends Prefab {
 								(empty($expr[2])?
 									'':
 									(' alt="'.
-										$self->esc($expr[3]).'"')).
+										$this->esc($expr[3]).'"')).
 								(empty($match[3])?
 									'':
 									(' title="'.
-										$self->esc($match[3]).'"')).
+										$this->esc($match[3]).'"')).
 								' />');
 						},
 						$tmp=$dst

+ 1 - 1
lib/matrix.php

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

+ 9 - 9
lib/session.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -58,13 +58,13 @@ class Session {
 
 	/**
 	*	Return session data in serialized format
-	*	@return string|FALSE
+	*	@return string
 	*	@param $id string
 	**/
 	function read($id) {
 		$this->sid=$id;
 		if (!$data=$this->_cache->get($id.'.@'))
-			return FALSE;
+			return '';
 		if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) {
 			$fw=Base::instance();
 			if (!isset($this->onsuspect) ||
@@ -72,7 +72,7 @@ class Session {
 				//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
 				$this->destroy($id);
 				$this->close();
-				$fw->clear('COOKIE.'.session_name());
+				unset($fw->{'COOKIE.'.session_name()});
 				$fw->error(403);
 			}
 		}
@@ -87,7 +87,7 @@ class Session {
 	**/
 	function write($id,$data) {
 		$fw=Base::instance();
-		$jar=$fw->get('JAR');
+		$jar=$fw->JAR;
 		$this->_cache->set($id.'.@',
 			[
 				'data'=>$data,
@@ -181,12 +181,12 @@ class Session {
 		);
 		register_shutdown_function('session_commit');
 		$fw=\Base::instance();
-		$headers=$fw->get('HEADERS');
-		$this->_csrf=$fw->get('SEED').'.'.$fw->hash(mt_rand());
+		$headers=$fw->HEADERS;
+		$this->_csrf=$fw->SEED.'.'.$fw->hash(mt_rand());
 		if ($key)
-			$fw->set($key,$this->_csrf);
+			$fw->$key=$this->_csrf;
 		$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
-		$this->_ip=$fw->get('IP');
+		$this->_ip=$fw->IP;
 	}
 
 }

+ 23 - 18
lib/smtp.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -45,6 +45,8 @@ class SMTP extends Magic {
 		$user,
 		//! Password
 		$pw,
+		//! TLS/SSL stream context
+		$context,
 		//! TCP/IP socket
 		$socket,
 		//! Server-client conversation
@@ -117,7 +119,7 @@ class SMTP extends Magic {
 	*	Send SMTP command and record server response
 	*	@return string
 	*	@param $cmd string
-	*	@param $log bool
+	*	@param $log bool|string
 	*	@param $mock bool
 	**/
 	protected function dialog($cmd=NULL,$log=TRUE,$mock=FALSE) {
@@ -177,7 +179,7 @@ class SMTP extends Magic {
 	*	Transmit message
 	*	@return bool
 	*	@param $message string
-	*	@param $log bool
+	*	@param $log bool|string
 	*	@param $mock bool
 	**/
 	function send($message,$log=TRUE,$mock=FALSE) {
@@ -192,7 +194,9 @@ class SMTP extends Magic {
 		// Connect to the server
 		if (!$mock) {
 			$socket=&$this->socket;
-			$socket=@fsockopen($this->host,$this->port,$errno,$errstr);
+			$socket=@stream_socket_client($this->host.':'.$this->port,
+				$errno,$errstr,ini_get('default_socket_timeout'),
+				STREAM_CLIENT_CONNECT,$this->context);
 			if (!$socket) {
 				$fw->error(500,$errstr);
 				return FALSE;
@@ -202,13 +206,13 @@ class SMTP extends Magic {
 		// Get server's initial response
 		$this->dialog(NULL,TRUE,$mock);
 		// Announce presence
-		$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock);
+		$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
 		if (strtolower($this->scheme)=='tls') {
 			$this->dialog('STARTTLS',$log,$mock);
 			if (!$mock)
 				stream_socket_enable_crypto(
 					$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
-			$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log,$mock);
+			$reply=$this->dialog('EHLO '.$fw->HOST,$log,$mock);
 		}
 		if (preg_match('/8BITMIME/',$reply))
 			$headers['Content-Transfer-Encoding']='8bit';
@@ -221,16 +225,16 @@ class SMTP extends Magic {
 			// Authenticate
 			$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)) {
+			$reply=$this->dialog(base64_encode($this->pw),$log,$mock);
+			if (!preg_match('/^235\s.*/',$reply)) {
 				$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['Message-Id']))
+			$headers['Message-Id']='<'.uniqid('',TRUE).'@'.$this->host.'>';
 		if (empty($headers['Date']))
 			$headers['Date']=date('r');
 		// Required headers
@@ -297,7 +301,7 @@ class SMTP extends Magic {
 				$out.='Content-Type: application/octet-stream'.$eol;
 				$out.='Content-Transfer-Encoding: base64'.$eol;
 				if ($attachment['cid'])
-					$out.='Content-ID: '.$attachment['cid'].$eol;
+					$out.='Content-Id: '.$attachment['cid'].$eol;
 				$out.='Content-Disposition: attachment; '.
 					'filename="'.$alias.'"'.$eol;
 				$out.=$eol;
@@ -307,7 +311,7 @@ class SMTP extends Magic {
 			$out.=$eol;
 			$out.='--'.$hash.'--'.$eol;
 			$out.='.';
-			$this->dialog($out,TRUE,$mock);
+			$this->dialog($out,preg_match('/verbose/i',$log),$mock);
 		}
 		else {
 			// Send mail headers
@@ -319,7 +323,7 @@ class SMTP extends Magic {
 			$out.=$message.$eol;
 			$out.='.';
 			// Send message
-			$this->dialog($out,TRUE,$mock);
+			$this->dialog($out,preg_match('/verbose/i',$log),$mock);
 		}
 		$this->dialog('QUIT',$log,$mock);
 		if (!$mock && $socket)
@@ -334,20 +338,21 @@ class SMTP extends Magic {
 	*	@param $scheme string
 	*	@param $user string
 	*	@param $pw string
+	*	@param $ctx resource
 	**/
 	function __construct(
-		$host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL) {
+		$host='localhost',$port=25,$scheme=NULL,$user=NULL,$pw=NULL,$ctx=NULL) {
 		$this->headers=[
 			'MIME-Version'=>'1.0',
 			'Content-Type'=>'text/plain; '.
-				'charset='.Base::instance()->get('ENCODING')
+				'charset='.Base::instance()->ENCODING
 		];
-		$this->host=$host;
-		if (strtolower($this->scheme=strtolower($scheme))=='ssl')
-			$this->host='ssl://'.$host;
+		$this->host=strtolower((($this->scheme=strtolower($scheme))=='ssl'?
+			'ssl':'tcp').'://'.$host);
 		$this->port=$port;
 		$this->user=$user;
 		$this->pw=$pw;
+		$this->context=stream_context_create($ctx);
 	}
 
 }

+ 2 - 2
lib/template.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -77,7 +77,7 @@ class Template extends Preview {
 					(preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])?
 						$this->token($attrib['href']):
 						Base::instance()->stringify($attrib['href'])).','.
-					'$this->mime,'.$hive.','.$ttl.'); ?>');
+					'NULL,'.$hive.','.$ttl.'); ?>');
 	}
 
 	/**

+ 1 - 1
lib/test.php

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

+ 2 - 2
lib/utf.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -191,7 +191,7 @@ class UTF extends Prefab {
 			':,'=>'\u1f60f', // think
 			':/'=>'\u1f623', // skeptic
 			'8O'=>'\u1f632', // oops
-		]+Base::instance()->get('EMOJI');
+		]+Base::instance()->EMOJI;
 		return $this->translate(str_replace(array_keys($map),
 			array_values($map),$str));
 	}

+ 105 - 86
lib/web.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -138,28 +138,35 @@ class Web extends Prefab {
 					'filename="'.($name!==NULL?$name:basename($file)).'"');
 			header('Accept-Ranges: bytes');
 			header('Content-Length: '.$size);
-			header('X-Powered-By: '.Base::instance()->get('PACKAGE'));
+			header('X-Powered-By: '.Base::instance()->PACKAGE);
 		}
-		$ctr=0;
-		$handle=fopen($file,'rb');
-		$start=microtime(TRUE);
-		while (!feof($handle) &&
-			($info=stream_get_meta_data($handle)) &&
-			!$info['timed_out'] && !connection_aborted()) {
-			if ($kbps) {
-				// Throttle output
-				$ctr++;
-				if ($ctr/$kbps>$elapsed=microtime(TRUE)-$start)
-					usleep(1e6*($ctr/$kbps-$elapsed));
-			}
-			// Send 1KiB and reset timer
-			echo fread($handle,1024);
-			if ($flush) {
-				ob_flush();
-				flush();
+		if (!$kbps && $flush) {
+			while (ob_get_level())
+				ob_end_clean();
+			readfile($file);
+		}
+		else {
+			$ctr=0;
+			$handle=fopen($file,'rb');
+			$start=microtime(TRUE);
+			while (!feof($handle) &&
+				($info=stream_get_meta_data($handle)) &&
+				!$info['timed_out'] && !connection_aborted()) {
+				if ($kbps) {
+					// Throttle output
+					$ctr++;
+					if ($ctr/$kbps>$elapsed=microtime(TRUE)-$start)
+						usleep(1e6*($ctr/$kbps-$elapsed));
+				}
+				// Send 1KiB and reset timer
+				echo fread($handle,1024);
+				if ($flush) {
+					ob_flush();
+					flush();
+				}
 			}
+			fclose($handle);
 		}
-		fclose($handle);
 		return $size;
 	}
 
@@ -172,13 +179,13 @@ class Web extends Prefab {
 	**/
 	function receive($func=NULL,$overwrite=FALSE,$slug=TRUE) {
 		$fw=Base::instance();
-		$dir=$fw->get('UPLOADS');
+		$dir=$fw->UPLOADS;
 		if (!is_dir($dir))
 			mkdir($dir,Base::MODE,TRUE);
-		if ($fw->get('VERB')=='PUT') {
-			$tmp=$fw->get('TEMP').$fw->get('SEED').'.'.$fw->hash(uniqid());
-			if (!$fw->get('RAW'))
-				$fw->write($tmp,$fw->get('BODY'));
+		if ($fw->VERB=='PUT') {
+			$tmp=$fw->TEMP.$fw->SEED.'.'.$fw->hash(uniqid());
+			if (!$fw->RAW)
+				$fw->write($tmp,$fw->BODY);
 			else {
 				$src=@fopen('php://input','r');
 				$dst=@fopen($tmp,'w');
@@ -191,7 +198,7 @@ class Web extends Prefab {
 				fclose($dst);
 				fclose($src);
 			}
-			$base=basename($fw->get('URI'));
+			$base=basename($fw->URI);
 			$file=[
 				'name'=>$dir.
 					($slug && preg_match('/(.+?)(\.\w+)?$/',$base,$parts)?
@@ -482,9 +489,9 @@ class Web extends Prefab {
 		$parts=parse_url($url);
 		if (empty($parts['scheme'])) {
 			// Local URL
-			$url=$fw->get('SCHEME').'://'.
-				$fw->get('HOST').
-				($url[0]!='/'?($fw->get('BASE').'/'):'').$url;
+			$url=$fw->SCHEME.'://'.$fw->HOST.
+				(in_array($fw->PORT,[80,443])?'':(':'.$fw->PORT)).
+				($url[0]!='/'?($fw->BASE.'/'):'').$url;
 			$parts=parse_url($url);
 		}
 		elseif (!preg_match('/https?/',$parts['scheme']))
@@ -537,7 +544,7 @@ class Web extends Prefab {
 			'ignore_errors'=>FALSE
 		];
 		$eol="\r\n";
-		if ($fw->get('CACHE') &&
+		if ($fw->CACHE &&
 			preg_match('/GET|HEAD/',$options['method'])) {
 			$cache=Cache::instance();
 			if ($cache->exists(
@@ -584,13 +591,13 @@ class Web extends Prefab {
 		$cache=Cache::instance();
 		$dst='';
 		if (!isset($path))
-			$path=$fw->get('UI').';./';
+			$path=$fw->UI.';./';
 		foreach ($fw->split($path,FALSE) as $dir)
 			foreach ($files as $file)
 				if (is_file($save=$fw->fixslashes($dir.$file)) &&
 					is_bool(strpos($save,'../')) &&
 					preg_match('/\.(css|js)$/i',$file)) {
-					if ($fw->get('CACHE') &&
+					if ($fw->CACHE &&
 						($cached=$cache->exists(
 							$hash=$fw->hash($save).'.'.$ext[0],$data)) &&
 						$cached[0]>filemtime($save))
@@ -685,7 +692,9 @@ class Web extends Prefab {
 									preg_match('/[\w'.($ext[0]=='css'?
 										'#\.%+\-*()\[\]':'\$').']{2}|'.
 										'[+\-]{2}/',
-										substr($data,-1).$src[$ptr+1]))
+										substr($data,-1).$src[$ptr+1]) ||
+									($ext[0]=='css' && $ptr+2<strlen($src) &&
+									preg_match('/:\w/',substr($src,$ptr+1,2))))
 									$data.=' ';
 								$ptr++;
 								continue;
@@ -693,13 +702,15 @@ class Web extends Prefab {
 							$data.=$src[$ptr];
 							$ptr++;
 						}
-						if ($fw->get('CACHE'))
+						if ($ext[0]=='css')
+							$data=str_replace(';}','}',$data);
+						if ($fw->CACHE)
 							$cache->set($hash,$data);
 						$dst.=$data;
 					}
 				}
 		if (PHP_SAPI!='cli' && $header)
-			header('Content-Type: '.$mime.'; charset='.$fw->get('ENCODING'));
+			header('Content-Type: '.$mime.'; charset='.$fw->ENCODING);
 		return $dst;
 	}
 
@@ -767,6 +778,62 @@ class Web extends Prefab {
 	}
 
 	/**
+	*	Return preset diacritics translation table
+	*	@return array
+	**/
+	function diacritics() {
+		return [
+			'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A',
+			'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A',
+			'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C',
+			'Č'=>'C','Ċ'=>'C','Ц'=>'C','Ч'=>'Ch','Ð'=>'Dj','Đ'=>'Dj',
+			'Ď'=>'Dj','Д'=>'Dj','É'=>'E','Ę'=>'E','Ё'=>'E','Ė'=>'E',
+			'Ê'=>'E','Ě'=>'E','Ē'=>'E','È'=>'E','Е'=>'E','Э'=>'E',
+			'Ë'=>'E','Ĕ'=>'E','Ф'=>'F','Г'=>'G','Ģ'=>'G','Ġ'=>'G',
+			'Ĝ'=>'G','Ğ'=>'G','Х'=>'H','Ĥ'=>'H','Ħ'=>'H','Ï'=>'I',
+			'Ĭ'=>'I','İ'=>'I','Į'=>'I','Ī'=>'I','Í'=>'I','Ì'=>'I',
+			'И'=>'I','Ǐ'=>'I','Ĩ'=>'I','Î'=>'I','IJ'=>'IJ','Ĵ'=>'J',
+			'Й'=>'J','Я'=>'Ja','Ю'=>'Ju','К'=>'K','Ķ'=>'K','Ĺ'=>'L',
+			'Л'=>'L','Ł'=>'L','Ŀ'=>'L','Ļ'=>'L','Ľ'=>'L','М'=>'M',
+			'Н'=>'N','Ń'=>'N','Ñ'=>'N','Ņ'=>'N','Ň'=>'N','Ō'=>'O',
+			'О'=>'O','Ǿ'=>'O','Ǒ'=>'O','Ơ'=>'O','Ŏ'=>'O','Ő'=>'O',
+			'Ø'=>'O','Ö'=>'Oe','Õ'=>'O','Ó'=>'O','Ò'=>'O','Ô'=>'O',
+			'Œ'=>'OE','П'=>'P','Ŗ'=>'R','Р'=>'R','Ř'=>'R','Ŕ'=>'R',
+			'Ŝ'=>'S','Ş'=>'S','Š'=>'S','Ș'=>'S','Ś'=>'S','С'=>'S',
+			'Ш'=>'Sh','Щ'=>'Shch','Ť'=>'T','Ŧ'=>'T','Ţ'=>'T','Ț'=>'T',
+			'Т'=>'T','Ů'=>'U','Ű'=>'U','Ŭ'=>'U','Ũ'=>'U','Ų'=>'U',
+			'Ū'=>'U','Ǜ'=>'U','Ǚ'=>'U','Ù'=>'U','Ú'=>'U','Ü'=>'Ue',
+			'Ǘ'=>'U','Ǖ'=>'U','У'=>'U','Ư'=>'U','Ǔ'=>'U','Û'=>'U',
+			'В'=>'V','Ŵ'=>'W','Ы'=>'Y','Ŷ'=>'Y','Ý'=>'Y','Ÿ'=>'Y',
+			'Ź'=>'Z','З'=>'Z','Ż'=>'Z','Ž'=>'Z','Ж'=>'Zh','á'=>'a',
+			'ă'=>'a','â'=>'a','à'=>'a','ā'=>'a','ǻ'=>'a','å'=>'a',
+			'ä'=>'ae','ą'=>'a','ǎ'=>'a','ã'=>'a','а'=>'a','ª'=>'a',
+			'æ'=>'ae','ǽ'=>'ae','б'=>'b','č'=>'c','ç'=>'c','ц'=>'c',
+			'ċ'=>'c','ĉ'=>'c','ć'=>'c','ч'=>'ch','ð'=>'dj','ď'=>'dj',
+			'д'=>'dj','đ'=>'dj','э'=>'e','é'=>'e','ё'=>'e','ë'=>'e',
+			'ê'=>'e','е'=>'e','ĕ'=>'e','è'=>'e','ę'=>'e','ě'=>'e',
+			'ė'=>'e','ē'=>'e','ƒ'=>'f','ф'=>'f','ġ'=>'g','ĝ'=>'g',
+			'ğ'=>'g','г'=>'g','ģ'=>'g','х'=>'h','ĥ'=>'h','ħ'=>'h',
+			'ǐ'=>'i','ĭ'=>'i','и'=>'i','ī'=>'i','ĩ'=>'i','į'=>'i',
+			'ı'=>'i','ì'=>'i','î'=>'i','í'=>'i','ï'=>'i','ij'=>'ij',
+			'ĵ'=>'j','й'=>'j','я'=>'ja','ю'=>'ju','ķ'=>'k','к'=>'k',
+			'ľ'=>'l','ł'=>'l','ŀ'=>'l','ĺ'=>'l','ļ'=>'l','л'=>'l',
+			'м'=>'m','ņ'=>'n','ñ'=>'n','ń'=>'n','н'=>'n','ň'=>'n',
+			'ʼn'=>'n','ó'=>'o','ò'=>'o','ǒ'=>'o','ő'=>'o','о'=>'o',
+			'ō'=>'o','º'=>'o','ơ'=>'o','ŏ'=>'o','ô'=>'o','ö'=>'oe',
+			'õ'=>'o','ø'=>'o','ǿ'=>'o','œ'=>'oe','п'=>'p','р'=>'r',
+			'ř'=>'r','ŕ'=>'r','ŗ'=>'r','ſ'=>'s','ŝ'=>'s','ș'=>'s',
+			'š'=>'s','ś'=>'s','с'=>'s','ş'=>'s','ш'=>'sh','щ'=>'shch',
+			'ß'=>'ss','ţ'=>'t','т'=>'t','ŧ'=>'t','ť'=>'t','ț'=>'t',
+			'у'=>'u','ǘ'=>'u','ŭ'=>'u','û'=>'u','ú'=>'u','ų'=>'u',
+			'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u',
+			'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v',
+			'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z',
+			'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>''
+		];
+	}
+
+	/**
 	*	Return a URL/filesystem-friendly version of string
 	*	@return string
 	*	@param $text string
@@ -774,55 +841,7 @@ class Web extends Prefab {
 	function slug($text) {
 		return trim(strtolower(preg_replace('/([^\pL\pN])+/u','-',
 			trim(strtr(str_replace('\'','',$text),
-			[
-				'Ǎ'=>'A','А'=>'A','Ā'=>'A','Ă'=>'A','Ą'=>'A','Å'=>'A',
-				'Ǻ'=>'A','Ä'=>'Ae','Á'=>'A','À'=>'A','Ã'=>'A','Â'=>'A',
-				'Æ'=>'AE','Ǽ'=>'AE','Б'=>'B','Ç'=>'C','Ć'=>'C','Ĉ'=>'C',
-				'Č'=>'C','Ċ'=>'C','Ц'=>'C','Ч'=>'Ch','Ð'=>'Dj','Đ'=>'Dj',
-				'Ď'=>'Dj','Д'=>'Dj','É'=>'E','Ę'=>'E','Ё'=>'E','Ė'=>'E',
-				'Ê'=>'E','Ě'=>'E','Ē'=>'E','È'=>'E','Е'=>'E','Э'=>'E',
-				'Ë'=>'E','Ĕ'=>'E','Ф'=>'F','Г'=>'G','Ģ'=>'G','Ġ'=>'G',
-				'Ĝ'=>'G','Ğ'=>'G','Х'=>'H','Ĥ'=>'H','Ħ'=>'H','Ï'=>'I',
-				'Ĭ'=>'I','İ'=>'I','Į'=>'I','Ī'=>'I','Í'=>'I','Ì'=>'I',
-				'И'=>'I','Ǐ'=>'I','Ĩ'=>'I','Î'=>'I','IJ'=>'IJ','Ĵ'=>'J',
-				'Й'=>'J','Я'=>'Ja','Ю'=>'Ju','К'=>'K','Ķ'=>'K','Ĺ'=>'L',
-				'Л'=>'L','Ł'=>'L','Ŀ'=>'L','Ļ'=>'L','Ľ'=>'L','М'=>'M',
-				'Н'=>'N','Ń'=>'N','Ñ'=>'N','Ņ'=>'N','Ň'=>'N','Ō'=>'O',
-				'О'=>'O','Ǿ'=>'O','Ǒ'=>'O','Ơ'=>'O','Ŏ'=>'O','Ő'=>'O',
-				'Ø'=>'O','Ö'=>'Oe','Õ'=>'O','Ó'=>'O','Ò'=>'O','Ô'=>'O',
-				'Œ'=>'OE','П'=>'P','Ŗ'=>'R','Р'=>'R','Ř'=>'R','Ŕ'=>'R',
-				'Ŝ'=>'S','Ş'=>'S','Š'=>'S','Ș'=>'S','Ś'=>'S','С'=>'S',
-				'Ш'=>'Sh','Щ'=>'Shch','Ť'=>'T','Ŧ'=>'T','Ţ'=>'T','Ț'=>'T',
-				'Т'=>'T','Ů'=>'U','Ű'=>'U','Ŭ'=>'U','Ũ'=>'U','Ų'=>'U',
-				'Ū'=>'U','Ǜ'=>'U','Ǚ'=>'U','Ù'=>'U','Ú'=>'U','Ü'=>'Ue',
-				'Ǘ'=>'U','Ǖ'=>'U','У'=>'U','Ư'=>'U','Ǔ'=>'U','Û'=>'U',
-				'В'=>'V','Ŵ'=>'W','Ы'=>'Y','Ŷ'=>'Y','Ý'=>'Y','Ÿ'=>'Y',
-				'Ź'=>'Z','З'=>'Z','Ż'=>'Z','Ž'=>'Z','Ж'=>'Zh','á'=>'a',
-				'ă'=>'a','â'=>'a','à'=>'a','ā'=>'a','ǻ'=>'a','å'=>'a',
-				'ä'=>'ae','ą'=>'a','ǎ'=>'a','ã'=>'a','а'=>'a','ª'=>'a',
-				'æ'=>'ae','ǽ'=>'ae','б'=>'b','č'=>'c','ç'=>'c','ц'=>'c',
-				'ċ'=>'c','ĉ'=>'c','ć'=>'c','ч'=>'ch','ð'=>'dj','ď'=>'dj',
-				'д'=>'dj','đ'=>'dj','э'=>'e','é'=>'e','ё'=>'e','ë'=>'e',
-				'ê'=>'e','е'=>'e','ĕ'=>'e','è'=>'e','ę'=>'e','ě'=>'e',
-				'ė'=>'e','ē'=>'e','ƒ'=>'f','ф'=>'f','ġ'=>'g','ĝ'=>'g',
-				'ğ'=>'g','г'=>'g','ģ'=>'g','х'=>'h','ĥ'=>'h','ħ'=>'h',
-				'ǐ'=>'i','ĭ'=>'i','и'=>'i','ī'=>'i','ĩ'=>'i','į'=>'i',
-				'ı'=>'i','ì'=>'i','î'=>'i','í'=>'i','ï'=>'i','ij'=>'ij',
-				'ĵ'=>'j','й'=>'j','я'=>'ja','ю'=>'ju','ķ'=>'k','к'=>'k',
-				'ľ'=>'l','ł'=>'l','ŀ'=>'l','ĺ'=>'l','ļ'=>'l','л'=>'l',
-				'м'=>'m','ņ'=>'n','ñ'=>'n','ń'=>'n','н'=>'n','ň'=>'n',
-				'ʼn'=>'n','ó'=>'o','ò'=>'o','ǒ'=>'o','ő'=>'o','о'=>'o',
-				'ō'=>'o','º'=>'o','ơ'=>'o','ŏ'=>'o','ô'=>'o','ö'=>'oe',
-				'õ'=>'o','ø'=>'o','ǿ'=>'o','œ'=>'oe','п'=>'p','р'=>'r',
-				'ř'=>'r','ŕ'=>'r','ŗ'=>'r','ſ'=>'s','ŝ'=>'s','ș'=>'s',
-				'š'=>'s','ś'=>'s','с'=>'s','ş'=>'s','ш'=>'sh','щ'=>'shch',
-				'ß'=>'ss','ţ'=>'t','т'=>'t','ŧ'=>'t','ť'=>'t','ț'=>'t',
-				'у'=>'u','ǘ'=>'u','ŭ'=>'u','û'=>'u','ú'=>'u','ų'=>'u',
-				'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u',
-				'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v',
-				'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z',
-				'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>''
-			]+Base::instance()->get('DIACRITICS'))))),'-');
+			$this->diacritics()+Base::instance()->DIACRITICS)))),'-');
 	}
 
 	/**
@@ -874,9 +893,9 @@ if (!function_exists('gzdecode')) {
 	**/
 	function gzdecode($str) {
 		$fw=Base::instance();
-		if (!is_dir($tmp=$fw->get('TEMP')))
+		if (!is_dir($tmp=$fw->TEMP))
 			mkdir($tmp,Base::MODE,TRUE);
-		file_put_contents($file=$tmp.'/'.$fw->get('SEED').'.'.
+		file_put_contents($file=$tmp.'/'.$fw->SEED.'.'.
 			$fw->hash(uniqid(NULL,TRUE)).'.gz',$str,LOCK_EX);
 		ob_start();
 		readgzfile($file);

+ 2 - 2
lib/web/geo.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -55,7 +55,7 @@ class Geo extends \Prefab {
 		$fw=\Base::instance();
 		$web=\Web::instance();
 		if (!$ip)
-			$ip=$fw->get('IP');
+			$ip=$fw->IP;
 		$public=filter_var($ip,FILTER_VALIDATE_IP,
 			FILTER_FLAG_IPV4|FILTER_FLAG_IPV6|
 			FILTER_FLAG_NO_RES_RANGE|FILTER_FLAG_NO_PRIV_RANGE);

+ 58 - 0
lib/web/google/recaptcha.php

@@ -0,0 +1,58 @@
+<?php
+
+/*
+
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
+
+	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
+
+	This is free software: you can redistribute it and/or modify it under the
+	terms of the GNU General Public License as published by the Free Software
+	Foundation, either version 3 of the License, or later.
+
+	Fat-Free Framework is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+	General Public License for more details.
+
+	You should have received a copy of the GNU General Public License along
+	with Fat-Free Framework.  If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+namespace Web\Google;
+
+//! Google ReCAPTCHA v2 plug-in
+class Recaptcha {
+
+	const
+		//! API URL
+		URL_Recaptcha='https://www.google.com/recaptcha/api/siteverify';
+
+	/**
+	 *	Verify reCAPTCHA response
+	 *	@param string $secret
+	 *	@param string $response
+	 *	@return bool
+	 **/
+	static function verify($secret,$response=NULL) {
+		$fw=\Base::instance();
+		if (!isset($response))
+			$response=$fw->{'POST.g-recaptcha-response'};
+		if (!$response)
+			return FALSE;
+		$web=\Web::instance();
+		$out=$web->request(self::URL_Recaptcha,[
+			'method'=>'POST',
+			'content'=>http_build_query([
+				'secret'=>$secret,
+				'response'=>$response,
+				'remoteip'=>$fw->IP
+			]),
+		]);
+		return isset($out['body']) &&
+			($json=json_decode($out['body'],TRUE)) &&
+			isset($json['success']) && $json['success'];
+	}
+
+}

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

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

+ 4 - 2
lib/web/oauth2.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -62,6 +62,8 @@ class OAuth2 extends \Magic {
 				)
 			);
 		$response=$web->request($uri,$options);
+		if ($response['error'])
+			user_error($response['error'],E_USER_ERROR);
 		return $response['body'] &&
 			preg_grep('/HTTP\/1\.\d 200/',$response['headers'])?
 			json_decode($response['body'],TRUE):
@@ -121,7 +123,7 @@ class OAuth2 extends \Magic {
 	/**
 	*	Remove scope/claim
 	*	@return NULL
-	*	@param $key
+	*	@param $key string
 	**/
 	function clear($key=NULL) {
 		if ($key)

+ 3 - 3
lib/web/openid.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -143,9 +143,9 @@ class OpenID extends \Magic {
 	**/
 	function auth($proxy=NULL,$attr=[],array $reqd=NULL) {
 		$fw=\Base::instance();
-		$root=$fw->get('SCHEME').'://'.$fw->get('HOST');
+		$root=$fw->SCHEME.'://'.$fw->HOST;
 		if (empty($this->args['trust_root']))
-			$this->args['trust_root']=$root.$fw->get('BASE').'/';
+			$this->args['trust_root']=$root.$fw->BASE.'/';
 		if (empty($this->args['return_to']))
 			$this->args['return_to']=$root.$_SERVER['REQUEST_URI'];
 		$this->args['mode']='checkid_setup';

+ 12 - 12
lib/web/pingback.php

@@ -2,7 +2,7 @@
 
 /*
 
-	Copyright (c) 2009-2016 F3::Factory/Bong Cosca, All rights reserved.
+	Copyright (c) 2009-2017 F3::Factory/Bong Cosca, All rights reserved.
 
 	This file is part of the Fat-Free Framework (http://fatfreeframework.com).
 
@@ -66,9 +66,9 @@ class Pingback extends \Prefab {
 		$web=\Web::instance();
 		$parts=parse_url($source);
 		if (empty($parts['scheme']) || empty($parts['host']) ||
-			$parts['host']==$fw->get('HOST')) {
+			$parts['host']==$fw->HOST) {
 			$req=$web->request($source);
-			$doc=new \DOMDocument('1.0',$fw->get('ENCODING'));
+			$doc=new \DOMDocument('1.0',$fw->ENCODING);
 			$doc->stricterrorchecking=FALSE;
 			$doc->recover=TRUE;
 			if (@$doc->loadhtml($req['body'])) {
@@ -85,7 +85,7 @@ class Pingback extends \Prefab {
 								'content'=>xmlrpc_encode_request(
 									'pingback.ping',
 									[$source,$permalink],
-									['encoding'=>$fw->get('ENCODING')]
+									['encoding'=>$fw->ENCODING]
 								)
 							]
 						);
@@ -110,29 +110,29 @@ class Pingback extends \Prefab {
 	function listen($func,$path=NULL) {
 		$fw=\Base::instance();
 		if (PHP_SAPI!='cli') {
-			header('X-Powered-By: '.$fw->get('PACKAGE'));
+			header('X-Powered-By: '.$fw->PACKAGE);
 			header('Content-Type: application/xml; '.
-				'charset='.$charset=$fw->get('ENCODING'));
+				'charset='.$charset=$fw->ENCODING);
 		}
 		if (!$path)
-			$path=$fw->get('BASE');
+			$path=$fw->BASE;
 		$web=\Web::instance();
-		$args=xmlrpc_decode_request($fw->get('BODY'),$method,$charset);
+		$args=xmlrpc_decode_request($fw->BODY,$method,$charset);
 		$options=['encoding'=>$charset];
 		if ($method=='pingback.ping' && isset($args[0],$args[1])) {
 			list($source,$permalink)=$args;
-			$doc=new \DOMDocument('1.0',$fw->get('ENCODING'));
+			$doc=new \DOMDocument('1.0',$fw->ENCODING);
 			// Check local page if pingback-enabled
 			$parts=parse_url($permalink);
 			if ((empty($parts['scheme']) ||
-				$parts['host']==$fw->get('HOST')) &&
+				$parts['host']==$fw->HOST) &&
 				preg_match('/^'.preg_quote($path,'/').'/'.
-					($fw->get('CASELESS')?'i':''),$parts['path']) &&
+					($fw->CASELESS?'i':''),$parts['path']) &&
 				$this->enabled($permalink)) {
 				// Check source
 				$parts=parse_url($source);
 				if ((empty($parts['scheme']) ||
-					$parts['host']==$fw->get('HOST')) &&
+					$parts['host']==$fw->HOST) &&
 					($req=$web->request($source)) &&
 					$doc->loadhtml($req['body'])) {
 					$links=$doc->getelementsbytagname('a');