- PHP Framework upgrade 3.5.0 -> 3.5.1 (fixes some issues with CREST cURL caching, and SESSION management)

- #138 added "cookie logout" to "logout" menu entry
This commit is contained in:
Exodus4D
2016-05-06 18:09:28 +02:00
parent eb1e365020
commit dfd1e8501d
28 changed files with 575 additions and 388 deletions

View File

@@ -1,5 +1,52 @@
CHANGELOG
3.5.1 (31 December 2015)
* NEW: ttl attribute in <include> template tag
* NEW: allow anonymous function for template filter
* NEW: format modifier for international and custom currency symbol
* NEW: Image->data() returns image resource
* NEW: extract() get prefixed array keys from an assoc array
* NEW: Optimized and faster Template parser with full support for HTML5 empty tags
* NEW: Added support for {@token} encapsulation syntax in routes definition
* NEW: DB\SQL->exec(), automatically shift to 1-based query arguments
* NEW: abort() flush output
* Added referenced value to devoid()
* Template token filters are now resolved within Preview->token()
* Web->_curl: restrict redirections to HTTP
* Web->minify(), skip importing of external files
* Improved session and error handling in until()
* Get the error trace array with the new $format parameter
* Better support for unicode URLs
* Optimized TZ detection with date_default_timezone_get()
* format() Provide default decimal places
* Optimize code: remove redundant TTL checks
* Optimized timeout handling in Web->request()
* Improved PHPDoc hints
* Added missing russian DIACRITICS letters
* DB\Cursor: allow child implementation of reset()
* DB\Cursor: Copyfrom now does an internal call to set()
* DB\SQL: Provide the ability to disable SQL logging
* DB\SQL: improved query analysis to trigger fetchAll
* DB\SQL\Mapper: added support for binary table columns
* SQL,JIG,MONGO,CACHE Session handlers refactored and optimized
* SMTP Refactoring and optimization
* Bug fix: SMTP, Align quoted_printable_encode() with SMTP specs (dot-stuffing)
* Bug fix: SMTP, Send buffered optional headers to output
* Bug fix: SMTP, Content-Transfer-Encoding for non-TLS connections
* Bug fix: SMTP, Single attachment error
* Bug fix: Cursor->load not always mapping to first record
* Bug fix: dry SQL mapper should not trigger 'load'
* Bug fix: Code highlighting on empty text
* Bug fix: Image->resize, round dimensions instead of cast
* Bug fix: whitespace handling in $f3->compile()
* Bug fix: TTL of `View` and `Preview` (`Template`)
* Bug fix: token filter regex
* Bug fix: Template, empty attributes
* Bug fix: Preview->build() greedy regex
* Bug fix: Web->minify() single-line comment on last line
* Bug fix: Web->request(), follow_location with cURL and open_basedir
* Bug fix: Web->send() Single quotes around filename not interpreted correctly by some browsers
3.5.0 (2 June 2015)
* NEW: until() method for long polling
* NEW: abort() to disconnect HTTP client (and continue execution)

View File

