- upgraded FatFreeFramework 3.6.1 -> 3.6.2
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -115,18 +115,27 @@ class Auth {
|
||||
* @param $pw string
|
||||
**/
|
||||
protected function _ldap($id,$pw) {
|
||||
$dc=@ldap_connect($this->args['dc']);
|
||||
$port=(int)($this->args['port']?:389);
|
||||
$filter=$this->args['filter']=$this->args['filter']?:"uid=".$id;
|
||||
$this->args['attr']=$this->args['attr']?:["uid"];
|
||||
array_walk($this->args['attr'],
|
||||
function($attr)use(&$filter,$id) {
|
||||
$filter=str_ireplace($attr."=*",$attr."=".$id,$filter);});
|
||||
$dc=@ldap_connect($this->args['dc'],$port);
|
||||
if ($dc &&
|
||||
ldap_set_option($dc,LDAP_OPT_PROTOCOL_VERSION,3) &&
|
||||
ldap_set_option($dc,LDAP_OPT_REFERRALS,0) &&
|
||||
ldap_bind($dc,$this->args['rdn'],$this->args['pw']) &&
|
||||
($result=ldap_search($dc,$this->args['base_dn'],
|
||||
'uid='.$id)) &&
|
||||
$filter,$this->args['attr'])) &&
|
||||
ldap_count_entries($dc,$result) &&
|
||||
($info=ldap_get_entries($dc,$result)) &&
|
||||
$info['count']==1 &&
|
||||
@ldap_bind($dc,$info[0]['dn'],$pw) &&
|
||||
@ldap_close($dc)) {
|
||||
return $info[0]['uid'][0]==$id;
|
||||
return in_array($id,(array_map(function($value){return $value[0];},
|
||||
array_intersect_key($info[0],
|
||||
array_flip($this->args['attr'])))),TRUE);
|
||||
}
|
||||
user_error(self::E_LDAP,E_USER_ERROR);
|
||||
}
|
||||
@@ -160,12 +169,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 +205,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'];
|
||||
|
||||
612
app/lib/base.php
612
app/lib/base.php
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
|
||||
@@ -143,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) {
|
||||
$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
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -367,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;
|
||||
}
|
||||
@@ -385,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;
|
||||
@@ -404,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;
|
||||
@@ -428,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)
|
||||
|
||||
@@ -70,10 +70,11 @@ class Session extends Mapper {
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -146,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]);
|
||||
@@ -179,7 +179,7 @@ class Mapper extends \DB\Cursor {
|
||||
}
|
||||
if ($options['group'])
|
||||
$tmp->drop();
|
||||
if ($fw->get('CACHE') && $ttl)
|
||||
if ($fw->CACHE && $ttl)
|
||||
// Save to cache backend
|
||||
$cache->set($hash,$result,$ttl);
|
||||
}
|
||||
@@ -222,7 +222,7 @@ class Mapper extends \DB\Cursor {
|
||||
[$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);
|
||||
}
|
||||
@@ -292,10 +292,17 @@ class Mapper extends \DB\Cursor {
|
||||
/**
|
||||
* Delete current record
|
||||
* @return bool
|
||||
* @param $quick bool
|
||||
* @param $filter array
|
||||
**/
|
||||
function erase($filter=NULL) {
|
||||
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);
|
||||
@@ -332,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)
|
||||
|
||||
@@ -70,10 +70,11 @@ class Session extends Mapper {
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ class SQL {
|
||||
$val=str_replace(',','.',$val);
|
||||
return $val;
|
||||
case \PDO::PARAM_NULL:
|
||||
return (unset)$val;
|
||||
return NULL;
|
||||
case \PDO::PARAM_INT:
|
||||
return (int)$val;
|
||||
case \PDO::PARAM_BOOL:
|
||||
@@ -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)) {
|
||||
@@ -220,13 +220,15 @@ class SQL {
|
||||
'/';
|
||||
}
|
||||
if ($log)
|
||||
$this->log.=($stamp?(date('r').' '):'').'('.
|
||||
sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
|
||||
$this->log.=($stamp?(date('r').' '):'').' (-0ms) '.
|
||||
preg_replace($keys,$vals,
|
||||
str_replace('?',chr(0).'?',$cmd),1).PHP_EOL;
|
||||
$query->execute();
|
||||
$error=$query->errorinfo();
|
||||
if ($error[0]!=\PDO::ERR_NONE) {
|
||||
if ($log)
|
||||
$this->log=str_replace('(-0ms)',
|
||||
'('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms)',
|
||||
$this->log);
|
||||
if (($error=$query->errorinfo()) && $error[0]!=\PDO::ERR_NONE) {
|
||||
// Statement-level error occurred
|
||||
if ($this->trans)
|
||||
$this->rollback();
|
||||
@@ -246,7 +248,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);
|
||||
}
|
||||
@@ -255,15 +257,13 @@ class SQL {
|
||||
$query->closecursor();
|
||||
unset($query);
|
||||
}
|
||||
else {
|
||||
$error=$this->errorinfo();
|
||||
if ($error[0]!=\PDO::ERR_NONE) {
|
||||
// PDO-level error occurred
|
||||
if ($this->trans)
|
||||
$this->rollback();
|
||||
user_error('PDO: '.$error[2],E_USER_ERROR);
|
||||
}
|
||||
elseif (($error=$this->errorinfo()) && $error[0]!=\PDO::ERR_NONE) {
|
||||
// PDO-level error occurred
|
||||
if ($this->trans)
|
||||
$this->rollback();
|
||||
user_error('PDO: '.$error[2],E_USER_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
if ($this->trans && $auto)
|
||||
$this->commit();
|
||||
@@ -280,8 +280,8 @@ class SQL {
|
||||
|
||||
/**
|
||||
* Return SQL profiler results (or disable logging)
|
||||
* @param $flag bool
|
||||
* @return string
|
||||
* @param $flag bool
|
||||
**/
|
||||
function log($flag=TRUE) {
|
||||
if ($flag)
|
||||
@@ -289,6 +289,20 @@ class SQL {
|
||||
$this->log=FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return TRUE if table exists
|
||||
* @return bool
|
||||
* @param $table string
|
||||
**/
|
||||
function exists($table) {
|
||||
$mode=$this->pdo->getAttribute(\PDO::ATTR_ERRMODE);
|
||||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE,\PDO::ERRMODE_SILENT);
|
||||
$out=$this->pdo->
|
||||
query('SELECT 1 FROM '.$this->quotekey($table).' LIMIT 1');
|
||||
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE,$mode);
|
||||
return is_object($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve schema of SQL table
|
||||
* @return array|FALSE
|
||||
@@ -297,43 +311,50 @@ 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
|
||||
$cmd=[
|
||||
'sqlite2?'=>[
|
||||
'PRAGMA table_info("'.$table.'")',
|
||||
'PRAGMA table_info(`'.$table.'`)',
|
||||
'name','type','dflt_value','notnull',0,'pk',TRUE],
|
||||
'mysql'=>[
|
||||
'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`',
|
||||
'Field','Type','Default','Null','YES','Key','PRI'],
|
||||
'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>[
|
||||
'SELECT '.
|
||||
'c.column_name AS field,'.
|
||||
'c.data_type AS type,'.
|
||||
'c.column_default AS defval,'.
|
||||
'c.is_nullable AS nullable,'.
|
||||
't.constraint_type AS pkey '.
|
||||
'FROM information_schema.columns AS c '.
|
||||
'C.COLUMN_NAME AS field,'.
|
||||
'C.DATA_TYPE AS type,'.
|
||||
'C.COLUMN_DEFAULT AS defval,'.
|
||||
'C.IS_NULLABLE AS nullable,'.
|
||||
'T.CONSTRAINT_TYPE AS pkey '.
|
||||
'FROM INFORMATION_SCHEMA.COLUMNS AS C '.
|
||||
'LEFT OUTER JOIN '.
|
||||
'information_schema.key_column_usage AS k '.
|
||||
'INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS K '.
|
||||
'ON '.
|
||||
'c.table_name=k.table_name AND '.
|
||||
'c.column_name=k.column_name AND '.
|
||||
'c.table_schema=k.table_schema '.
|
||||
'C.TABLE_NAME=K.TABLE_NAME AND '.
|
||||
'C.COLUMN_NAME=K.COLUMN_NAME AND '.
|
||||
'C.TABLE_SCHEMA=K.TABLE_SCHEMA '.
|
||||
($this->dbname?
|
||||
('AND c.table_catalog=k.table_catalog '):'').
|
||||
('AND C.TABLE_CATALOG=K.TABLE_CATALOG '):'').
|
||||
'LEFT OUTER JOIN '.
|
||||
'information_schema.table_constraints AS t ON '.
|
||||
'k.table_name=t.table_name AND '.
|
||||
'k.constraint_name=t.constraint_name AND '.
|
||||
'k.table_schema=t.table_schema '.
|
||||
'INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS T ON '.
|
||||
'K.TABLE_NAME=T.TABLE_NAME AND '.
|
||||
'K.CONSTRAINT_NAME=T.CONSTRAINT_NAME AND '.
|
||||
'K.TABLE_SCHEMA=T.TABLE_SCHEMA '.
|
||||
($this->dbname?
|
||||
('AND k.table_catalog=t.table_catalog '):'').
|
||||
('AND K.TABLE_CATALOG=T.TABLE_CATALOG '):'').
|
||||
'WHERE '.
|
||||
'c.table_name='.$this->quote($table).
|
||||
'C.TABLE_NAME='.$this->quote($table).
|
||||
($this->dbname?
|
||||
(' AND c.table_catalog='.
|
||||
(' AND C.TABLE_CATALOG='.
|
||||
$this->quote($this->dbname)):''),
|
||||
'field','type','defval','nullable','YES','pkey','PRIMARY KEY'],
|
||||
'oci'=>[
|
||||
@@ -354,32 +375,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);
|
||||
@@ -448,8 +471,8 @@ class SQL {
|
||||
**/
|
||||
function quotekey($key, $split=TRUE) {
|
||||
$delims=[
|
||||
'mysql'=>'``',
|
||||
'sqlite2?|pgsql|oci'=>'""',
|
||||
'sqlite2?|mysql'=>'``',
|
||||
'pgsql|oci'=>'""',
|
||||
'mssql|sqlsrv|odbc|sybase|dblib'=>'[]'
|
||||
];
|
||||
$use='';
|
||||
@@ -492,7 +515,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);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ class Mapper extends \DB\Cursor {
|
||||
//! Defined fields
|
||||
$fields,
|
||||
//! Adhoc fields
|
||||
$adhoc=[];
|
||||
$adhoc=[],
|
||||
//! Dynamic properties
|
||||
$props=[];
|
||||
|
||||
/**
|
||||
* Return database type
|
||||
@@ -57,15 +59,6 @@ class Mapper extends \DB\Cursor {
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -80,6 +73,15 @@ class Mapper extends \DB\Cursor {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -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+=[
|
||||
@@ -224,7 +225,7 @@ class Mapper extends \DB\Cursor {
|
||||
$sql.=' GROUP BY '.implode(',',array_map(
|
||||
function($str) use($db) {
|
||||
return preg_replace_callback(
|
||||
'/\b(\w+)\h*(HAVING.+|$)/i',
|
||||
'/\b(\w+[._\-\w]*)\h*(HAVING.+|$)/i',
|
||||
function($parts) use($db) {
|
||||
return $db->quotekey($parts[1]).
|
||||
(isset($parts[2])?(' '.$parts[2]):'');
|
||||
@@ -235,47 +236,63 @@ class Mapper extends \DB\Cursor {
|
||||
explode(',',$options['group'])));
|
||||
}
|
||||
if ($options['order']) {
|
||||
$sql.=' ORDER BY '.implode(',',array_map(
|
||||
$order=' ORDER BY '.implode(',',array_map(
|
||||
function($str) use($db) {
|
||||
return preg_match('/^(\w+)(?:\h+(ASC|DESC))?\h*(?:,|$)/i',
|
||||
return preg_match('/^\h*(\w+[._\-\w]*)(?:\h+((?:ASC|DESC)[\w\h]*))?\h*$/i',
|
||||
$str,$parts)?
|
||||
($db->quotekey($parts[1]).
|
||||
(isset($parts[2])?(' '.$parts[2]):'')):$str;
|
||||
},
|
||||
explode(',',$options['order'])));
|
||||
}
|
||||
// SQL Server fixes
|
||||
if (preg_match('/mssql|sqlsrv|odbc/', $this->engine) &&
|
||||
($options['limit'] || $options['offset'])) {
|
||||
$pkeys=[];
|
||||
foreach ($this->fields as $key=>$field)
|
||||
if ($field['pkey'])
|
||||
$pkeys[]=$key;
|
||||
// order by pkey when no ordering option was given
|
||||
if (!$options['order'])
|
||||
foreach ($this->fields as $key=>$field)
|
||||
if ($field['pkey']) {
|
||||
$order=' ORDER BY '.$db->quotekey($key);
|
||||
break;
|
||||
}
|
||||
$ofs=$options['offset']?(int)$options['offset']:0;
|
||||
$lmt=$options['limit']?(int)$options['limit']:0;
|
||||
if (strncmp($db->version(),'11',2)>=0) {
|
||||
// SQL Server 2012
|
||||
if (!$options['order'])
|
||||
$sql.=' ORDER BY '.$db->quotekey($pkeys[0]);
|
||||
$sql.=' OFFSET '.$ofs.' ROWS';
|
||||
// SQL Server >= 2012
|
||||
$sql.=$order.' OFFSET '.$ofs.' ROWS';
|
||||
if ($lmt)
|
||||
$sql.=' FETCH NEXT '.$lmt.' ROWS ONLY';
|
||||
}
|
||||
else {
|
||||
// SQL Server 2008
|
||||
$sql=str_replace('SELECT',
|
||||
$sql=preg_replace('/SELECT/',
|
||||
'SELECT '.
|
||||
($lmt>0?'TOP '.($ofs+$lmt):'').' ROW_NUMBER() '.
|
||||
'OVER (ORDER BY '.
|
||||
$db->quotekey($pkeys[0]).') AS rnum,',$sql);
|
||||
'OVER ('.$order.') AS rnum,',$sql.$order,1);
|
||||
$sql='SELECT * FROM ('.$sql.') x WHERE rnum > '.($ofs);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isset($order))
|
||||
$sql.=$order;
|
||||
if ($options['limit'])
|
||||
$sql.=' LIMIT '.(int)$options['limit'];
|
||||
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) {
|
||||
@@ -285,8 +302,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);
|
||||
@@ -329,14 +344,17 @@ class Mapper extends \DB\Cursor {
|
||||
* @param $ttl int|array
|
||||
**/
|
||||
function count($filter=NULL,array $options=NULL,$ttl=0) {
|
||||
$expr='COUNT(*)';
|
||||
$field='rows';
|
||||
$this->adhoc[$field]=['expr'=>$expr,'value'=>NULL];
|
||||
$result=$this->select($expr.' AS '.$this->db->quotekey($field),
|
||||
$filter,$options,$ttl);
|
||||
$out=$result[0]->adhoc[$field]['value'];
|
||||
unset($this->adhoc[$field]);
|
||||
return $out;
|
||||
$adhoc='';
|
||||
foreach ($this->adhoc as $key=>$field)
|
||||
$adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
|
||||
$fields='*'.$adhoc;
|
||||
if (preg_match('/mssql|dblib|sqlsrv/',$this->engine))
|
||||
$fields='TOP 100 PERCENT '.$fields;
|
||||
list($sql,$args)=$this->stringify($fields,$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 (int)$result[0]['_rows'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,28 +422,27 @@ class Mapper extends \DB\Cursor {
|
||||
$actr++;
|
||||
$ckeys[]=$key;
|
||||
}
|
||||
$field['changed']=FALSE;
|
||||
unset($field);
|
||||
}
|
||||
if ($fields) {
|
||||
$this->db->exec(
|
||||
$add='';
|
||||
if ($this->engine=='pgsql') {
|
||||
$names=array_keys($pkeys);
|
||||
$aik=end($names);
|
||||
$add=' RETURNING '.$this->db->quotekey($aik);
|
||||
}
|
||||
$lID=$this->db->exec(
|
||||
(preg_match('/mssql|dblib|sqlsrv/',$this->engine) &&
|
||||
array_intersect(array_keys($pkeys),$ckeys)?
|
||||
'SET IDENTITY_INSERT '.$this->table.' ON;':'').
|
||||
'INSERT INTO '.$this->table.' ('.$fields.') '.
|
||||
'VALUES ('.$values.')',$args
|
||||
'VALUES ('.$values.')'.$add,$args
|
||||
);
|
||||
$seq=NULL;
|
||||
if ($this->engine=='pgsql') {
|
||||
$names=array_keys($pkeys);
|
||||
$aik=end($names);
|
||||
if ($this->fields[$aik]['pdo_type']==\PDO::PARAM_INT)
|
||||
$seq=$this->source.'_'.$aik.'_seq';
|
||||
}
|
||||
if ($this->engine!='oci' && !($this->engine=='pgsql' && !$seq))
|
||||
$this->_id=$this->db->lastinsertid($seq);
|
||||
if ($this->engine=='pgsql' && $lID)
|
||||
$this->_id=$lID[0][$aik];
|
||||
elseif ($this->engine!='oci')
|
||||
$this->_id=$this->db->lastinsertid();
|
||||
// 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)]:
|
||||
@@ -433,6 +450,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;
|
||||
}
|
||||
@@ -484,10 +508,17 @@ class Mapper extends \DB\Cursor {
|
||||
/**
|
||||
* Delete current record
|
||||
* @return int
|
||||
* @param $quick bool
|
||||
* @param $filter string|array
|
||||
**/
|
||||
function erase($filter=NULL) {
|
||||
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])?
|
||||
@@ -497,7 +528,8 @@ class Mapper extends \DB\Cursor {
|
||||
list($filter)=$filter;
|
||||
}
|
||||
return $this->db->
|
||||
exec('DELETE FROM '.$this->table.($filter?' WHERE '.$filter:'').';',$args);
|
||||
exec('DELETE FROM '.$this->table.
|
||||
($filter?' WHERE '.$filter:'').';',$args);
|
||||
}
|
||||
$args=[];
|
||||
$ctr=0;
|
||||
@@ -561,7 +593,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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -170,8 +170,9 @@ class Session extends Mapper {
|
||||
if ($force) {
|
||||
$eol="\n";
|
||||
$tab="\t";
|
||||
$sqlsrv=preg_match('/mssql|sqlsrv|sybase/',$db->driver());
|
||||
$db->exec(
|
||||
(preg_match('/mssql|sqlsrv|sybase/',$db->driver())?
|
||||
($sqlsrv?
|
||||
('IF NOT EXISTS (SELECT * FROM sysobjects WHERE '.
|
||||
'name='.$db->quote($table).' AND xtype=\'U\') '.
|
||||
'CREATE TABLE dbo.'):
|
||||
@@ -179,12 +180,14 @@ class Session extends Mapper {
|
||||
((($name=$db->name())&&$db->driver()!='pgsql')?
|
||||
($db->quotekey($name,FALSE).'.'):''))).
|
||||
$db->quotekey($table,FALSE).' ('.$eol.
|
||||
($sqlsrv?$tab.$db->quotekey('id').' INT IDENTITY,'.$eol:'').
|
||||
$tab.$db->quotekey('session_id').' VARCHAR(255),'.$eol.
|
||||
$tab.$db->quotekey('data').' TEXT,'.$eol.
|
||||
$tab.$db->quotekey('ip').' VARCHAR(45),'.$eol.
|
||||
$tab.$db->quotekey('agent').' VARCHAR(300),'.$eol.
|
||||
$tab.$db->quotekey('stamp').' INTEGER,'.$eol.
|
||||
$tab.'PRIMARY KEY ('.$db->quotekey('session_id').')'.$eol.
|
||||
$tab.'PRIMARY KEY ('.$db->quotekey($sqlsrv?'id':'session_id').')'.$eol.
|
||||
($sqlsrv?',CONSTRAINT [UK_session_id] UNIQUE(session_id)':'').
|
||||
');'
|
||||
);
|
||||
}
|
||||
@@ -200,12 +203,15 @@ 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');
|
||||
if (strlen($this->_agent) > 300) {
|
||||
$this->_agent = substr($this->_agent, 0, 300);
|
||||
}
|
||||
$this->_ip=$fw->IP;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
@@ -95,7 +95,7 @@ class Session {
|
||||
'agent'=>$this->_agent,
|
||||
'stamp'=>time()
|
||||
],
|
||||
$jar['expire']?($jar['expire']-time()):0
|
||||
$jar['expire']
|
||||
);
|
||||
return TRUE;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ class SMTP extends Magic {
|
||||
if (!is_file($file))
|
||||
user_error(sprintf(self::E_Attach,$file),E_USER_ERROR);
|
||||
if ($alias)
|
||||
$file=[$alias=>$file];
|
||||
$file=[$alias,$file];
|
||||
$this->attachments[]=['filename'=>$file,'cid'=>$cid];
|
||||
}
|
||||
|
||||
@@ -206,14 +206,15 @@ 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);
|
||||
}
|
||||
$message=wordwrap($message,998);
|
||||
if (preg_match('/8BITMIME/',$reply))
|
||||
$headers['Content-Transfer-Encoding']='8bit';
|
||||
else {
|
||||
@@ -243,14 +244,8 @@ class SMTP extends Magic {
|
||||
if (empty($headers[$id]))
|
||||
user_error(sprintf(self::E_Header,$id),E_USER_ERROR);
|
||||
$eol="\r\n";
|
||||
$str='';
|
||||
// Stringify headers
|
||||
foreach ($headers as $key=>&$val) {
|
||||
if (!in_array($key,$reqd) &&
|
||||
(!$this->attachments ||
|
||||
$key!='Content-Type' &&
|
||||
$key!='Content-Transfer-Encoding'))
|
||||
$str.=$key.': '.$val.$eol;
|
||||
if (in_array($key,['From','To','Cc','Bcc'])) {
|
||||
$email='';
|
||||
preg_match_all('/(?:".+?" )?(?:<.+?>|[^ ,]+)/',
|
||||
@@ -290,11 +285,11 @@ class SMTP extends Magic {
|
||||
$out.='--'.$hash.$eol;
|
||||
$out.='Content-Type: '.$type.$eol;
|
||||
$out.='Content-Transfer-Encoding: '.$enc.$eol;
|
||||
$out.=$str.$eol;
|
||||
$out.=$eol;
|
||||
$out.=$message.$eol;
|
||||
foreach ($this->attachments as $attachment) {
|
||||
if (is_array($attachment['filename']))
|
||||
list($alias,$file)=each($attachment['filename']);
|
||||
list($alias,$file)=$attachment['filename'];
|
||||
else
|
||||
$alias=basename($file=$attachment['filename']);
|
||||
$out.='--'.$hash.$eol;
|
||||
@@ -338,20 +333,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,$ctx=NULL) {
|
||||
$this->headers=[
|
||||
'MIME-Version'=>'1.0',
|
||||
'Content-Type'=>'text/plain; '.
|
||||
'charset='.Base::instance()->get('ENCODING')
|
||||
'charset='.Base::instance()->ENCODING
|
||||
];
|
||||
$this->host=(strtolower($this->scheme=strtolower($scheme))=='ssl'?
|
||||
'ssl':'tcp').'://'.$host;
|
||||
$this->host=strtolower((($this->scheme=strtolower($scheme))=='ssl'?
|
||||
'ssl':'tcp').'://'.$host);
|
||||
$this->port=$port;
|
||||
$this->user=$user;
|
||||
$this->pw=$pw;
|
||||
$this->context=$ctx;
|
||||
$this->context=stream_context_create($ctx);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ class Template extends Preview {
|
||||
return '\''.$pair[1].'\'=>'.
|
||||
(preg_match('/^\'.*\'$/',$pair[2]) ||
|
||||
preg_match('/\$/',$pair[2])?
|
||||
$pair[2]:
|
||||
\Base::instance()->stringify($pair[2]));
|
||||
$pair[2]:Base::instance()->stringify(
|
||||
Base::instance()->cast($pair[2])));
|
||||
},$pairs)).']+get_defined_vars()'):
|
||||
'get_defined_vars()';
|
||||
$ttl=isset($attrib['ttl'])?(int)$attrib['ttl']:0;
|
||||
@@ -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.'); ?>');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -252,7 +252,7 @@ class Template extends Preview {
|
||||
/**
|
||||
* Call custom tag handler
|
||||
* @return string|FALSE
|
||||
* @param $func callback
|
||||
* @param $func string
|
||||
* @param $args array
|
||||
**/
|
||||
function __call($func,array $args) {
|
||||
@@ -265,10 +265,11 @@ class Template extends Preview {
|
||||
|
||||
/**
|
||||
* Parse string for template directives and tokens
|
||||
* @return string|array
|
||||
* @return array
|
||||
* @param $text string
|
||||
**/
|
||||
function parse($text) {
|
||||
$text=parent::parse($text);
|
||||
// Build tree structure
|
||||
for ($ptr=0,$w=5,$len=strlen($text),$tree=[],$tmp='';$ptr<$len;)
|
||||
if (preg_match('/^(.{0,'.$w.'}?)<(\/?)(?:F3:)?'.
|
||||
@@ -276,21 +277,23 @@ class Template extends Preview {
|
||||
'(?:\h*=\h*(?:"(?:.*?)"|\'(?:.*?)\'))?|'.
|
||||
'\h*\{\{.+?\}\})*)\h*(\/?)>/is',
|
||||
substr($text,$ptr),$match)) {
|
||||
if (strlen($tmp)||$match[1])
|
||||
if (strlen($tmp) || $match[1])
|
||||
$tree[]=$tmp.$match[1];
|
||||
// Element node
|
||||
if ($match[2]) {
|
||||
// Find matching start tag
|
||||
$stack=[];
|
||||
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])) {
|
||||
$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;
|
||||
}
|
||||
else $stack[]=$item;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
150
app/lib/web.php
150
app/lib/web.php
@@ -138,7 +138,7 @@ 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);
|
||||
}
|
||||
if (!$kbps && $flush) {
|
||||
while (ob_get_level())
|
||||
@@ -179,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');
|
||||
@@ -198,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)?
|
||||
@@ -305,6 +305,7 @@ class Web extends Prefab {
|
||||
$body=ob_get_clean();
|
||||
if (!$err &&
|
||||
$options['follow_location'] &&
|
||||
preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) &&
|
||||
preg_match('/^Location: (.+)$/m',implode(PHP_EOL,$headers),$loc)) {
|
||||
$options['max_redirects']--;
|
||||
if($loc[1][0] == '/') {
|
||||
@@ -424,6 +425,7 @@ class Web extends Prefab {
|
||||
break;
|
||||
}
|
||||
if ($options['follow_location'] &&
|
||||
preg_grep('/HTTP\/1\.\d 3\d{2}/',$headers) &&
|
||||
preg_match('/Location: (.+?)'.preg_quote($eol).'/',
|
||||
$html[0],$loc)) {
|
||||
$options['max_redirects']--;
|
||||
@@ -489,9 +491,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']))
|
||||
@@ -544,7 +546,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(
|
||||
@@ -591,13 +593,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))
|
||||
@@ -692,7 +694,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;
|
||||
@@ -700,13 +704,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;
|
||||
}
|
||||
|
||||
@@ -773,6 +779,63 @@ class Web extends Prefab {
|
||||
return $info['timed_out']?FALSE:trim($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -780,56 +843,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'))))),'-');
|
||||
trim(strtr($text,Base::instance()->DIACRITICS+$this->diacritics())))),'-');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -881,9 +895,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);
|
||||
|
||||
@@ -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
app/lib/web/google/recaptcha.php
Normal file
58
app/lib/web/google/recaptcha.php
Normal file
@@ -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'];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,7 +63,7 @@ class OAuth2 extends \Magic {
|
||||
);
|
||||
$response=$web->request($uri,$options);
|
||||
if ($response['error'])
|
||||
user_error($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):
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user