@@ -45,7 +45,7 @@ final class Base extends Prefab implements ArrayAccess {
//@{ Framework details
const
PACKAGE='Fat-Free Framework',
VERSION='3.5.0-Release';
VERSION='3.5.1-Release';
//@}
//@{ HTTP status codes (RFC 2616)
@@ -179,8 +179,8 @@ final class Base extends Prefab implements ArrayAccess {
}
/**
* assemble url from alias name
* @return NULL
* Assemble url from alias name
* @return string
* @param $name string
* @param $params array|string
**/
@@ -215,7 +215,7 @@ final class Base extends Prefab implements ArrayAccess {
function compile($str) {
$fw=$this;
return preg_replace_callback(
'/(?<!\w)@(\w(?:[\w\.\[\]\(]|\->|::)*)/',
'/(?<!\w)@(\w(?:[\h\w\.\[\]\(]|\->|::)*)/',
function($var) use($fw) {
return '$'.preg_replace_callback(
'/\.(\w+)\(|\.(\w+)|\[((?:[^\[\]]*|(?R))*)\]/',
@@ -226,7 +226,7 @@ final class Base extends Prefab implements ArrayAccess {
('['.var_export($expr[1],TRUE).']')).'('):
('['.var_export(
isset($expr[3])?
$fw->compile($expr[3]):
trim($fw->compile($expr[3])):
(ctype_digit($expr[2])?
(int)$expr[2]:
$expr[2]),TRUE).']');
@@ -305,10 +305,11 @@ final class Base extends Prefab implements ArrayAccess {
/**
* Return TRUE if hive key is empty and not cached
* @return bool
* @param $key string
* @param $val mixed
* @return bool
**/
function devoid($key) {
function devoid($key,&$val=NULL) {
$val=$this->ref($key,FALSE);
return empty($val) &&
(!Cache::instance()->exists($this->hash($key).'.var',$val) ||
@@ -422,8 +423,7 @@ final class Base extends Prefab implements ArrayAccess {
// End session
session_unset();
session_destroy();
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
$this->clear('COOKIE.'.session_name());
}
$this->sync('SESSION');
}
@@ -679,6 +679,19 @@ final class Base extends Prefab implements ArrayAccess {
return $num?($num/abs($num)):0;
}
/**
* Extract values of an associative array whose keys start with the given prefix
* @return array
* @param $arr array
* @param $prefix string
**/
function extract($arr,$prefix) {
$out=array();
foreach (preg_grep('/^'.preg_quote($prefix,'/').'/',array_keys($arr)) as $key)
$out[substr($key,strlen($prefix))]=$arr[$key];
return $out;
}
/**
* Convert class constants to array
* @return array
@@ -687,14 +700,7 @@ final class Base extends Prefab implements ArrayAccess {
**/
function constants($class,$prefix='') {
$ref=new ReflectionClass($class);
$out=array();
foreach (preg_grep('/^'.$prefix.'/',array_keys($ref->getconstants()))
as $val) {
$out[$key=substr($val,strlen($prefix))]=
constant((is_object($class)?get_class($class):$class).'::'.$prefix.$key);
}
unset($ref);
return $out;
return $this->extract($ref->getconstants(),$prefix);
}
/**
@@ -843,9 +849,12 @@ final class Base extends Prefab implements ArrayAccess {
return number_format(
$args[$pos],0,'',$thousands_sep);
case 'currency':
if (function_exists('money_format'))
$int=$cstm=false;
if (isset($prop) && $cstm=!$int=($prop=='int'))
$currency_symbol=$prop;
if (!$cstm && function_exists('money_format'))
return money_format(
'%n',$args[$pos]);
'%'.($int?'i':'n'),$args[$pos]);
$fmt=array(
0=>'(nc)',1=>'(n c)',
2=>'(nc)',10=>'+nc',
@@ -878,7 +887,8 @@ final class Base extends Prefab implements ArrayAccess {
$frac_digits,
$decimal_point,
$thousands_sep),
$currency_symbol),
$int?$int_curr_symbol
:$currency_symbol),
$fmt[(int)(
(${$pre.'_cs_precedes'}%2).
(${$pre.'_sign_posn'}%5).
@@ -891,8 +901,8 @@ final class Base extends Prefab implements ArrayAccess {
$thousands_sep).'%';
case 'decimal':
return number_format(
$args[$pos],$prop,$decimal_point,
$thousands_sep);
$args[$pos],isset($prop)?$prop:2,
$decimal_point,$thousands_sep);
}
break;
case 'date':
@@ -1022,7 +1032,7 @@ final class Base extends Prefab implements ArrayAccess {
**/
function status($code) {
$reason=@constant('self::HTTP_'.$code);
if (PHP_SAPI!='cli')
if (PHP_SAPI!='cli' && !headers_sent())
header($_SERVER['SERVER_PROTOCOL'].' '.$code.' '.$reason);
return $reason;
}
@@ -1089,11 +1099,12 @@ final class Base extends Prefab implements ArrayAccess {
}
/**
* Return formatted stack trace
* @return string
* Return filtered, formatted stack trace
* @return string|array
* @param $trace array|NULL
* @param $format bool
**/
function trace(array $trace=NULL) {
function trace(array $trace=NULL, $format=TRUE) {
if (!$trace) {
$trace=debug_backtrace(FALSE);
$frame=$trace[0];
@@ -1111,6 +1122,8 @@ final class Base extends Prefab implements ArrayAccess {
'__call|call_user_func)/',$frame['function']));
}
);
if (!$format)
return $trace;
$out='';
$eol="\n";
// Analyze stack trace
@@ -1369,7 +1382,7 @@ final class Base extends Prefab implements ArrayAccess {
$url=$this->rel($this->hive['URI']);
$case=$this->hive['CASELESS']?'i':'';
preg_match('/^'.
preg_replace('/@(\w+\b)/','(?P<\1>[^\/\?]+)',
preg_replace('/((\\\{)?@(\w+\b)(?(2)\\\}))/','(?P<\3>[^\/\?]+)',
str_replace('\*','([^\?]+)',preg_quote($pattern,'/'))).
'\/?(?:\?.*)?$/'.$case.'um',$url,$args);
return $args;
@@ -1394,7 +1407,7 @@ final class Base extends Prefab implements ArrayAccess {
array_multisort($paths,SORT_DESC,$keys,$vals);
$this->hive['ROUTES']=array_combine($keys,$vals);
// Convert to BASE-relative URL
$req=$this->rel($this->hive['URI']);
$req=$this->rel(urldecode($this->hive['URI']));
if ($cors=(isset($this->hive['HEADERS']['Origin']) &&
$this->hive['CORS']['origin'])) {
$cors=$this->hive['CORS'];
@@ -1428,7 +1441,7 @@ final class Base extends Prefab implements ArrayAccess {
if (is_numeric($key) && $key)
unset($args[$key]);
// Capture values of route pattern tokens
$this->hive['PARAMS']=$args=array_map('urldecode',$args);
$this->hive['PARAMS']=$args;
// Save matching route
$this->hive['ALIAS']=$alias;
$this->hive['PATTERN']=$pattern;
@@ -1437,9 +1450,10 @@ final class Base extends Prefab implements ArrayAccess {
implode(',',$cors['expose']):$cors['expose']));
if (is_string($handler)) {
// Replace route pattern tokens in handler if any
$handler=preg_replace_callback('/@(\w+\b)/',
$handler=preg_replace_callback('/({)?@(\w+\b)(?(1)})/',
function($id) use($args) {
return isset($args[$id[1]])?$args[$id[1]]:$id[0];
$pid=count($id)>2?2:1;
return isset($args[$id[$pid]])?$args[$id[$pid]]:$id[0];
},
$handler
);
@@ -1458,7 +1472,7 @@ final class Base extends Prefab implements ArrayAccess {
$cached=$cache->exists(
$hash=$this->hash($this->hive['VERB'].' '.
$this->hive['URI']).'.url',$data);
if ($cached && $cached[0]+$ttl>$now) {
if ($cached) {
if (isset($headers['If-Modified-Since']) &&
strtotime($headers['If-Modified-Since'])+
$ttl>$now) {
@@ -1520,10 +1534,13 @@ final class Base extends Prefab implements ArrayAccess {
// Unhandled HTTP method
header('Allow: '.implode(',',array_unique($allowed)));
if ($cors) {
header('Access-Control-Allow-Methods: OPTIONS,'.implode(',',$allowed));
header('Access-Control-Allow-Methods: OPTIONS,'.
implode(',',$allowed));
if ($cors['headers'])
header('Access-Control-Allow-Headers: '.(is_array($cors['headers'])?
implode(',',$cors['headers']):$cors['headers']));
header('Access-Control-Allow-Headers: '.
(is_array($cors['headers'])?
implode(',',$cors['headers']):
$cors['headers']));
if ($cors['ttl']>0)
header('Access-Control-Max-Age: '.$cors['ttl']);
}
@@ -1546,28 +1563,26 @@ final class Base extends Prefab implements ArrayAccess {
$time=time();
$limit=max(0,min($timeout,$max=ini_get('max_execution_time')-1));
$out='';
$flag=FALSE;
// Turn output buffering on
ob_start();
// Not for the weak of heart
while (
// No error occurred
!$this->hive['ERROR'] &&
// Still alive?
!connection_aborted() &&
// Got time left?
(time()-$time+1<$limit) &&
// Restart session
$flag=@session_start() &&
@session_start() &&
// CAUTION: Callback will kill host if it never becomes truthy!
!($out=$this->call($func,$args))) {
session_commit();
ob_flush();
flush();
// Hush down
sleep(1);
}
if ($flag) {
session_commit();
ob_flush();
flush();
}
ob_flush();
flush();
return $out;
}
@@ -1577,9 +1592,11 @@ final class Base extends Prefab implements ArrayAccess {
function abort() {
@session_start();
session_commit();
header('Content-Length: 0');
$out='';
while (ob_get_level())
ob_end_clean();
$out=ob_get_clean().$out;
header('Content-Length: '.strlen($out));
echo $out;
flush();
if (function_exists('fastcgi_finish_request'))
fastcgi_finish_request();
@@ -1826,7 +1843,7 @@ final class Base extends Prefab implements ArrayAccess {
$out='';
$pre=FALSE;
$text=trim($text);
if (!preg_match('/^<\?php/',$text)) {
if ($text && !preg_match('/^<\?php/',$text)) {
$text='<?php '.$text;
$pre=TRUE;
}
@@ -2083,9 +2100,9 @@ final class Base extends Prefab implements ArrayAccess {
'CONFIG'=>NULL,
'CORS'=>array(
'headers'=>'',
'origin'=>false,
'credentials'=>false,
'expose'=>false,
'origin'=>FALSE,
'credentials'=>FALSE,
'expose'=>FALSE,
'ttl'=>0),
'DEBUG'=>0,
'DIACRITICS'=>array(),
@@ -2131,7 +2148,7 @@ final class Base extends Prefab implements ArrayAccess {
'SERIALIZER'=>extension_loaded($ext='igbinary')?$ext:'php',
'TEMP'=>'tmp/',
'TIME'=>microtime(TRUE),
'TZ'=>(@ini_get('date.timezone'))?:'UTC',
'TZ'=>@date_default_timezone_get(),
'UI'=>'./',
'UNLOAD'=>NULL,
'UPLOADS'=>'./',
@@ -2500,8 +2517,7 @@ class View extends Prefab {
function render($file,$mime='text/html',array $hive=NULL,$ttl=0) {
$fw=Base::instance();
$cache=Cache::instance();
$cached=$cache->exists($hash=$fw->hash($file),$data);
if ($cached && $cached[0]+$ttl>microtime(TRUE))
if ($cache->exists($hash=$fw->hash($file),$data))
return $data;
foreach ($fw->split($fw->get('UI').';./') as $dir)
if (is_file($this->view=$fw->fixslashes($dir.$file))) {
@@ -2516,7 +2532,7 @@ class View extends Prefab {
foreach($this->trigger['afterrender'] as $func)
$data=$fw->call($func,$data);
if ($ttl)
$cache->set($hash,$data);
$cache->set($hash,$data,$ttl);
return $data;
}
user_error(sprintf(Base::E_Open,$file),E_USER_ERROR);
@@ -2539,7 +2555,7 @@ class Preview extends View {
//! MIME type
$mime,
//! token filter
$filter = array(
$filter=array(
'esc'=>'$this->esc',
'raw'=>'$this->raw',
'alias'=>'\Base::instance()->alias',
@@ -2552,15 +2568,24 @@ class Preview extends View {
* @param $str string
**/
function token($str) {
return trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
$str=trim(preg_replace('/\{\{(.+?)\}\}/s',trim('\1'),
Base::instance()->compile($str)));
if (preg_match('/^(.+)(?<!\|)\|((?:\h*\w+(?:\h*[,;]?))+)$/s',
$str,$parts)) {
$str=trim($parts[1]);
foreach (Base::instance()->split($parts[2]) as $func)
$str=is_string($cmd=$this->filter($func))?$cmd.'('.$str.')':
'\Base::instance()->call('.
'$this->filter(\''.$func.'\'),array('.$str.'))';
}
return $str;
}
/**
* register token filter
* Register or get (a specific one or all) token filters
* @param string $key
* @param string $func
* @return array
* @param string|closure $func
* @return array|closure|string
*/
function filter($key=NULL,$func=NULL) {
if (!$key)
@@ -2578,19 +2603,15 @@ class Preview extends View {
protected function build($node) {
$self=$this;
return preg_replace_callback(
'/\{\-(.+?)\-\}|\{\{(.+?)\}\}(\n+)?/s',
'/\{\-(.+?)\-\}|\{\{(.+?)\}\}(\n+)?|(\{\*.*?\*\})/s',
function($expr) use($self) {
if ($expr[1])
return $expr[1];
$str=trim($self->token($expr[2]));
if (preg_match('/^([^|]+?)\h*\|(\h*\w+(?:\h*[,;]\h*\w+)*)/',
$str,$parts)) {
$str=$parts[1];
foreach (Base::instance()->split($parts[2]) as $func)
$str=$self->filter($func).'('.$str.')';
}
return '<?php echo '.$str.'; ?>'.
(isset($expr[3])?$expr[3]."\n":'');
return empty($expr[4])?
('<?php echo '.$str.'; ?>'.
(isset($expr[3])?$expr[3]."\n":'')):
'';
},
preg_replace_callback(
'/\{~(.+?)~\}/s',
@@ -2631,8 +2652,7 @@ class Preview extends View {
if (!is_dir($tmp=$fw->get('TEMP')))
mkdir($tmp,Base::MODE,TRUE);
foreach ($fw->split($fw->get('UI')) as $dir) {
$cached=$cache->exists($hash=$fw->hash($dir.$file),$data);
if ($cached && $cached[0]+$ttl>microtime(TRUE))
if ($cache->exists($hash=$fw->hash($dir.$file),$data))
return $data;
if (is_file($view=$fw->fixslashes($dir.$file))) {
if (!is_file($this->view=($tmp.
@@ -2659,7 +2679,7 @@ class Preview extends View {
foreach ($this->trigger['afterrender'] as $func)
$data = $fw->call($func, $data);
if ($ttl)
$cache->set($hash,$data);
$cache->set($hash,$data,$ttl);
return $data;
}
}

View File

@@ -195,7 +195,7 @@ class Basket extends Magic {
if (is_string($var))
$var=\Base::instance()->get($var);
foreach ($var as $key=>$val)
$this->item[$key]=$val;
$this->set($key,$val);
}
/**

View File

@@ -103,7 +103,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
/**
* Get cursor's equivalent external iterator
* Causes a fatal error in PHP 5.3.5if uncommented
* Causes a fatal error in PHP 5.3.5 if uncommented
* return ArrayIterator
**/
abstract function getiterator();
@@ -119,7 +119,7 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
/**
* Return first record (mapper object) that matches criteria
* @return \DB\Cursor|FALSE
* @return static|FALSE
* @param $filter string|array
* @param $options array
* @param $ttl int
@@ -171,8 +171,9 @@ abstract class Cursor extends \Magic implements \IteratorAggregate {
* @param $ttl int
**/
function load($filter=NULL,array $options=NULL,$ttl=0) {
$this->reset();
return ($this->query=$this->find($filter,$options,$ttl)) &&
$this->skip(0)?$this->query[$this->ptr=0]:FALSE;
$this->skip(0)?$this->query[$this->ptr]:FALSE;
}
/**

View File

@@ -149,7 +149,7 @@ class Mapper extends \DB\Cursor {
/**
* Return records that match criteria
* @return \DB\JIG\Mapper[]|FALSE
* @return static[]|FALSE
* @param $filter array
* @param $options array
* @param $ttl int
@@ -431,7 +431,7 @@ class Mapper extends \DB\Cursor {
if ($func)
$var=call_user_func($func,$var);
foreach ($var as $key=>$val)
$this->document[$key]=$val;
$this->set($key,$val);
}
/**

View File

@@ -27,7 +27,15 @@ class Session extends Mapper {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -44,6 +52,8 @@ class Session extends Mapper {
* @return TRUE
**/
function close() {
$this->reset();
$this->sid=NULL;
return TRUE;
}
@@ -53,9 +63,20 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->load(array('@session_id=?',$this->sid=$id));
return $this->dry()?FALSE:$this->get('data');
$this->load(array('@session_id=?',$this->sid=$id));
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $this->get('data');
}
/**
@@ -65,19 +86,10 @@ class Session extends Mapper {
* @param $data string
**/
function write($id,$data) {
$fw=\Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
if ($id!=$this->sid)
$this->load(array('@session_id=?',$this->sid=$id));
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->set('session_id',$id);
$this->set('data',$data);
$this->set('csrf',$sent?$this->csrf():$csrf);
$this->set('ip',$fw->get('IP'));
$this->set('agent',
isset($headers['User-Agent'])?$headers['User-Agent']:'');
$this->set('ip',$this->_ip);
$this->set('agent',$this->_agent);
$this->set('stamp',time());
$this->save();
return TRUE;
@@ -90,9 +102,6 @@ class Session extends Mapper {
**/
function destroy($id) {
$this->erase(array('@session_id=?',$id));
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -107,19 +116,27 @@ class Session extends Mapper {
}
/**
* Return anti-CSRF token
* @return string|FALSE
**/
function csrf() {
return $this->dry()?FALSE:$this->get('csrf');
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return IP address
* @return string|FALSE
**/
* Return anti-CSRF token
* @return string
**/
function csrf() {
return $this->_csrf;
}
/**
* Return IP address
* @return string
**/
function ip() {
return $this->dry()?FALSE:$this->get('ip');
return $this->_ip;
}
/**
@@ -127,6 +144,8 @@ class Session extends Mapper {
* @return string|FALSE
**/
function stamp() {
if (!$this->sid)
session_start();
return $this->dry()?FALSE:$this->get('stamp');
}
@@ -135,17 +154,19 @@ class Session extends Mapper {
* @return string|FALSE
**/
function agent() {
return $this->dry()?FALSE:$this->get('agent');
return $this->_agent;
}
/**
* Instantiate class
* @param $db object
* @param $db \DB\Jig
* @param $file string
* @param $onsuspect callback
* @param $key string
**/
function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL) {
function __construct(\DB\Jig $db,$file='sessions',$onsuspect=NULL,$key=NULL) {
parent::__construct($db,$file);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -155,26 +176,14 @@ class Session extends Mapper {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
if ($this->load(array('@session_id=?',$this->sid=session_id()))) {
$this->set('csrf',$csrf);
$this->save();
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -84,7 +84,7 @@ class Mapper extends \DB\Cursor {
/**
* Convert array to mapper object
* @return \DB\Mongo\Mapper
* @return static
* @param $row array
**/
protected function factory($row) {
@@ -111,7 +111,7 @@ class Mapper extends \DB\Cursor {
/**
* Build query and execute
* @return \DB\Mongo\Mapper[]
* @return static[]
* @param $fields string
* @param $filter array
* @param $options array
@@ -177,7 +177,7 @@ class Mapper extends \DB\Cursor {
/**
* Return records that match criteria
* @return \DB\Mongo\Mapper[]
* @return static[]
* @param $filter array
* @param $options array
* @param $ttl int
@@ -308,7 +308,7 @@ class Mapper extends \DB\Cursor {
if ($func)
$var=call_user_func($func,$var);
foreach ($var as $key=>$val)
$this->document[$key]=$val;
$this->set($key,$val);
}
/**

View File

@@ -27,7 +27,15 @@ class Session extends Mapper {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -44,6 +52,8 @@ class Session extends Mapper {
* @return TRUE
**/
function close() {
$this->reset();
$this->sid=NULL;
return TRUE;
}
@@ -53,9 +63,20 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->load(array('session_id'=>$this->sid=$id));
return $this->dry()?FALSE:$this->get('data');
$this->load(array('session_id'=>$this->sid=$id));
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $this->get('data');
}
/**
@@ -65,19 +86,10 @@ class Session extends Mapper {
* @param $data string
**/
function write($id,$data) {
$fw=\Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
if ($id!=$this->sid)
$this->load(array('session_id'=>$this->sid=$id));
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->set('session_id',$id);
$this->set('data',$data);
$this->set('csrf',$sent?$this->csrf():$csrf);
$this->set('ip',$fw->get('IP'));
$this->set('agent',
isset($headers['User-Agent'])?$headers['User-Agent']:'');
$this->set('ip',$this->_ip);
$this->set('agent',$this->_agent);
$this->set('stamp',time());
$this->save();
return TRUE;
@@ -90,9 +102,6 @@ class Session extends Mapper {
**/
function destroy($id) {
$this->erase(array('session_id'=>$id));
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -107,45 +116,57 @@ class Session extends Mapper {
}
/**
* Return anti-CSRF token
* @return string|FALSE
**/
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return anti-CSRF token
* @return string
**/
function csrf() {
return $this->dry()?FALSE:$this->get('csrf');
return $this->_csrf;
}
/**
* Return IP address
* @return string|FALSE
**/
* Return IP address
* @return string
**/
function ip() {
return $this->dry()?FALSE:$this->get('ip');
return $this->_ip;
}
/**
* Return Unix timestamp
* @return string|FALSE
**/
* Return Unix timestamp
* @return string|FALSE
**/
function stamp() {
if (!$this->sid)
session_start();
return $this->dry()?FALSE:$this->get('stamp');
}
/**
* Return HTTP user agent
* @return string|FALSE
**/
* Return HTTP user agent
* @return string
**/
function agent() {
return $this->dry()?FALSE:$this->get('agent');
return $this->_agent;
}
/**
* Instantiate class
* @param $db object
* @param $db \DB\Mongo
* @param $table string
* @param $onsuspect callback
* @param $key string
**/
function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL) {
function __construct(\DB\Mongo $db,$table='sessions',$onsuspect=NULL,$key=NULL) {
parent::__construct($db,$table);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -155,26 +176,14 @@ class Session extends Mapper {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
if ($this->load(array('session_id'=>$this->sid=session_id()))) {
$this->set('csrf',$csrf);
$this->save();
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -91,6 +91,8 @@ class SQL {
return \PDO::PARAM_BOOL;
case 'integer':
return \PDO::PARAM_INT;
case 'resource':
return \PDO::PARAM_LOB;
default:
return \PDO::PARAM_STR;
}
@@ -112,6 +114,8 @@ class SQL {
return (bool)$val;
case \PDO::PARAM_STR:
return (string)$val;
case \PDO::PARAM_LOB:
return (binary)$val;
}
}
@@ -149,6 +153,11 @@ class SQL {
for ($i=0;$i<$count;$i++) {
$cmd=$cmds[$i];
$arg=$args[$i];
// ensure 1-based arguments
if (array_key_exists(0,$arg)) {
array_unshift($arg,'');
unset($arg[0]);
}
if (!preg_replace('/(^\s+|[\s;]+$)/','',$cmd))
continue;
$now=microtime(TRUE);
@@ -198,8 +207,8 @@ class SQL {
$this->rollback();
user_error('PDOStatement: '.$error[2],E_USER_ERROR);
}
if (preg_match('/^\s*'.
'(?:EXPLAIN|SELECT|PRAGMA|SHOW|RETURNING)\b/is',$cmd) ||
if (preg_match('/(?:^[\s\(]*'.
'(?:EXPLAIN|SELECT|PRAGMA|SHOW)|RETURNING)\b/is',$cmd) ||
(preg_match('/^\s*(?:CALL|EXEC)\b/is',$cmd) &&
$query->columnCount())) {
$result=$query->fetchall(\PDO::FETCH_ASSOC);
@@ -245,11 +254,14 @@ class SQL {
}
/**
* Return SQL profiler results
* Return SQL profiler results (or disable logging)
* @param $flag bool
* @return string
**/
function log() {
return $this->log;
function log($flag=TRUE) {
if ($flag)
return $this->log;
$this->log=FALSE;
}
/**
@@ -333,7 +345,10 @@ class SQL {
\PDO::PARAM_INT:
(preg_match('/bool/i',$row[$val[2]])?
\PDO::PARAM_BOOL:
\PDO::PARAM_STR),
(preg_match('/blob|bytea|image|binary/i',
$row[$val[2]])?
\PDO::PARAM_LOB:
\PDO::PARAM_STR)),
'default'=>is_string($row[$val[3]])?
preg_replace('/^\s*([\'"])(.*)\1\s*/','\2',
$row[$val[3]]):$row[$val[3]],
@@ -422,7 +437,7 @@ class SQL {
}
/**
* Redirect call to MongoDB object
* Redirect call to PDO object
* @return mixed
* @param $func string
* @param $args array

View File

@@ -190,7 +190,7 @@ class Mapper extends \DB\Cursor {
/**
* Build query string and execute
* @return \DB\SQL\Mapper[]
* @return static[]
* @param $fields string
* @param $filter string|array
* @param $options array
@@ -294,7 +294,7 @@ class Mapper extends \DB\Cursor {
/**
* Return records that match criteria
* @return \DB\SQL\Mapper[]
* @return static[]
* @param $filter string|array
* @param $options array
* @param $ttl int
@@ -362,7 +362,7 @@ class Mapper extends \DB\Cursor {
$field['value']=$dry?NULL:$out->adhoc[$key]['value'];
unset($field);
}
if (isset($this->trigger['load']))
if (!$dry && isset($this->trigger['load']))
\Base::instance()->call($this->trigger['load'],$this);
return $out;
}
@@ -559,14 +559,8 @@ class Mapper extends \DB\Cursor {
if ($func)
$var=call_user_func($func,$var);
foreach ($var as $key=>$val)
if (in_array($key,array_keys($this->fields))) {
$field=&$this->fields[$key];
if ($field['value']!==$val) {
$field['value']=$val;
$field['changed']=TRUE;
}
unset($field);
}
if (in_array($key,array_keys($this->fields)))
$this->set($key,$val);
}
/**

View File

@@ -27,7 +27,15 @@ class Session extends Mapper {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -44,6 +52,8 @@ class Session extends Mapper {
* @return TRUE
**/
function close() {
$this->reset();
$this->sid=NULL;
return TRUE;
}
@@ -53,9 +63,20 @@ class Session extends Mapper {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->load(array('session_id=?',$this->sid=$id));
return $this->dry()?FALSE:$this->get('data');
$this->load(array('session_id=?',$this->sid=$id));
if ($this->dry())
return FALSE;
if ($this->get('ip')!=$this->_ip || $this->get('agent')!=$this->_agent) {
$fw=\Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $this->get('data');
}
/**
@@ -65,19 +86,10 @@ class Session extends Mapper {
* @param $data string
**/
function write($id,$data) {
$fw=\Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
if ($id!=$this->sid)
$this->load(array('session_id=?',$this->sid=$id));
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$this->set('session_id',$id);
$this->set('data',$data);
$this->set('csrf',$sent?$this->csrf():$csrf);
$this->set('ip',$fw->get('IP'));
$this->set('agent',
isset($headers['User-Agent'])?$headers['User-Agent']:'');
$this->set('ip',$this->_ip);
$this->set('agent',$this->_agent);
$this->set('stamp',time());
$this->save();
return TRUE;
@@ -90,9 +102,6 @@ class Session extends Mapper {
**/
function destroy($id) {
$this->erase(array('session_id=?',$id));
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -106,20 +115,28 @@ class Session extends Mapper {
return TRUE;
}
/**
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return anti-CSRF token
* @return string|FALSE
* @return string
**/
function csrf() {
return $this->dry()?FALSE:$this->get('csrf');
return $this->_csrf;
}
/**
* Return IP address
* @return string|FALSE
* @return string
**/
function ip() {
return $this->dry()?FALSE:$this->get('ip');
return $this->_ip;
}
/**
@@ -127,25 +144,28 @@ class Session extends Mapper {
* @return string|FALSE
**/
function stamp() {
if (!$this->sid)
session_start();
return $this->dry()?FALSE:$this->get('stamp');
}
/**
* Return HTTP user agent
* @return string|FALSE
* @return string
**/
function agent() {
return $this->dry()?FALSE:$this->get('agent');
return $this->_agent;
}
/**
* Instantiate class
* @param $db object
* @param $db \DB\SQL
* @param $table string
* @param $force bool
* @param $onsuspect callback
* @param $key string
**/
function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL) {
function __construct(\DB\SQL $db,$table='sessions',$force=TRUE,$onsuspect=NULL,$key=NULL) {
if ($force) {
$eol="\n";
$tab="\t";
@@ -160,7 +180,6 @@ class Session extends Mapper {
$table.' ('.$eol.
$tab.$db->quotekey('session_id').' VARCHAR(40),'.$eol.
$tab.$db->quotekey('data').' TEXT,'.$eol.
$tab.$db->quotekey('csrf').' TEXT,'.$eol.
$tab.$db->quotekey('ip').' VARCHAR(40),'.$eol.
$tab.$db->quotekey('agent').' VARCHAR(255),'.$eol.
$tab.$db->quotekey('stamp').' INTEGER,'.$eol.
@@ -169,6 +188,7 @@ class Session extends Mapper {
);
}
parent::__construct($db,$table);
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -178,26 +198,14 @@ class Session extends Mapper {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
if ($this->load(array('session_id=?',$this->sid=session_id()))) {
$this->set('csrf',$csrf);
$this->save();
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -230,9 +230,9 @@ class Image {
$ratio=($origw=imagesx($this->data))/($origh=imagesy($this->data));
if (!$crop) {
if ($width/$ratio<=$height)
$height=$width/$ratio;
$height=round($width/$ratio);
else
$width=$height*$ratio;
$width=round($height*$ratio);
}
if (!$enlarge) {
$width=min($origw,$width);
@@ -245,12 +245,12 @@ class Image {
// Resize
if ($crop) {
if ($width/$ratio<=$height) {
$cropw=$origh*$width/$height;
$cropw=round($origh*$width/$height);
imagecopyresampled($tmp,$this->data,
0,0,($origw-$cropw)/2,0,$width,$height,$cropw,$origh);
}
else {
$croph=$origw*$height/$width;
$croph=round($origw*$height/$width);
imagecopyresampled($tmp,$this->data,
0,0,0,($origh-$croph)/2,$width,$height,$origw,$croph);
}
@@ -489,6 +489,14 @@ class Image {
return ob_get_clean();
}
/**
* Return image resource
* @return resource
**/
function data() {
return $this->data;
}
/**
* Save current state
* @return object

View File

@@ -25,7 +25,15 @@ class Session {
protected
//! Session ID
$sid;
$sid,
//! Anti-CSRF token
$_csrf,
//! User agent
$_agent,
//! IP,
$_ip,
//! Suspect callback
$onsuspect;
/**
* Open session
@@ -42,6 +50,7 @@ class Session {
* @return TRUE
**/
function close() {
$this->sid=NULL;
return TRUE;
}
@@ -51,9 +60,20 @@ class Session {
* @param $id string
**/
function read($id) {
if ($id!=$this->sid)
$this->sid=$id;
return Cache::instance()->exists($id.'.@',$data)?$data['data']:FALSE;
$this->sid=$id;
if (!$data=Cache::instance()->get($id.'.@'))
return FALSE;
if ($data['ip']!=$this->_ip || $data['agent']!=$this->_agent) {
$fw=Base::instance();
if (!isset($this->onsuspect) || FALSE===$fw->call($this->onsuspect,array($this,$id))) {
//NB: `session_destroy` can't be called at that stage (`session_start` not completed)
$this->destroy($id);
$this->close();
$fw->clear('COOKIE.'.session_name());
$fw->error(403);
}
}
return $data['data'];
}
/**
@@ -64,20 +84,12 @@ class Session {
**/
function write($id,$data) {
$fw=Base::instance();
$sent=headers_sent();
$headers=$fw->get('HEADERS');
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$jar=$fw->get('JAR');
if ($id!=$this->sid)
$this->sid=$id;
Cache::instance()->set($id.'.@',
array(
'data'=>$data,
'csrf'=>$sent?$this->csrf():$csrf,
'ip'=>$fw->get('IP'),
'agent'=>isset($headers['User-Agent'])?
$headers['User-Agent']:'',
'ip'=>$this->_ip,
'agent'=>$this->_agent,
'stamp'=>time()
),
$jar['expire']?($jar['expire']-time()):0
@@ -92,9 +104,6 @@ class Session {
**/
function destroy($id) {
Cache::instance()->clear($id.'.@');
setcookie(session_name(),'',strtotime('-1 year'));
unset($_COOKIE[session_name()]);
header_remove('Set-Cookie');
return TRUE;
}
@@ -109,50 +118,55 @@ class Session {
}
/**
* Return anti-CSRF token
* @return string|FALSE
**/
* Return session id (if session has started)
* @return string|NULL
**/
function sid() {
return $this->sid;
}
/**
* Return anti-CSRF token
* @return string
**/
function csrf() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['csrf']:FALSE;
return $this->_csrf;
}
/**
* Return IP address
* @return string|FALSE
**/
* Return IP address
* @return string
**/
function ip() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['ip']:FALSE;
return $this->_ip;
}
/**
* Return Unix timestamp
* @return string|FALSE
**/
* Return Unix timestamp
* @return string|FALSE
**/
function stamp() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['stamp']:FALSE;
if (!$this->sid)
session_start();
return Cache::instance()->exists($this->sid.'.@',$data)?
$data['stamp']:FALSE;
}
/**
* Return HTTP user agent
* @return string|FALSE
**/
* Return HTTP user agent
* @return string
**/
function agent() {
return Cache::instance()->
exists(($this->sid?:session_id()).'.@',$data)?
$data['agent']:FALSE;
return $this->_agent;
}
/**
* Instantiate class
* @param $onsuspect callback
* @param $key string
**/
function __construct($onsuspect=NULL) {
function __construct($onsuspect=NULL,$key=NULL) {
$this->onsuspect=$onsuspect;
session_set_save_handler(
array($this,'open'),
array($this,'close'),
@@ -162,30 +176,14 @@ class Session {
array($this,'cleanup')
);
register_shutdown_function('session_commit');
@session_start();
$fw=\Base::instance();
$headers=$fw->get('HEADERS');
if (($ip=$this->ip()) && $ip!=$fw->get('IP') ||
($agent=$this->agent()) &&
(!isset($headers['User-Agent']) ||
$agent!=$headers['User-Agent'])) {
if (isset($onsuspect))
$fw->call($onsuspect,array($this));
else {
session_destroy();
$fw->error(403);
}
}
$csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$this->_csrf=$fw->hash($fw->get('ROOT').$fw->get('BASE')).'.'.
$fw->hash(mt_rand());
$jar=$fw->get('JAR');
if (Cache::instance()->exists(($this->sid=session_id()).'.@',$data)) {
$data['csrf']=$csrf;
Cache::instance()->set($this->sid.'.@',
$data,
$jar['expire']?($jar['expire']-time()):0
);
}
if ($key)
$fw->set($key,$this->_csrf);
$this->_agent=isset($headers['User-Agent'])?$headers['User-Agent']:'';
$this->_ip=$fw->get('IP');
}
}

View File

@@ -182,12 +182,13 @@ class SMTP extends Magic {
stream_socket_enable_crypto(
$socket,TRUE,STREAM_CRYPTO_METHOD_TLS_CLIENT);
$reply=$this->dialog('EHLO '.$fw->get('HOST'),$log);
if (preg_match('/8BITMIME/',$reply))
$headers['Content-Transfer-Encoding']='8bit';
else {
$headers['Content-Transfer-Encoding']='quoted-printable';
$message=quoted_printable_encode($message);
}
}
if (preg_match('/8BITMIME/',$reply))
$headers['Content-Transfer-Encoding']='8bit';
else {
$headers['Content-Transfer-Encoding']='quoted-printable';
$message=preg_replace('/^\.(.+)/m',
'..$1',quoted_printable_encode($message));
}
if ($this->user && $this->pw && preg_match('/AUTH/',$reply)) {
// Authenticate
@@ -204,9 +205,9 @@ class SMTP extends Magic {
$str='';
// Stringify headers
foreach ($headers as $key=>&$val) {
if (!in_array($key,$reqd)) {
if (!in_array($key,$reqd) && (!$this->attachments ||
$key!='Content-Type' && $key!='Content-Transfer-Encoding'))
$str.=$key.': '.$val.$eol;
}
if (in_array($key,array('From','To','Cc','Bcc')) &&
!preg_match('/[<>]/',$val))
$val='<'.$val.'>';
@@ -221,12 +222,13 @@ class SMTP extends Magic {
$this->dialog('DATA',$log);
if ($this->attachments) {
// Replace Content-Type
$hash=uniqid(NULL,TRUE);
$type=$headers['Content-Type'];
$headers['Content-Type']='multipart/mixed; '.
'boundary="'.$hash.'"';
unset($headers['Content-Type']);
$enc=$headers['Content-Transfer-Encoding'];
unset($headers['Content-Transfer-Encoding']);
$hash=uniqid(NULL,TRUE);
// Send mail headers
$out='';
$out='Content-Type: multipart/mixed; boundary="'.$hash.'"'.$eol;
foreach ($headers as $key=>$val)
if ($key!='Bcc')
$out.=$key.': '.$val.$eol;
@@ -235,16 +237,17 @@ class SMTP extends Magic {
$out.=$eol;
$out.='--'.$hash.$eol;
$out.='Content-Type: '.$type.$eol;
$out.=$eol;
$out.='Content-Transfer-Encoding: '.$enc.$eol;
$out.=$str.$eol;
$out.=$message.$eol;
foreach ($this->attachments as $attachment) {
if (is_array($attachment['filename'])) {
list($alias,$file)=each($attachment);
list($alias,$file)=each($attachment['filename']);
$filename=$alias;
$attachment['filename']=$file;
}
else
$filename=basename($attachment);
$filename=basename($attachment['filename']);
$out.='--'.$hash.$eol;
$out.='Content-Type: application/octet-stream'.$eol;
$out.='Content-Transfer-Encoding: base64'.$eol;
@@ -253,8 +256,8 @@ class SMTP extends Magic {
$out.='Content-Disposition: attachment; '.
'filename="'.$filename.'"'.$eol;
$out.=$eol;
$out.=chunk_split(
base64_encode(file_get_contents($attachment))).$eol;
$out.=chunk_split(base64_encode(
file_get_contents($attachment['filename']))).$eol;
}
$out.=$eol;
$out.='--'.$hash.'--'.$eol;
@@ -287,7 +290,7 @@ class SMTP extends Magic {
* @param $user string
* @param $pw string
**/
function __construct($host,$port,$scheme,$user,$pw) {
function __construct($host='localhost',$port=25,$scheme=null,$user=null,$pw=null) {
$this->headers=array(
'MIME-Version'=>'1.0',
'Content-Type'=>'text/plain; '.

View File

@@ -69,6 +69,7 @@ class Template extends Preview {
\Base::instance()->stringify($pair[2]));
},$pairs)).')+get_defined_vars()':
'get_defined_vars()';
$ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0;
return
'<?php '.(isset($attrib['if'])?
('if ('.$this->token($attrib['if']).') '):'').
@@ -76,7 +77,7 @@ class Template extends Preview {
(preg_match('/^\{\{(.+?)\}\}$/',$attrib['href'])?
$this->token($attrib['href']):
Base::instance()->stringify($attrib['href'])).','.
'$this->mime,'.$hive.'); ?>');
'$this->mime,'.$hive.','.$ttl.'); ?>');
}
/**
@@ -269,45 +270,40 @@ class Template extends Preview {
**/
function parse($text) {
// Build tree structure
for ($ptr=0,$len=strlen($text),$tree=array(),$node=&$tree,
$stack=array(),$depth=0,$tmp='';$ptr<$len;)
if (preg_match('/^<(\/?)(?:F3:)?'.
for ($ptr=0,$w=5,$len=strlen($text),$tree=array(),$tmp='';$ptr<$len;)
if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
'('.$this->tags.')\b((?:\h+[\w-]+'.
'(?:\h*=\h*(?:"(?:.+?)"|\'(?:.+?)\'))?|'.
'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
'\h*\{\{.+?\}\})*)\h*(\/?)>/is',
substr($text,$ptr),$match)) {
if (strlen($tmp))
$node[]=$tmp;
if (strlen($tmp)||$match[1])
$tree[]=$tmp.$match[1];
// Element node
if ($match[1]) {
if ($match[2]) {
// Find matching start tag
$save=$depth;
$found=FALSE;
while ($depth>0) {
$depth--;
foreach ($stack[$depth] as $item)
if (is_array($item) && isset($item[$match[2]])) {
// Start tag found
$found=TRUE;
break 2;
}
$stack=array();
for($i=count($tree)-1;$i>=0;$i--) {
$item = $tree[$i];
if (is_array($item) && array_key_exists($match[3],$item)
&& !isset($item[$match[3]][0])) {
// Start tag found
$tree[$i][$match[3]]+=array_reverse($stack);
$tree=array_slice($tree,0,$i+1);
break;
} else $stack[]=$item;
}
if (!$found)
// Unbalanced tag
$depth=$save;
$node=&$stack[$depth];
}
else {
// Start tag
$stack[$depth]=&$node;
$node=&$node[][$match[2]];
if ($match[3]) {
$node=&$tree[][$match[3]];
$node=array();
if ($match[4]) {
// Process attributes
preg_match_all(
'/(?:\b([\w-]+)\h*'.
'(?:=\h*(?:"(.*?)"|\'(.*?)\'))?|'.
'(\{\{.+?\}\}))/s',
$match[3],$attr,PREG_SET_ORDER);
$match[4],$attr,PREG_SET_ORDER);
foreach ($attr as $kv)
if (isset($kv[4]))
$node['@attrib'][]=$kv[4];
@@ -318,26 +314,23 @@ class Template extends Preview {
(isset($kv[3]) && $kv[3]!==''?
$kv[3]:NULL));
}
if ($match[4])
// Empty tag
$node=&$stack[$depth];
else
$depth++;
}
$tmp='';
$ptr+=strlen($match[0]);
$w=5;
}
else {
// Text node
$tmp.=substr($text,$ptr,1);
$ptr++;
$tmp.=substr($text,$ptr,$w);
$ptr+=$w;
if ($w<50)
$w++;
}
if (strlen($tmp))
// Append trailing text
$node[]=$tmp;
$tree[]=$tmp;
// Break references
unset($node);
unset($stack);
return $tree;
}

View File

@@ -133,7 +133,7 @@ class Web extends Prefab {
header('Content-Type: '.($mime?:$this->mime($file)));
if ($force)
header('Content-Disposition: attachment; '.
'filename='.var_export(basename($file),TRUE));
'filename="'.basename($file).'"');
header('Accept-Ranges: bytes');
header('Content-Length: '.$size);
header('X-Powered-By: '.Base::instance()->get('PACKAGE'));
@@ -259,10 +259,13 @@ class Web extends Prefab {
**/
protected function _curl($url,$options) {
$curl=curl_init($url);
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,
$options['follow_location']);
if (!ini_get('open_basedir'))
curl_setopt($curl,CURLOPT_FOLLOWLOCATION,
$options['follow_location']);
curl_setopt($curl,CURLOPT_MAXREDIRS,
$options['max_redirects']);
curl_setopt($curl,CURLOPT_PROTOCOLS,CURLPROTO_HTTP|CURLPROTO_HTTPS);
curl_setopt($curl,CURLOPT_REDIR_PROTOCOLS,CURLPROTO_HTTP|CURLPROTO_HTTPS);
curl_setopt($curl,CURLOPT_CUSTOMREQUEST,$options['method']);
if (isset($options['header']))
curl_setopt($curl,CURLOPT_HTTPHEADER,$options['header']);
@@ -288,6 +291,11 @@ class Web extends Prefab {
curl_exec($curl);
curl_close($curl);
$body=ob_get_clean();
if ($options['follow_location'] &&
preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) {
$options['max_redirects']--;
return $this->request($loc[1],$options);
}
return array(
'body'=>$body,
'headers'=>$headers,
@@ -357,7 +365,8 @@ class Web extends Prefab {
if (!$socket)
return FALSE;
stream_set_blocking($socket,TRUE);
stream_set_timeout($socket,$options['timeout']);
stream_set_timeout($socket,isset($options['timeout'])?
$options['timeout']:ini_get('default_socket_timeout'));
fputs($socket,$options['method'].' '.$parts['path'].
($parts['query']?('?'.$parts['query']):'').' HTTP/1.0'.$eol
);
@@ -565,7 +574,7 @@ class Web extends Prefab {
$src=$fw->read($save);
for ($ptr=0,$len=strlen($src);$ptr<$len;) {
if (preg_match('/^@import\h+url'.
'\(\h*([\'"])(.+?)\1\h*\)[^;]*;/',
'\(\h*([\'"])((?!(?:https?:)?\/\/).+?)\1\h*\)[^;]*;/',
substr($src,$ptr),$parts)) {
$path=dirname($file);
$data.=$this->minify(
@@ -586,7 +595,8 @@ class Web extends Prefab {
// Single-line comment
$str=strstr(
substr($src,$ptr+2),"\n",TRUE);
$ptr+=strlen($str)+2;
$ptr+=(empty($str))?
strlen(substr($src,$ptr)):strlen($str)+2;
}
else {
// Presume it's a regex pattern
@@ -785,7 +795,7 @@ class Web extends Prefab {
'ù'=>'u','ű'=>'u','ů'=>'u','ư'=>'u','ū'=>'u','ǚ'=>'u',
'ǜ'=>'u','ǔ'=>'u','ǖ'=>'u','ũ'=>'u','ü'=>'ue','в'=>'v',
'ŵ'=>'w','ы'=>'y','ÿ'=>'y','ý'=>'y','ŷ'=>'y','ź'=>'z',
'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh'
'ž'=>'z','з'=>'z','ż'=>'z','ж'=>'zh','ь'=>'','ъ'=>''
)+Base::instance()->get('DIACRITICS'))))),'-');
}

View File

@@ -25,7 +25,7 @@ class AccessController extends Controller {
if( !$loginCheck ){
// no user found or LogIn timer expired
$this->logOut($f3);
$this->logout($f3);
}
}

View File

@@ -173,9 +173,9 @@ class User extends Controller\Controller{
* log the current user out + clear character system log data
* @param \Base $f3
*/
public function logOut(\Base $f3){
public function logout(\Base $f3){
$this->deleteLog($f3);
parent::logOut($f3);
parent::logout($f3);
}
/**
@@ -344,7 +344,7 @@ class User extends Controller\Controller{
// remove user
$user->erase();
$this->logOut($f3);
$this->logout($f3);
die();
}
}

View File

@@ -197,7 +197,7 @@ class Controller {
'expires' => $expireTime->format('Y-m-d H:i:s')
];
$authenticationModel = $character->rel('characterTokens');
$authenticationModel = $character->rel('characterAuthentications');
$authenticationModel->copyfrom($authData);
$authenticationModel->save();
@@ -349,15 +349,26 @@ class Controller {
}
/**
* log out current user
* log out current character
* @param \Base $f3
*/
public function logOut(\Base $f3){
// destroy session
public function logout(\Base $f3){
$params = (array)$f3->get('POST');
// ----------------------------------------------------------
// delete server side cookie validation data
// for the current character as well
if(
$params['clearCookies'] === '1' &&
( $activeCharacter = $this->getCharacter())
){
$activeCharacter->logout();
}
// destroy session login data -------------------------------
$f3->clear('SESSION');
if( $f3->get('AJAX') ){
$params = $f3->get('POST');
$return = (object) [];
if(
isset($params['reroute']) &&
@@ -370,7 +381,6 @@ class Controller {
}
echo json_encode($return);
die();
}else{
// redirect to landing page
$f3->reroute('@login');

View File

@@ -89,6 +89,10 @@ abstract class BasicModel extends \DB\Cortex {
$self->beforeInsertEvent($self);
});
$this->beforeerase( function($self){
$self->beforeeraseEvent($self);
});
$this->aftererase( function($self){
$self->aftereraseEvent($self);
});
@@ -409,6 +413,15 @@ abstract class BasicModel extends \DB\Cortex {
return true;
}
/**
* Event "Hook" function
* can be overwritten
* @return bool
*/
public function beforeeraseEvent($self){
return true;
}
/**
* Event "Hook" function
* can be overwritten

View File

@@ -9,6 +9,7 @@
namespace Model;
use DB\SQL\Schema;
use Controller;
class CharacterAuthenticationModel extends BasicModel{
@@ -51,4 +52,23 @@ class CharacterAuthenticationModel extends BasicModel{
'index' => true
]
];
/**
* Event "Hook" function
* can be overwritten
* @param $self CharacterAuthenticationModel
* @return bool
*/
public function beforeeraseEvent($self){
// clear existing client Cookies as well
$cookieName = Controller\Controller::COOKIE_PREFIX_CHARACTER;
$cookieName .= '_' . $this->characterId->getCookieName();
$self::getF3()->clear('COOKIE.' . $cookieName);
return true;
}
}

View File

@@ -94,7 +94,7 @@ class CharacterModel extends BasicModel {
'characterMaps' => [
'has-many' => ['Model\CharacterMapModel', 'characterId']
],
'characterTokens' => [
'characterAuthentications' => [
'has-many' => ['Model\CharacterAuthenticationModel', 'characterId']
]
];
@@ -538,4 +538,15 @@ class CharacterModel extends BasicModel {
return $maps;
}
public function logout(){
if($this->characterAuthentications){
foreach($this->characterAuthentications as $characterAuthentication){
/**
* @var $characterAuthentication CharacterAuthenticationModel
*/
$characterAuthentication->erase();
}
}
}
}

View File

@@ -15,7 +15,7 @@ define(['jquery'], function($) {
sendInviteKey: 'api/user/sendInvite', // ajax URL - send registration key
getCookieCharacterData: 'api/user/getCookieCharacter', // ajax URL - get character data from cookie
logIn: 'api/user/logIn', // ajax URL - login
logOut: 'api/user/logOut', // ajax URL - logout
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data

View File

@@ -230,7 +230,7 @@ define([
class: 'fa fa-sign-in fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('Logout');
$(document).triggerMenuEvent('Logout', {clearCookies: 1});
})
)
);
@@ -590,10 +590,19 @@ define([
});
$(document).on('pf:menuLogout', function(e, data){
var clearCookies = false;
if( typeof data === 'object' ){
if( data.hasOwnProperty('clearCookies') ){
clearCookies = data.clearCookies;
}
}
// logout
Util.logout({
ajaxData: {
reroute: 1
reroute: 1,
clearCookies: clearCookies
}
});
return false;

View File

@@ -1778,7 +1778,7 @@ define([
$.ajax({
type: 'POST',
url: Init.path.logOut,
url: Init.path.logout,
data: data,
dataType: 'json'
}).done(function(data){

View File

@@ -15,7 +15,7 @@ define(['jquery'], function($) {
sendInviteKey: 'api/user/sendInvite', // ajax URL - send registration key
getCookieCharacterData: 'api/user/getCookieCharacter', // ajax URL - get character data from cookie
logIn: 'api/user/logIn', // ajax URL - login
logOut: 'api/user/logOut', // ajax URL - logout
logout: 'api/user/logout', // ajax URL - logout
deleteLog: 'api/user/deleteLog', // ajax URL - delete character log
saveUserConfig: 'api/user/saveAccount', // ajax URL - saves/update user account
deleteAccount: 'api/user/deleteAccount', // ajax URL - delete Account data

View File

@@ -230,7 +230,7 @@ define([
class: 'fa fa-sign-in fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('Logout');
$(document).triggerMenuEvent('Logout', {clearCookies: 1});
})
)
);
@@ -590,10 +590,19 @@ define([
});
$(document).on('pf:menuLogout', function(e, data){
var clearCookies = false;
if( typeof data === 'object' ){
if( data.hasOwnProperty('clearCookies') ){
clearCookies = data.clearCookies;
}
}
// logout
Util.logout({
ajaxData: {
reroute: 1
reroute: 1,
clearCookies: clearCookies
}
});
return false;

View File

@@ -1778,7 +1778,7 @@ define([
$.ajax({
type: 'POST',
url: Init.path.logOut,
url: Init.path.logout,
data: data,
dataType: 'json'
}).done(function(data){