summaryrefslogtreecommitdiffstats
path: root/management-interface/lib/db
diff options
context:
space:
mode:
authorNils Schwabe2014-06-04 14:27:03 +0200
committerNils Schwabe2014-06-04 14:27:03 +0200
commit155cf6aeea9ba7ecbc39face6442d3ce1b03ad8e (patch)
tree1dcc8354eaf6ce216461fc434d9c1a6a67559914 /management-interface/lib/db
parentImprove login (diff)
downloadmasterserver-155cf6aeea9ba7ecbc39face6442d3ce1b03ad8e.tar.gz
masterserver-155cf6aeea9ba7ecbc39face6442d3ce1b03ad8e.tar.xz
masterserver-155cf6aeea9ba7ecbc39face6442d3ce1b03ad8e.zip
Add webinterface with functionallity
Diffstat (limited to 'management-interface/lib/db')
-rw-r--r--management-interface/lib/db/cursor.php313
-rw-r--r--management-interface/lib/db/jig.php133
-rw-r--r--management-interface/lib/db/jig/mapper.php459
-rw-r--r--management-interface/lib/db/jig/session.php168
-rw-r--r--management-interface/lib/db/mongo.php92
-rw-r--r--management-interface/lib/db/mongo/mapper.php346
-rw-r--r--management-interface/lib/db/mongo/session.php174
-rw-r--r--management-interface/lib/db/sql.php403
-rw-r--r--management-interface/lib/db/sql/mapper.php552
-rw-r--r--management-interface/lib/db/sql/session.php187
10 files changed, 2827 insertions, 0 deletions
diff --git a/management-interface/lib/db/cursor.php b/management-interface/lib/db/cursor.php
new file mode 100644
index 0000000..354c683
--- /dev/null
+++ b/management-interface/lib/db/cursor.php
@@ -0,0 +1,313 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB;
+
+//! Simple cursor implementation
+abstract class Cursor extends \Magic {
+
+ //@{ Error messages
+ const
+ E_Field='Undefined field %s';
+ //@}
+
+ protected
+ //! Query results
+ $query=array(),
+ //! Current position
+ $ptr=0,
+ //! Event listeners
+ $trigger=array();
+
+ /**
+ * Return database type
+ * @return string
+ **/
+ abstract function dbtype();
+
+ /**
+ * Return fields of mapper object as an associative array
+ * @return array
+ * @param $obj object
+ **/
+ abstract function cast($obj=NULL);
+
+ /**
+ * Return records (array of mapper objects) that match criteria
+ * @return array
+ * @param $filter string|array
+ * @param $options array
+ * @param $ttl int
+ **/
+ abstract function find($filter=NULL,array $options=NULL,$ttl=0);
+
+ /**
+ * Count records that match criteria
+ * @return int
+ * @param $filter array
+ * @param $ttl int
+ **/
+ abstract function count($filter=NULL,$ttl=0);
+
+ /**
+ * Insert new record
+ * @return array
+ **/
+ abstract function insert();
+
+ /**
+ * Update current record
+ * @return array
+ **/
+ abstract function update();
+
+ /**
+ * Hydrate mapper object using hive array variable
+ * @return NULL
+ * @param $key string
+ * @param $func callback
+ **/
+ abstract function copyfrom($key,$func=NULL);
+
+ /**
+ * Populate hive array variable with mapper fields
+ * @return NULL
+ * @param $key string
+ **/
+ abstract function copyto($key);
+
+ /**
+ * Return TRUE if current cursor position is not mapped to any record
+ * @return bool
+ **/
+ function dry() {
+ return empty($this->query[$this->ptr]);
+ }
+
+ /**
+ * Return first record (mapper object) that matches criteria
+ * @return object|FALSE
+ * @param $filter string|array
+ * @param $options array
+ * @param $ttl int
+ **/
+ function findone($filter=NULL,array $options=NULL,$ttl=0) {
+ return ($data=$this->find($filter,$options,$ttl))?$data[0]:FALSE;
+ }
+
+ /**
+ * Return array containing subset of records matching criteria,
+ * total number of records in superset, specified limit, number of
+ * subsets available, and actual subset position
+ * @return array
+ * @param $pos int
+ * @param $size int
+ * @param $filter string|array
+ * @param $options array
+ * @param $ttl int
+ **/
+ function paginate(
+ $pos=0,$size=10,$filter=NULL,array $options=NULL,$ttl=0) {
+ $total=$this->count($filter,$ttl);
+ $count=ceil($total/$size);
+ $pos=max(0,min($pos,$count-1));
+ return array(
+ 'subset'=>$this->find($filter,
+ array_merge(
+ $options?:array(),
+ array('limit'=>$size,'offset'=>$pos*$size)
+ ),
+ $ttl
+ ),
+ 'total'=>$total,
+ 'limit'=>$size,
+ 'count'=>$count,
+ 'pos'=>$pos<$count?$pos:0
+ );
+ }
+
+ /**
+ * Map to first record that matches criteria
+ * @return array|FALSE
+ * @param $filter string|array
+ * @param $options array
+ * @param $ttl int
+ **/
+ function load($filter=NULL,array $options=NULL,$ttl=0) {
+ return ($this->query=$this->find($filter,$options,$ttl)) &&
+ $this->skip(0)?$this->query[$this->ptr=0]:FALSE;
+ }
+
+ /**
+ * Map to first record in cursor
+ * @return mixed
+ **/
+ function first() {
+ return $this->skip(-$this->ptr);
+ }
+
+ /**
+ * Map to last record in cursor
+ * @return mixed
+ **/
+ function last() {
+ return $this->skip(($ofs=count($this->query)-$this->ptr)?$ofs-1:0);
+ }
+
+ /**
+ * Map to nth record relative to current cursor position
+ * @return mixed
+ * @param $ofs int
+ **/
+ function skip($ofs=1) {
+ $this->ptr+=$ofs;
+ return $this->ptr>-1 && $this->ptr<count($this->query)?
+ $this->query[$this->ptr]:FALSE;
+ }
+
+ /**
+ * Map next record
+ * @return mixed
+ **/
+ function next() {
+ return $this->skip();
+ }
+
+ /**
+ * Map previous record
+ * @return mixed
+ **/
+ function prev() {
+ return $this->skip(-1);
+ }
+
+ /**
+ * Save mapped record
+ * @return mixed
+ **/
+ function save() {
+ return $this->query?$this->update():$this->insert();
+ }
+
+ /**
+ * Delete current record
+ * @return int|bool
+ **/
+ function erase() {
+ $this->query=array_slice($this->query,0,$this->ptr,TRUE)+
+ array_slice($this->query,$this->ptr,NULL,TRUE);
+ $this->ptr=0;
+ }
+
+ /**
+ * Define onload trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function onload($func) {
+ return $this->trigger['load']=$func;
+ }
+
+ /**
+ * Define beforeinsert trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function beforeinsert($func) {
+ return $this->trigger['beforeinsert']=$func;
+ }
+
+ /**
+ * Define afterinsert trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function afterinsert($func) {
+ return $this->trigger['afterinsert']=$func;
+ }
+
+ /**
+ * Define oninsert trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function oninsert($func) {
+ return $this->afterinsert($func);
+ }
+
+ /**
+ * Define beforeupdate trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function beforeupdate($func) {
+ return $this->trigger['beforeupdate']=$func;
+ }
+
+ /**
+ * Define afterupdate trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function afterupdate($func) {
+ return $this->trigger['afterupdate']=$func;
+ }
+
+ /**
+ * Define onupdate trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function onupdate($func) {
+ return $this->afterupdate($func);
+ }
+
+ /**
+ * Define beforeerase trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function beforeerase($func) {
+ return $this->trigger['beforeerase']=$func;
+ }
+
+ /**
+ * Define aftererase trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function aftererase($func) {
+ return $this->trigger['aftererase']=$func;
+ }
+
+ /**
+ * Define onerase trigger
+ * @return callback
+ * @param $func callback
+ **/
+ function onerase($func) {
+ return $this->aftererase($func);
+ }
+
+ /**
+ * Reset cursor
+ * @return NULL
+ **/
+ function reset() {
+ $this->query=array();
+ $this->ptr=0;
+ }
+
+}
diff --git a/management-interface/lib/db/jig.php b/management-interface/lib/db/jig.php
new file mode 100644
index 0000000..16f2255
--- /dev/null
+++ b/management-interface/lib/db/jig.php
@@ -0,0 +1,133 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB;
+
+//! Flat-file DB wrapper
+class Jig {
+
+ //@{ Storage formats
+ const
+ FORMAT_JSON=0,
+ FORMAT_Serialized=1;
+ //@}
+
+ protected
+ //! UUID
+ $uuid,
+ //! Storage location
+ $dir,
+ //! Current storage format
+ $format,
+ //! Jig log
+ $log;
+
+ /**
+ * Read data from file
+ * @return array
+ * @param $file string
+ **/
+ function read($file) {
+ $fw=\Base::instance();
+ if (!is_file($dst=$this->dir.$file))
+ return array();
+ $raw=$fw->read($dst);
+ switch ($this->format) {
+ case self::FORMAT_JSON:
+ $data=json_decode($raw,TRUE);
+ break;
+ case self::FORMAT_Serialized:
+ $data=$fw->unserialize($raw);
+ break;
+ }
+ return $data;
+ }
+
+ /**
+ * Write data to file
+ * @return int
+ * @param $file string
+ * @param $data array
+ **/
+ function write($file,array $data=NULL) {
+ $fw=\Base::instance();
+ switch ($this->format) {
+ case self::FORMAT_JSON:
+ $out=json_encode($data,@constant('JSON_PRETTY_PRINT'));
+ break;
+ case self::FORMAT_Serialized:
+ $out=$fw->serialize($data);
+ break;
+ }
+ return $fw->write($this->dir.$file,$out);
+ }
+
+ /**
+ * Return directory
+ * @return string
+ **/
+ function dir() {
+ return $this->dir;
+ }
+
+ /**
+ * Return UUID
+ * @return string
+ **/
+ function uuid() {
+ return $this->uuid;
+ }
+
+ /**
+ * Return SQL profiler results
+ * @return string
+ **/
+ function log() {
+ return $this->log;
+ }
+
+ /**
+ * Jot down log entry
+ * @return NULL
+ * @param $frame string
+ **/
+ function jot($frame) {
+ if ($frame)
+ $this->log.=date('r').' '.$frame.PHP_EOL;
+ }
+
+ /**
+ * Clean storage
+ * @return NULL
+ **/
+ function drop() {
+ if ($glob=@glob($this->dir.'/*',GLOB_NOSORT))
+ foreach ($glob as $file)
+ @unlink($file);
+ }
+
+ /**
+ * Instantiate class
+ * @param $dir string
+ * @param $format int
+ **/
+ function __construct($dir,$format=self::FORMAT_JSON) {
+ if (!is_dir($dir))
+ mkdir($dir,\Base::MODE,TRUE);
+ $this->uuid=\Base::instance()->hash($this->dir=$dir);
+ $this->format=$format;
+ }
+
+}
diff --git a/management-interface/lib/db/jig/mapper.php b/management-interface/lib/db/jig/mapper.php
new file mode 100644
index 0000000..3ac3d21
--- /dev/null
+++ b/management-interface/lib/db/jig/mapper.php
@@ -0,0 +1,459 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB\Jig;
+
+//! Flat-file DB mapper
+class Mapper extends \DB\Cursor {
+
+ protected
+ //! Flat-file DB wrapper
+ $db,
+ //! Data file
+ $file,
+ //! Document identifier
+ $id,
+ //! Document contents
+ $document=array();
+
+ /**
+ * Return database type
+ * @return string
+ **/
+ function dbtype() {
+ return 'Jig';
+ }
+
+ /**
+ * Return TRUE if field is defined
+ * @return bool
+ * @param $key string
+ **/
+ function exists($key) {
+ return array_key_exists($key,$this->document);
+ }
+
+ /**
+ * Assign value to field
+ * @return scalar|FALSE
+ * @param $key string
+ * @param $val scalar
+ **/
+ function set($key,$val) {
+ return ($key=='_id')?FALSE:($this->document[$key]=$val);
+ }
+
+ /**
+ * Retrieve value of field
+ * @return scalar|FALSE
+ * @param $key string
+ **/
+ function get($key) {
+ if ($key=='_id')
+ return $this->id;
+ if (array_key_exists($key,$this->document))
+ return $this->document[$key];
+ user_error(sprintf(self::E_Field,$key));
+ return FALSE;
+ }
+
+ /**
+ * Delete field
+ * @return NULL
+ * @param $key string
+ **/
+ function clear($key) {
+ if ($key!='_id')
+ unset($this->document[$key]);
+ }
+
+ /**
+ * Convert array to mapper object
+ * @return object
+ * @param $id string
+ * @param $row array
+ **/
+ protected function factory($id,$row) {
+ $mapper=clone($this);
+ $mapper->reset();
+ $mapper->id=$id;
+ foreach ($row as $field=>$val)
+ $mapper->document[$field]=$val;
+ $mapper->query=array(clone($mapper));
+ if (isset($mapper->trigger['load']))
+ \Base::instance()->call($mapper->trigger['load'],$mapper);
+ return $mapper;
+ }
+
+ /**
+ * Return fields of mapper object as an associative array
+ * @return array
+ * @param $obj object
+ **/
+ function cast($obj=NULL) {
+ if (!$obj)
+ $obj=$this;
+ return $obj->document+array('_id'=>$this->id);
+ }
+
+ /**
+ * Convert tokens in string expression to variable names
+ * @return string
+ * @param $str string
+ **/
+ function token($str) {
+ $self=$this;
+ $str=preg_replace_callback(
+ '/(?<!\w)@(\w(?:[\w\.\[\]])*)/',
+ function($token) use($self) {
+ // Convert from JS dot notation to PHP array notation
+ return '$'.preg_replace_callback(
+ '/(\.\w+)|\[((?:[^\[\]]*|(?R))*)\]/',
+ function($expr) use($self) {
+ $fw=\Base::instance();
+ return
+ '['.
+ ($expr[1]?
+ $fw->stringify(substr($expr[1],1)):
+ (preg_match('/^\w+/',
+ $mix=$self->token($expr[2]))?
+ $fw->stringify($mix):
+ $mix)).
+ ']';
+ },
+ $token[1]
+ );
+ },
+ $str
+ );
+ return trim($str);
+ }
+
+ /**
+ * Return records that match criteria
+ * @return array|FALSE
+ * @param $filter array
+ * @param $options array
+ * @param $ttl int
+ * @param $log bool
+ **/
+ function find($filter=NULL,array $options=NULL,$ttl=0,$log=TRUE) {
+ if (!$options)
+ $options=array();
+ $options+=array(
+ 'order'=>NULL,
+ 'limit'=>0,
+ 'offset'=>0
+ );
+ $fw=\Base::instance();
+ $cache=\Cache::instance();
+ $db=$this->db;
+ $now=microtime(TRUE);
+ $data=array();
+ if (!$fw->get('CACHE') || !$ttl || !($cached=$cache->exists(
+ $hash=$fw->hash($this->db->dir().
+ $fw->stringify(array($filter,$options))).'.jig',$data)) ||
+ $cached[0]+$ttl<microtime(TRUE)) {
+ $data=$db->read($this->file);
+ if (is_null($data))
+ return FALSE;
+ foreach ($data as $id=>&$doc) {
+ $doc['_id']=$id;
+ unset($doc);
+ }
+ if ($filter) {
+ if (!is_array($filter))
+ return FALSE;
+ // Normalize equality operator
+ $expr=preg_replace('/(?<=[^<>!=])=(?!=)/','==',$filter[0]);
+ // Prepare query arguments
+ $args=isset($filter[1]) && is_array($filter[1])?
+ $filter[1]:
+ array_slice($filter,1,NULL,TRUE);
+ $args=is_array($args)?$args:array(1=>$args);
+ $keys=$vals=array();
+ $tokens=array_slice(
+ token_get_all('<?php '.$this->token($expr)),1);
+ $data=array_filter($data,
+ function($_row) use($fw,$args,$tokens) {
+ $_expr='';
+ $ctr=0;
+ $named=FALSE;
+ foreach ($tokens as $token) {
+ if (is_string($token))
+ if ($token=='?') {
+ // Positional
+ $ctr++;
+ $key=$ctr;
+ }
+ else {
+ if ($token==':')
+ $named=TRUE;
+ else
+ $_expr.=$token;
+ continue;
+ }
+ elseif ($named &&
+ token_name($token[0])=='T_STRING') {
+ $key=':'.$token[1];
+ $named=FALSE;
+ }
+ else {
+ $_expr.=$token[1];
+ continue;
+ }
+ $_expr.=$fw->stringify(
+ is_string($args[$key])?
+ addcslashes($args[$key],'\''):
+ $args[$key]);
+ }
+ // Avoid conflict with user code
+ unset($fw,$tokens,$args,$ctr,$token,$key,$named);
+ extract($_row);
+ // Evaluate pseudo-SQL expression
+ return eval('return '.$_expr.';');
+ }
+ );
+ }
+ if (isset($options['order'])) {
+ $cols=$fw->split($options['order']);
+ uasort(
+ $data,
+ function($val1,$val2) use($cols) {
+ foreach ($cols as $col) {
+ $parts=explode(' ',$col,2);
+ $order=empty($parts[1])?
+ SORT_ASC:
+ constant($parts[1]);
+ $col=$parts[0];
+ if (!array_key_exists($col,$val1))
+ $val1[$col]=NULL;
+ if (!array_key_exists($col,$val2))
+ $val2[$col]=NULL;
+ list($v1,$v2)=array($val1[$col],$val2[$col]);
+ if ($out=strnatcmp($v1,$v2)*
+ (($order==SORT_ASC)*2-1))
+ return $out;
+ }
+ return 0;
+ }
+ );
+ }
+ $data=array_slice($data,
+ $options['offset'],$options['limit']?:NULL,TRUE);
+ if ($fw->get('CACHE') && $ttl)
+ // Save to cache backend
+ $cache->set($hash,$data,$ttl);
+ }
+ $out=array();
+ foreach ($data as $id=>&$doc) {
+ unset($doc['_id']);
+ $out[]=$this->factory($id,$doc);
+ unset($doc);
+ }
+ if ($log && isset($args)) {
+ if ($filter)
+ foreach ($args as $key=>$val) {
+ $vals[]=$fw->stringify(is_array($val)?$val[0]:$val);
+ $keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/';
+ }
+ $db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
+ $this->file.' [find] '.
+ ($filter?preg_replace($keys,$vals,$filter[0],1):''));
+ }
+ return $out;
+ }
+
+ /**
+ * Count records that match criteria
+ * @return int
+ * @param $filter array
+ * @param $ttl int
+ **/
+ function count($filter=NULL,$ttl=0) {
+ $now=microtime(TRUE);
+ $out=count($this->find($filter,NULL,$ttl,FALSE));
+ $this->db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
+ $this->file.' [count] '.($filter?json_encode($filter):''));
+ return $out;
+ }
+
+ /**
+ * Return record at specified offset using criteria of previous
+ * load() call and make it active
+ * @return array
+ * @param $ofs int
+ **/
+ function skip($ofs=1) {
+ $this->document=($out=parent::skip($ofs))?$out->document:array();
+ $this->id=$out?$out->id:NULL;
+ if ($this->document && isset($this->trigger['load']))
+ \Base::instance()->call($this->trigger['load'],$this);
+ return $out;
+ }
+
+ /**
+ * Insert new record
+ * @return array
+ **/
+ function insert() {
+ if ($this->id)
+ return $this->update();
+ $db=$this->db;
+ $now=microtime(TRUE);
+ while (($id=uniqid(NULL,TRUE)) &&
+ ($data=$db->read($this->file)) && isset($data[$id]) &&
+ !connection_aborted())
+ usleep(mt_rand(0,100));
+ $this->id=$id;
+ $data[$id]=$this->document;
+ $pkey=array('_id'=>$this->id);
+ if (isset($this->trigger['beforeinsert']))
+ \Base::instance()->call($this->trigger['beforeinsert'],
+ array($this,$pkey));
+ $db->write($this->file,$data);
+ $db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
+ $this->file.' [insert] '.json_encode($this->document));
+ if (isset($this->trigger['afterinsert']))
+ \Base::instance()->call($this->trigger['afterinsert'],
+ array($this,$pkey));
+ $this->load(array('@_id=?',$this->id));
+ return $this->document;
+ }
+
+ /**
+ * Update current record
+ * @return array
+ **/
+ function update() {
+ $db=$this->db;
+ $now=microtime(TRUE);
+ $data=$db->read($this->file);
+ $data[$this->id]=$this->document;
+ if (isset($this->trigger['beforeupdate']))
+ \Base::instance()->call($this->trigger['beforeupdate'],
+ array($this,array('_id'=>$this->id)));
+ $db->write($this->file,$data);
+ $db->jot('('.sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
+ $this->file.' [update] '.json_encode($this->document));
+ if (isset($this->trigger['afterupdate']))
+ \Base::instance()->call($this->trigger['afterupdate'],
+ array($this,array('_id'=>$this->id)));
+ return $this->document;
+ }
+
+ /**
+ * Delete current record
+ * @return bool
+ * @param $filter array
+ **/
+ function erase($filter=NULL) {
+ $db=$this->db;
+ $now=microtime(TRUE);
+ $data=$db->read($this->file);
+ if ($filter) {
+ foreach ($this->find($filter,NULL,FALSE) as $mapper)
+ if (!$mapper->erase())
+ return FALSE;
+ return TRUE;
+ }
+ elseif (isset($this->id)) {
+ $pkey=array('_id'=>$this->id);
+ unset($data[$this->id]);
+ parent::erase();
+ $this->skip(0);
+ }
+ else
+ return FALSE;
+ if (isset($this->trigger['beforeerase']))
+ \Base::instance()->call($this->trigger['beforeerase'],
+ array($this,$pkey));
+ $db->write($this->file,$data);
+ if ($filter) {
+ $args=isset($filter[1]) && is_array($filter[1])?
+ $filter[1]:
+ array_slice($filter,1,NULL,TRUE);
+ $args=is_array($args)?$args:array(1=>$args);
+ foreach ($args as $key=>$val) {
+ $vals[]=\Base::instance()->
+ stringify(is_array($val)?$val[0]:$val);
+ $keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/';
+ }
+ }
+ $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']))
+ \Base::instance()->call($this->trigger['aftererase'],
+ array($this,$pkey));
+ return TRUE;
+ }
+
+ /**
+ * Reset cursor
+ * @return NULL
+ **/
+ function reset() {
+ $this->id=NULL;
+ $this->document=array();
+ parent::reset();
+ }
+
+ /**
+ * Hydrate mapper object using hive array variable
+ * @return NULL
+ * @param $key string
+ * @param $func callback
+ **/
+ function copyfrom($key,$func=NULL) {
+ $var=\Base::instance()->get($key);
+ if ($func)
+ $var=$func($var);
+ foreach ($var as $key=>$val)
+ $this->document[$key]=$val;
+ }
+
+ /**
+ * Populate hive array variable with mapper fields
+ * @return NULL
+ * @param $key string
+ **/
+ function copyto($key) {
+ $var=&\Base::instance()->ref($key);
+ foreach ($this->document as $key=>$field)
+ $var[$key]=$field;
+ }
+
+ /**
+ * Return field names
+ * @return array
+ **/
+ function fields() {
+ return array_keys($this->document);
+ }
+
+ /**
+ * Instantiate class
+ * @return void
+ * @param $db object
+ * @param $file string
+ **/
+ function __construct(\DB\Jig $db,$file) {
+ $this->db=$db;
+ $this->file=$file;
+ $this->reset();
+ }
+
+}
diff --git a/management-interface/lib/db/jig/session.php b/management-interface/lib/db/jig/session.php
new file mode 100644
index 0000000..705cbce
--- /dev/null
+++ b/management-interface/lib/db/jig/session.php
@@ -0,0 +1,168 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB\Jig;
+
+//! Jig-managed session handler
+class Session extends Mapper {
+
+ protected
+ //! Session ID
+ $sid;
+
+ /**
+ * Open session
+ * @return TRUE
+ * @param $path string
+ * @param $name string
+ **/
+ function open($path,$name) {
+ return TRUE;
+ }
+
+ /**
+ * Close session
+ * @return TRUE
+ **/
+ function close() {
+ return TRUE;
+ }
+
+ /**
+ * Return session data in serialized format
+ * @return string|FALSE
+ * @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');
+ }
+
+ /**
+ * Write session data
+ * @return TRUE
+ * @param $id string
+ * @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('stamp',time());
+ $this->save();
+ return TRUE;
+ }
+
+ /**
+ * Destroy session
+ * @return TRUE
+ * @param $id string
+ **/
+ 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;
+ }
+
+ /**
+ * Garbage collector
+ * @return TRUE
+ * @param $max int
+ **/
+ function cleanup($max) {
+ $this->erase(array('@stamp+?<?',$max,time()));
+ return TRUE;
+ }
+
+ /**
+ * Return anti-CSRF token
+ * @return string|FALSE
+ **/
+ function csrf() {
+ return $this->dry()?FALSE:$this->get('csrf');
+ }
+
+ /**
+ * Return IP address
+ * @return string|FALSE
+ **/
+ function ip() {
+ return $this->dry()?FALSE:$this->get('ip');
+ }
+
+ /**
+ * Return Unix timestamp
+ * @return string|FALSE
+ **/
+ function stamp() {
+ return $this->dry()?FALSE:$this->get('stamp');
+ }
+
+ /**
+ * Return HTTP user agent
+ * @return string|FALSE
+ **/
+ function agent() {
+ return $this->dry()?FALSE:$this->get('agent');
+ }
+
+ /**
+ * Instantiate class
+ * @param $db object
+ * @param $table string
+ **/
+ function __construct(\DB\Jig $db,$table='sessions') {
+ parent::__construct($db,'sessions');
+ session_set_save_handler(
+ array($this,'open'),
+ array($this,'close'),
+ array($this,'read'),
+ array($this,'write'),
+ array($this,'destroy'),
+ 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'])) {
+ session_destroy();
+ $fw->error(403);
+ }
+ $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();
+ }
+ }
+
+}
diff --git a/management-interface/lib/db/mongo.php b/management-interface/lib/db/mongo.php
new file mode 100644
index 0000000..833f160
--- /dev/null
+++ b/management-interface/lib/db/mongo.php
@@ -0,0 +1,92 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB;
+
+//! MongoDB wrapper
+class Mongo extends \MongoDB {
+
+ //@{
+ const
+ E_Profiler='MongoDB profiler is disabled';
+ //@}
+
+ protected
+ //! UUID
+ $uuid,
+ //! Data source name
+ $dsn,
+ //! MongoDB log
+ $log;
+
+ /**
+ * Return data source name
+ * @return string
+ **/
+ function dsn() {
+ return $this->dsn;
+ }
+
+ /**
+ * Return UUID
+ * @return string
+ **/
+ function uuid() {
+ return $this->uuid;
+ }
+
+ /**
+ * Return MongoDB profiler results
+ * @return string
+ **/
+ function log() {
+ $cursor=$this->selectcollection('system.profile')->find();
+ foreach (iterator_to_array($cursor) as $frame)
+ if (!preg_match('/\.system\..+$/',$frame['ns']))
+ $this->log.=date('r',$frame['ts']->sec).' ('.
+ sprintf('%.1f',$frame['millis']).'ms) '.
+ $frame['ns'].' ['.$frame['op'].'] '.
+ (empty($frame['query'])?
+ '':json_encode($frame['query'])).
+ (empty($frame['command'])?
+ '':json_encode($frame['command'])).
+ PHP_EOL;
+ return $this->log;
+ }
+
+ /**
+ * Intercept native call to re-enable profiler
+ * @return int
+ **/
+ function drop() {
+ $out=parent::drop();
+ $this->setprofilinglevel(2);
+ return $out;
+ }
+
+ /**
+ * Instantiate class
+ * @param $dsn string
+ * @param $dbname string
+ * @param $options array
+ **/
+ function __construct($dsn,$dbname,array $options=NULL) {
+ $this->uuid=\Base::instance()->hash($this->dsn=$dsn);
+ $class=class_exists('\MongoClient')?'\MongoClient':'\Mongo';
+ parent::__construct(new $class($dsn,$options?:array()),$dbname);
+ $this->setprofilinglevel(2);
+ }
+
+}
diff --git a/management-interface/lib/db/mongo/mapper.php b/management-interface/lib/db/mongo/mapper.php
new file mode 100644
index 0000000..bcb7f6e
--- /dev/null
+++ b/management-interface/lib/db/mongo/mapper.php
@@ -0,0 +1,346 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB\Mongo;
+
+//! MongoDB mapper
+class Mapper extends \DB\Cursor {
+
+ protected
+ //! MongoDB wrapper
+ $db,
+ //! Mongo collection
+ $collection,
+ //! Mongo document
+ $document=array(),
+ //! Mongo cursor
+ $cursor;
+
+ /**
+ * Return database type
+ * @return string
+ **/
+ function dbtype() {
+ return 'Mongo';
+ }
+
+ /**
+ * Return TRUE if field is defined
+ * @return bool
+ * @param $key string
+ **/
+ function exists($key) {
+ return array_key_exists($key,$this->document);
+ }
+
+ /**
+ * Assign value to field
+ * @return scalar|FALSE
+ * @param $key string
+ * @param $val scalar
+ **/
+ function set($key,$val) {
+ return $this->document[$key]=$val;
+ }
+
+ /**
+ * Retrieve value of field
+ * @return scalar|FALSE
+ * @param $key string
+ **/
+ function get($key) {
+ if ($this->exists($key))
+ return $this->document[$key];
+ user_error(sprintf(self::E_Field,$key));
+ return FALSE;
+ }
+
+ /**
+ * Delete field
+ * @return NULL
+ * @param $key string
+ **/
+ function clear($key) {
+ unset($this->document[$key]);
+ }
+
+ /**
+ * Convert array to mapper object
+ * @return object
+ * @param $row array
+ **/
+ protected function factory($row) {
+ $mapper=clone($this);
+ $mapper->reset();
+ foreach ($row as $key=>$val)
+ $mapper->document[$key]=$val;
+ $mapper->query=array(clone($mapper));
+ if (isset($mapper->trigger['load']))
+ \Base::instance()->call($mapper->trigger['load'],$mapper);
+ return $mapper;
+ }
+
+ /**
+ * Return fields of mapper object as an associative array
+ * @return array
+ * @param $obj object
+ **/
+ function cast($obj=NULL) {
+ if (!$obj)
+ $obj=$this;
+ return $obj->document;
+ }
+
+ /**
+ * Build query and execute
+ * @return array
+ * @param $fields string
+ * @param $filter array
+ * @param $options array
+ * @param $ttl int
+ **/
+ function select($fields=NULL,$filter=NULL,array $options=NULL,$ttl=0) {
+ if (!$options)
+ $options=array();
+ $options+=array(
+ 'group'=>NULL,
+ 'order'=>NULL,
+ 'limit'=>0,
+ 'offset'=>0
+ );
+ $fw=\Base::instance();
+ $cache=\Cache::instance();
+ if (!($cached=$cache->exists($hash=$fw->hash($this->db->dsn().
+ $fw->stringify(array($fields,$filter,$options))).'.mongo',
+ $result)) || !$ttl || $cached[0]+$ttl<microtime(TRUE)) {
+ if ($options['group']) {
+ $grp=$this->collection->group(
+ $options['group']['keys'],
+ $options['group']['initial'],
+ $options['group']['reduce'],
+ array(
+ 'condition'=>$filter,
+ 'finalize'=>$options['group']['finalize']
+ )
+ );
+ $tmp=$this->db->selectcollection(
+ $fw->get('HOST').'.'.$fw->get('BASE').'.'.
+ uniqid(NULL,TRUE).'.tmp'
+ );
+ $tmp->batchinsert($grp['retval'],array('safe'=>TRUE));
+ $filter=array();
+ $collection=$tmp;
+ }
+ else {
+ $filter=$filter?:array();
+ $collection=$this->collection;
+ }
+ $this->cursor=$collection->find($filter,$fields?:array());
+ if ($options['order'])
+ $this->cursor=$this->cursor->sort($options['order']);
+ if ($options['limit'])
+ $this->cursor=$this->cursor->limit($options['limit']);
+ if ($options['offset'])
+ $this->cursor=$this->cursor->skip($options['offset']);
+ $result=array();
+ while ($this->cursor->hasnext())
+ $result[]=$this->cursor->getnext();
+ if ($options['group'])
+ $tmp->drop();
+ if ($fw->get('CACHE') && $ttl)
+ // Save to cache backend
+ $cache->set($hash,$result,$ttl);
+ }
+ $out=array();
+ foreach ($result as $doc)
+ $out[]=$this->factory($doc);
+ return $out;
+ }
+
+ /**
+ * Return records that match criteria
+ * @return array
+ * @param $filter array
+ * @param $options array
+ * @param $ttl int
+ **/
+ function find($filter=NULL,array $options=NULL,$ttl=0) {
+ if (!$options)
+ $options=array();
+ $options+=array(
+ 'group'=>NULL,
+ 'order'=>NULL,
+ 'limit'=>0,
+ 'offset'=>0
+ );
+ return $this->select(NULL,$filter,$options,$ttl);
+ }
+
+ /**
+ * Count records that match criteria
+ * @return int
+ * @param $filter array
+ * @param $ttl int
+ **/
+ function count($filter=NULL,$ttl=0) {
+ $fw=\Base::instance();
+ $cache=\Cache::instance();
+ if (!($cached=$cache->exists($hash=$fw->hash($fw->stringify(
+ array($filter))).'.mongo',$result)) || !$ttl ||
+ $cached[0]+$ttl<microtime(TRUE)) {
+ $result=$this->collection->count($filter);
+ if ($fw->get('CACHE') && $ttl)
+ // Save to cache backend
+ $cache->set($hash,$result,$ttl);
+ }
+ return $result;
+ }
+
+ /**
+ * Return record at specified offset using criteria of previous
+ * load() call and make it active
+ * @return array
+ * @param $ofs int
+ **/
+ function skip($ofs=1) {
+ $this->document=($out=parent::skip($ofs))?$out->document:array();
+ if ($this->document && isset($this->trigger['load']))
+ \Base::instance()->call($this->trigger['load'],$this);
+ return $out;
+ }
+
+ /**
+ * Insert new record
+ * @return array
+ **/
+ function insert() {
+ if (isset($this->document['_id']))
+ return $this->update();
+ if (isset($this->trigger['beforeinsert']))
+ \Base::instance()->call($this->trigger['beforeinsert'],
+ array($this,$pkey));
+ $this->collection->insert($this->document);
+ $pkey=array('_id'=>$this->document['_id']);
+ if (isset($this->trigger['afterinsert']))
+ \Base::instance()->call($this->trigger['afterinsert'],
+ array($this,$pkey));
+ $this->load(array('_id'=>$this->document['_id']));
+ return $this->document;
+ }
+
+ /**
+ * Update current record
+ * @return array
+ **/
+ function update() {
+ if (isset($this->trigger['beforeupdate']))
+ \Base::instance()->call($this->trigger['beforeupdate'],
+ array($this,$pkey));
+ $this->collection->update(
+ $pkey=array('_id'=>$this->document['_id']),
+ $this->document,
+ array('upsert'=>TRUE)
+ );
+ if (isset($this->trigger['afterupdate']))
+ \Base::instance()->call($this->trigger['afterupdate'],
+ array($this,$pkey));
+ return $this->document;
+ }
+
+ /**
+ * Delete current record
+ * @return bool
+ * @param $filter array
+ **/
+ function erase($filter=NULL) {
+ if ($filter)
+ return $this->collection->remove($filter);
+ $pkey=array('_id'=>$this->document['_id']);
+ if (isset($this->trigger['beforeerase']))
+ \Base::instance()->call($this->trigger['beforeerase'],
+ array($this,$pkey));
+ $result=$this->collection->
+ remove(array('_id'=>$this->document['_id']));
+ parent::erase();
+ $this->skip(0);
+ if (isset($this->trigger['aftererase']))
+ \Base::instance()->call($this->trigger['aftererase'],
+ array($this,$pkey));
+ return $result;
+ }
+
+ /**
+ * Reset cursor
+ * @return NULL
+ **/
+ function reset() {
+ $this->document=array();
+ parent::reset();
+ }
+
+ /**
+ * Hydrate mapper object using hive array variable
+ * @return NULL
+ * @param $key string
+ * @param $func callback
+ **/
+ function copyfrom($key,$func=NULL) {
+ $var=\Base::instance()->get($key);
+ if ($func)
+ $var=$func($var);
+ foreach ($var as $key=>$val)
+ $this->document[$key]=$val;
+ }
+
+ /**
+ * Populate hive array variable with mapper fields
+ * @return NULL
+ * @param $key string
+ **/
+ function copyto($key) {
+ $var=&\Base::instance()->ref($key);
+ foreach ($this->document as $key=>$field)
+ $var[$key]=$field;
+ }
+
+ /**
+ * Return field names
+ * @return array
+ **/
+ function fields() {
+ return array_keys($this->document);
+ }
+
+ /**
+ * Return the cursor from last query
+ * @return object|NULL
+ **/
+ function cursor() {
+ return $this->cursor;
+ }
+
+ /**
+ * Instantiate class
+ * @return void
+ * @param $db object
+ * @param $collection string
+ **/
+ function __construct(\DB\Mongo $db,$collection) {
+ $this->db=$db;
+ $this->collection=$db->{$collection};
+ $this->reset();
+ }
+
+}
diff --git a/management-interface/lib/db/mongo/session.php b/management-interface/lib/db/mongo/session.php
new file mode 100644
index 0000000..e3c6665
--- /dev/null
+++ b/management-interface/lib/db/mongo/session.php
@@ -0,0 +1,174 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB\Mongo;
+
+//! MongoDB-managed session handler
+class Session extends Mapper {
+
+ protected
+ //! Session ID
+ $sid;
+
+ /**
+ * Open session
+ * @return TRUE
+ * @param $path string
+ * @param $name string
+ **/
+ function open($path,$name) {
+ return TRUE;
+ }
+
+ /**
+ * Close session
+ * @return TRUE
+ **/
+ function close() {
+ return TRUE;
+ }
+
+ /**
+ * Return session data in serialized format
+ * @return string|FALSE
+ * @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');
+ }
+
+ /**
+ * Write session data
+ * @return TRUE
+ * @param $id string
+ * @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('stamp',time());
+ $this->save();
+ if (!$sent) {
+ if (isset($_COOKIE['_']))
+ setcookie('_','',strtotime('-1 year'));
+ call_user_func_array('setcookie',
+ array('_',$csrf)+$fw->get('JAR'));
+ }
+ return TRUE;
+ }
+
+ /**
+ * Destroy session
+ * @return TRUE
+ * @param $id string
+ **/
+ 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;
+ }
+
+ /**
+ * Garbage collector
+ * @return TRUE
+ * @param $max int
+ **/
+ function cleanup($max) {
+ $this->erase(array('$where'=>'this.stamp+'.$max.'<'.time()));
+ return TRUE;
+ }
+
+ /**
+ * Return anti-CSRF token
+ * @return string|FALSE
+ **/
+ function csrf() {
+ return $this->dry()?FALSE:$this->get('csrf');
+ }
+
+ /**
+ * Return IP address
+ * @return string|FALSE
+ **/
+ function ip() {
+ return $this->dry()?FALSE:$this->get('ip');
+ }
+
+ /**
+ * Return Unix timestamp
+ * @return string|FALSE
+ **/
+ function stamp() {
+ return $this->dry()?FALSE:$this->get('stamp');
+ }
+
+ /**
+ * Return HTTP user agent
+ * @return string|FALSE
+ **/
+ function agent() {
+ return $this->dry()?FALSE:$this->get('agent');
+ }
+
+ /**
+ * Instantiate class
+ * @param $db object
+ * @param $table string
+ **/
+ function __construct(\DB\Mongo $db,$table='sessions') {
+ parent::__construct($db,$table);
+ session_set_save_handler(
+ array($this,'open'),
+ array($this,'close'),
+ array($this,'read'),
+ array($this,'write'),
+ array($this,'destroy'),
+ 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'])) {
+ session_destroy();
+ $fw->error(403);
+ }
+ $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();
+ }
+ }
+
+}
diff --git a/management-interface/lib/db/sql.php b/management-interface/lib/db/sql.php
new file mode 100644
index 0000000..88e34dc
--- /dev/null
+++ b/management-interface/lib/db/sql.php
@@ -0,0 +1,403 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB;
+
+//! PDO wrapper
+class SQL extends \PDO {
+
+ protected
+ //! UUID
+ $uuid,
+ //! Data source name
+ $dsn,
+ //! Database engine
+ $engine,
+ //! Database name
+ $dbname,
+ //! Transaction flag
+ $trans=FALSE,
+ //! Number of rows affected by query
+ $rows=0,
+ //! SQL log
+ $log;
+
+ /**
+ * Begin SQL transaction
+ * @return bool
+ **/
+ function begin() {
+ $out=parent::begintransaction();
+ $this->trans=TRUE;
+ return $out;
+ }
+
+ /**
+ * Rollback SQL transaction
+ * @return bool
+ **/
+ function rollback() {
+ $out=parent::rollback();
+ $this->trans=FALSE;
+ return $out;
+ }
+
+ /**
+ * Commit SQL transaction
+ * @return bool
+ **/
+ function commit() {
+ $out=parent::commit();
+ $this->trans=FALSE;
+ return $out;
+ }
+
+ /**
+ * Map data type of argument to a PDO constant
+ * @return int
+ * @param $val scalar
+ **/
+ function type($val) {
+ switch (gettype($val)) {
+ case 'NULL':
+ return \PDO::PARAM_NULL;
+ case 'boolean':
+ return \PDO::PARAM_BOOL;
+ case 'integer':
+ return \PDO::PARAM_INT;
+ default:
+ return \PDO::PARAM_STR;
+ }
+ }
+
+ /**
+ * Cast value to PHP type
+ * @return scalar
+ * @param $type string
+ * @param $val scalar
+ **/
+ function value($type,$val) {
+ switch ($type) {
+ case \PDO::PARAM_NULL:
+ return (unset)$val;
+ case \PDO::PARAM_INT:
+ return (int)$val;
+ case \PDO::PARAM_BOOL:
+ return (bool)$val;
+ case \PDO::PARAM_STR:
+ return (string)$val;
+ }
+ }
+
+ /**
+ * Execute SQL statement(s)
+ * @return array|int|FALSE
+ * @param $cmds string|array
+ * @param $args string|array
+ * @param $ttl int
+ * @param $log bool
+ **/
+ function exec($cmds,$args=NULL,$ttl=0,$log=TRUE) {
+ $auto=FALSE;
+ if (is_null($args))
+ $args=array();
+ elseif (is_scalar($args))
+ $args=array(1=>$args);
+ if (is_array($cmds)) {
+ if (count($args)<($count=count($cmds)))
+ // Apply arguments to SQL commands
+ $args=array_fill(0,$count,$args);
+ if (!$this->trans) {
+ $this->begin();
+ $auto=TRUE;
+ }
+ }
+ else {
+ $cmds=array($cmds);
+ $args=array($args);
+ }
+ $fw=\Base::instance();
+ $cache=\Cache::instance();
+ $result=FALSE;
+ foreach (array_combine($cmds,$args) as $cmd=>$arg) {
+ if (!preg_replace('/(^\s+|[\s;]+$)/','',$cmd))
+ continue;
+ $now=microtime(TRUE);
+ $keys=$vals=array();
+ if ($fw->get('CACHE') && $ttl && ($cached=$cache->exists(
+ $hash=$fw->hash($this->dsn.$cmd.
+ $fw->stringify($arg)).'.sql',$result)) &&
+ $cached[0]+$ttl>microtime(TRUE)) {
+ foreach ($arg as $key=>$val) {
+ $vals[]=$fw->stringify(is_array($val)?$val[0]:$val);
+ $keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/';
+ }
+ }
+ elseif (is_object($query=$this->prepare($cmd))) {
+ foreach ($arg as $key=>$val) {
+ if (is_array($val)) {
+ // User-specified data type
+ $query->bindvalue($key,$val[0],$val[1]);
+ $vals[]=$fw->stringify($this->value($val[1],$val[0]));
+ }
+ else {
+ // Convert to PDO data type
+ $query->bindvalue($key,$val,
+ $type=$this->type($val));
+ $vals[]=$fw->stringify($this->value($type,$val));
+ }
+ $keys[]='/'.(is_numeric($key)?'\?':preg_quote($key)).'/';
+ }
+ $query->execute();
+ $error=$query->errorinfo();
+ if ($error[0]!=\PDO::ERR_NONE) {
+ // Statement-level error occurred
+ if ($this->trans)
+ $this->rollback();
+ user_error('PDOStatement: '.$error[2]);
+ }
+ if (preg_match('/^\s*'.
+ '(?:CALL|EXPLAIN|SELECT|PRAGMA|SHOW|RETURNING|EXEC)\b/is',
+ $cmd)) {
+ $result=$query->fetchall(\PDO::FETCH_ASSOC);
+ // Work around SQLite quote bug
+ if (preg_match('/sqlite2?/',$this->engine))
+ foreach ($result as $pos=>$rec) {
+ unset($result[$pos]);
+ $result[$pos]=array();
+ foreach ($rec as $key=>$val)
+ $result[$pos][trim($key,'\'"[]`')]=$val;
+ }
+ $this->rows=count($result);
+ if ($fw->get('CACHE') && $ttl)
+ // Save to cache backend
+ $cache->set($hash,$result,$ttl);
+ }
+ else
+ $this->rows=$result=$query->rowcount();
+ $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]);
+ }
+ }
+ if ($log)
+ $this->log.=date('r').' ('.
+ sprintf('%.1f',1e3*(microtime(TRUE)-$now)).'ms) '.
+ (empty($cached)?'':'[CACHED] ').
+ preg_replace($keys,$vals,$cmd,1).PHP_EOL;
+ }
+ if ($this->trans && $auto)
+ $this->commit();
+ return $result;
+ }
+
+ /**
+ * Return number of rows affected by last query
+ * @return int
+ **/
+ function count() {
+ return $this->rows;
+ }
+
+ /**
+ * Return SQL profiler results
+ * @return string
+ **/
+ function log() {
+ return $this->log;
+ }
+
+ /**
+ * Retrieve schema of SQL table
+ * @return array|FALSE
+ * @param $table string
+ * @param $fields array|string
+ * @param $ttl int
+ **/
+ function schema($table,$fields=NULL,$ttl=0) {
+ // Supported engines
+ $cmd=array(
+ 'sqlite2?'=>array(
+ 'PRAGMA table_info("'.$table.'");',
+ 'name','type','dflt_value','notnull',0,'pk',TRUE),
+ 'mysql'=>array(
+ 'SHOW columns FROM `'.$this->dbname.'`.`'.$table.'`;',
+ 'Field','Type','Default','Null','YES','Key','PRI'),
+ 'mssql|sqlsrv|sybase|dblib|pgsql|odbc'=>array(
+ '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 '.
+ 'LEFT OUTER JOIN '.
+ '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 '.
+ ($this->dbname?
+ ('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 '.
+ 'k.table_schema=t.table_schema '.
+ ($this->dbname?
+ ('AND k.table_catalog=t.table_catalog '):'').
+ 'WHERE '.
+ 'c.table_name='.$this->quote($table).' '.
+ ($this->dbname?
+ ('AND c.table_catalog='.
+ $this->quote($this->dbname)):'').
+ ';',
+ 'field','type','defval','nullable','YES','pkey','PRIMARY KEY'),
+ 'oci'=>array(
+ 'SELECT c.column_name AS field, '.
+ 'c.data_type AS type, '.
+ 'c.data_default AS defval, '.
+ 'c.nullable AS nullable, '.
+ '(SELECT t.constraint_type '.
+ 'FROM all_cons_columns acc '.
+ 'LEFT OUTER JOIN all_constraints t '.
+ 'ON acc.constraint_name=t.constraint_name '.
+ 'WHERE acc.table_name='.$this->quote($table).' '.
+ 'AND acc.column_name=c.column_name '.
+ 'AND constraint_type='.$this->quote('P').') AS pkey '.
+ 'FROM all_tab_cols c '.
+ 'WHERE c.table_name='.$this->quote($table),
+ 'FIELD','TYPE','DEFVAL','NULLABLE','Y','PKEY','P')
+ );
+ if (is_string($fields))
+ $fields=\Base::instance()->split($fields);
+ foreach ($cmd as $key=>$val)
+ if (preg_match('/'.$key.'/',$this->engine)) {
+ // Improve InnoDB performance on MySQL with
+ // SET GLOBAL innodb_stats_on_metadata=0;
+ // This requires SUPER privilege!
+ $rows=array();
+ foreach ($this->exec($val[0],NULL,$ttl) as $row) {
+ if (!$fields || in_array($row[$val[1]],$fields))
+ $rows[$row[$val[1]]]=array(
+ 'type'=>$row[$val[2]],
+ 'pdo_type'=>
+ preg_match('/int\b|int(?=eger)|bool/i',
+ $row[$val[2]],$parts)?
+ constant('\PDO::PARAM_'.
+ strtoupper($parts[0])):
+ \PDO::PARAM_STR,
+ 'default'=>$row[$val[3]],
+ 'nullable'=>$row[$val[4]]==$val[5],
+ 'pkey'=>$row[$val[6]]==$val[7]
+ );
+ }
+ return $rows;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Quote string
+ * @return string
+ * @param $val mixed
+ * @param $type int
+ **/
+ function quote($val,$type=\PDO::PARAM_STR) {
+ return $this->engine=='odbc'?
+ (is_string($val)?
+ \Base::instance()->stringify(str_replace('\'','\'\'',$val)):
+ $val):
+ parent::quote($val,$type);
+ }
+
+ /**
+ * Return UUID
+ * @return string
+ **/
+ function uuid() {
+ return $this->uuid;
+ }
+
+ /**
+ * Return database engine
+ * @return string
+ **/
+ function driver() {
+ return $this->engine;
+ }
+
+ /**
+ * Return server version
+ * @return string
+ **/
+ function version() {
+ return parent::getattribute(parent::ATTR_SERVER_VERSION);
+ }
+
+ /**
+ * Return database name
+ * @return string
+ **/
+ function name() {
+ return $this->dbname;
+ }
+
+ /**
+ * Return quoted identifier name
+ * @return string
+ * @param $key
+ **/
+ function quotekey($key) {
+ if ($this->engine=='mysql')
+ $key="`".implode('`.`',explode('.',$key))."`";
+ elseif (preg_match('/sybase|dblib/',$this->engine))
+ $key="'".implode("'.'",explode('.',$key))."'";
+ elseif (preg_match('/sqlite2?|pgsql|oci/',$this->engine))
+ $key='"'.implode('"."',explode('.',$key)).'"';
+ elseif (preg_match('/mssql|sqlsrv|odbc/',$this->engine))
+ $key="[".implode('].[',explode('.',$key))."]";
+ return $key;
+ }
+
+ /**
+ * Instantiate class
+ * @param $dsn string
+ * @param $user string
+ * @param $pw string
+ * @param $options array
+ **/
+ function __construct($dsn,$user=NULL,$pw=NULL,array $options=NULL) {
+ $fw=\Base::instance();
+ $this->uuid=$fw->hash($this->dsn=$dsn);
+ if (preg_match('/^.+?(?:dbname|database)=(.+?)(?=;|$)/i',$dsn,$parts))
+ $this->dbname=$parts[1];
+ if (!$options)
+ $options=array();
+ if (isset($parts[0]) && strstr($parts[0],':',TRUE)=='mysql')
+ $options+=array(\PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES '.
+ strtolower(str_replace('-','',$fw->get('ENCODING'))).';');
+ parent::__construct($dsn,$user,$pw,$options);
+ $this->engine=parent::getattribute(parent::ATTR_DRIVER_NAME);
+ }
+
+}
diff --git a/management-interface/lib/db/sql/mapper.php b/management-interface/lib/db/sql/mapper.php
new file mode 100644
index 0000000..6af4675
--- /dev/null
+++ b/management-interface/lib/db/sql/mapper.php
@@ -0,0 +1,552 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB\SQL;
+
+//! SQL data mapper
+class Mapper extends \DB\Cursor {
+
+ //@{ Error messages
+ const
+ E_Adhoc='Unable to process ad hoc field %s';
+ //@}
+
+ protected
+ //! PDO wrapper
+ $db,
+ //! Database engine
+ $engine,
+ //! SQL table
+ $source,
+ //! SQL table (quoted)
+ $table,
+ //! Last insert ID
+ $_id,
+ //! Defined fields
+ $fields,
+ //! Adhoc fields
+ $adhoc=array();
+
+ /**
+ * Return database type
+ * @return string
+ **/
+ function dbtype() {
+ return 'SQL';
+ }
+
+ /**
+ * Return TRUE if field is defined
+ * @return bool
+ * @param $key string
+ **/
+ function exists($key) {
+ return array_key_exists($key,$this->fields+$this->adhoc);
+ }
+
+ /**
+ * Assign value to field
+ * @return scalar
+ * @param $key string
+ * @param $val scalar
+ **/
+ function set($key,$val) {
+ if (array_key_exists($key,$this->fields)) {
+ $val=is_null($val) && $this->fields[$key]['nullable']?
+ NULL:$this->db->value($this->fields[$key]['pdo_type'],$val);
+ if ($this->fields[$key]['value']!==$val ||
+ $this->fields[$key]['default']!==$val && is_null($val))
+ $this->fields[$key]['changed']=TRUE;
+ return $this->fields[$key]['value']=$val;
+ }
+ // Parenthesize expression in case it's a subquery
+ $this->adhoc[$key]=array('expr'=>'('.$val.')','value'=>NULL);
+ return $val;
+ }
+
+ /**
+ * Retrieve value of field
+ * @return scalar
+ * @param $key string
+ **/
+ function get($key) {
+ if ($key=='_id')
+ return $this->_id;
+ elseif (array_key_exists($key,$this->fields))
+ return $this->fields[$key]['value'];
+ elseif (array_key_exists($key,$this->adhoc))
+ return $this->adhoc[$key]['value'];
+ user_error(sprintf(self::E_Field,$key));
+ }
+
+ /**
+ * Clear value of field
+ * @return NULL
+ * @param $key string
+ **/
+ function clear($key) {
+ if (array_key_exists($key,$this->adhoc))
+ unset($this->adhoc[$key]);
+ }
+
+ /**
+ * Get PHP type equivalent of PDO constant
+ * @return string
+ * @param $pdo string
+ **/
+ 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';
+ }
+ }
+
+ /**
+ * Convert array to mapper object
+ * @return object
+ * @param $row array
+ **/
+ protected function factory($row) {
+ $mapper=clone($this);
+ $mapper->reset();
+ foreach ($row as $key=>$val) {
+ if (array_key_exists($key,$this->fields))
+ $var='fields';
+ elseif (array_key_exists($key,$this->adhoc))
+ $var='adhoc';
+ else
+ continue;
+ $mapper->{$var}[$key]['value']=$val;
+ if ($var=='fields' && $mapper->{$var}[$key]['pkey'])
+ $mapper->{$var}[$key]['previous']=$val;
+ }
+ $mapper->query=array(clone($mapper));
+ if (isset($mapper->trigger['load']))
+ \Base::instance()->call($mapper->trigger['load'],$mapper);
+ return $mapper;
+ }
+
+ /**
+ * Return fields of mapper object as an associative array
+ * @return array
+ * @param $obj object
+ **/
+ function cast($obj=NULL) {
+ if (!$obj)
+ $obj=$this;
+ return array_map(
+ function($row) {
+ return $row['value'];
+ },
+ $obj->fields+$obj->adhoc
+ );
+ }
+
+ /**
+ * Build query string and execute
+ * @return array
+ * @param $fields string
+ * @param $filter string|array
+ * @param $options array
+ * @param $ttl int
+ **/
+ function select($fields,$filter=NULL,array $options=NULL,$ttl=0) {
+ if (!$options)
+ $options=array();
+ $options+=array(
+ 'group'=>NULL,
+ 'order'=>NULL,
+ 'limit'=>0,
+ 'offset'=>0
+ );
+ $sql='SELECT '.$fields.' FROM '.$this->table;
+ $args=array();
+ if ($filter) {
+ if (is_array($filter)) {
+ $args=isset($filter[1]) && is_array($filter[1])?
+ $filter[1]:
+ array_slice($filter,1,NULL,TRUE);
+ $args=is_array($args)?$args:array(1=>$args);
+ list($filter)=$filter;
+ }
+ $sql.=' WHERE '.$filter;
+ }
+ $db=$this->db;
+ if ($options['group'])
+ $sql.=' GROUP BY '.implode(',',array_map(
+ function($str) use($db) {
+ return preg_match('/^(\w+)(?:\h+HAVING|\h*(?:,|$))/i',
+ $str,$parts)?
+ ($db->quotekey($parts[1]).
+ (isset($parts[2])?(' '.$parts[2]):'')):$str;
+ },
+ explode(',',$options['group'])));
+ if ($options['order']) {
+ $sql.=' ORDER BY '.implode(',',array_map(
+ function($str) use($db) {
+ return preg_match('/^(\w+)(?:\h+(ASC|DESC))?\h*(?:,|$)/i',
+ $str,$parts)?
+ ($db->quotekey($parts[1]).
+ (isset($parts[2])?(' '.$parts[2]):'')):$str;
+ },
+ explode(',',$options['order'])));
+ }
+ if ($options['limit'])
+ $sql.=' LIMIT '.(int)$options['limit'];
+ if ($options['offset'])
+ $sql.=' OFFSET '.(int)$options['offset'];
+ $result=$this->db->exec($sql,$args,$ttl);
+ $out=array();
+ foreach ($result as &$row) {
+ foreach ($row as $field=>&$val) {
+ if (array_key_exists($field,$this->fields)) {
+ if (!is_null($val) || !$this->fields[$field]['nullable'])
+ $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);
+ unset($row);
+ }
+ return $out;
+ }
+
+ /**
+ * Return records that match criteria
+ * @return array
+ * @param $filter string|array
+ * @param $options array
+ * @param $ttl int
+ **/
+ function find($filter=NULL,array $options=NULL,$ttl=0) {
+ if (!$options)
+ $options=array();
+ $options+=array(
+ 'group'=>NULL,
+ 'order'=>NULL,
+ 'limit'=>0,
+ 'offset'=>0
+ );
+ $adhoc='';
+ foreach ($this->adhoc as $key=>$field)
+ $adhoc.=','.$field['expr'].' AS '.$this->db->quotekey($key);
+ return $this->select(implode(',',
+ array_map(array($this->db,'quotekey'),array_keys($this->fields))).
+ $adhoc,$filter,$options,$ttl);
+ }
+
+ /**
+ * Count records that match criteria
+ * @return int
+ * @param $filter string|array
+ * @param $ttl int
+ **/
+ function count($filter=NULL,$ttl=0) {
+ $sql='SELECT COUNT(*) AS '.
+ $this->db->quotekey('rows').' FROM '.$this->table;
+ $args=array();
+ if ($filter) {
+ if (is_array($filter)) {
+ $args=isset($filter[1]) && is_array($filter[1])?
+ $filter[1]:
+ array_slice($filter,1,NULL,TRUE);
+ $args=is_array($args)?$args:array(1=>$args);
+ list($filter)=$filter;
+ }
+ $sql.=' WHERE '.$filter;
+ }
+ $result=$this->db->exec($sql,$args,$ttl);
+ return $result[0]['rows'];
+ }
+
+ /**
+ * Return record at specified offset using same criteria as
+ * previous load() call and make it active
+ * @return array
+ * @param $ofs int
+ **/
+ function skip($ofs=1) {
+ $out=parent::skip($ofs);
+ $dry=$this->dry();
+ foreach ($this->fields as $key=>&$field) {
+ $field['value']=$dry?NULL:$out->fields[$key]['value'];
+ $field['changed']=FALSE;
+ if ($field['pkey'])
+ $field['previous']=$dry?NULL:$out->fields[$key]['value'];
+ unset($field);
+ }
+ foreach ($this->adhoc as $key=>&$field) {
+ $field['value']=$dry?NULL:$out->adhoc[$key]['value'];
+ unset($field);
+ }
+ if (isset($this->trigger['load']))
+ \Base::instance()->call($this->trigger['load'],$this);
+ return $out;
+ }
+
+ /**
+ * Insert new record
+ * @return object
+ **/
+ function insert() {
+ $args=array();
+ $ctr=0;
+ $fields='';
+ $values='';
+ $filter='';
+ $pkeys=array();
+ $nkeys=array();
+ $ckeys=array();
+ $inc=NULL;
+ foreach ($this->fields as $key=>$field)
+ if ($field['pkey'])
+ $pkeys[$key]=$field['previous'];
+ if (isset($this->trigger['beforeinsert']))
+ \Base::instance()->call($this->trigger['beforeinsert'],
+ array($this,$pkeys));
+ foreach ($this->fields as $key=>&$field) {
+ if ($field['pkey']) {
+ $field['previous']=$field['value'];
+ if (!$inc && $field['pdo_type']==\PDO::PARAM_INT &&
+ empty($field['value']) && !$field['nullable'])
+ $inc=$key;
+ $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
+ $nkeys[$ctr+1]=array($field['value'],$field['pdo_type']);
+ }
+ if ($field['changed'] && $key!=$inc) {
+ $fields.=($ctr?',':'').$this->db->quotekey($key);
+ $values.=($ctr?',':'').'?';
+ $args[$ctr+1]=array($field['value'],$field['pdo_type']);
+ $ctr++;
+ $ckeys[]=$key;
+ }
+ $field['changed']=FALSE;
+ unset($field);
+ }
+ if ($fields) {
+ $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
+ );
+ $seq=NULL;
+ if ($this->engine=='pgsql') {
+ $names=array_keys($pkeys);
+ $seq=$this->source.'_'.end($names).'_seq';
+ }
+ if ($this->engine!='oci')
+ $this->_id=$this->db->lastinsertid($seq);
+ // Reload to obtain default and auto-increment field values
+ $this->load($inc?
+ array($inc.'=?',$this->db->value(
+ $this->fields[$inc]['pdo_type'],$this->_id)):
+ array($filter,$nkeys));
+ if (isset($this->trigger['afterinsert']))
+ \Base::instance()->call($this->trigger['afterinsert'],
+ array($this,$pkeys));
+ }
+ return $this;
+ }
+
+ /**
+ * Update current record
+ * @return object
+ **/
+ function update() {
+ $args=array();
+ $ctr=0;
+ $pairs='';
+ $filter='';
+ $pkeys=array();
+ foreach ($this->fields as $key=>$field)
+ if ($field['pkey'])
+ $pkeys[$key]=$field['previous'];
+ if (isset($this->trigger['beforeupdate']))
+ \Base::instance()->call($this->trigger['beforeupdate'],
+ array($this,$pkeys));
+ foreach ($this->fields as $key=>$field)
+ if ($field['changed']) {
+ $pairs.=($pairs?',':'').$this->db->quotekey($key).'=?';
+ $args[$ctr+1]=array($field['value'],$field['pdo_type']);
+ $ctr++;
+ }
+ foreach ($this->fields as $key=>$field)
+ if ($field['pkey']) {
+ $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
+ $args[$ctr+1]=array($field['previous'],$field['pdo_type']);
+ $ctr++;
+ }
+ if ($pairs) {
+ $sql='UPDATE '.$this->table.' SET '.$pairs;
+ if ($filter)
+ $sql.=' WHERE '.$filter;
+ $this->db->exec($sql,$args);
+ if (isset($this->trigger['afterupdate']))
+ \Base::instance()->call($this->trigger['afterupdate'],
+ array($this,$pkeys));
+ }
+ return $this;
+ }
+
+ /**
+ * Delete current record
+ * @return int
+ * @param $filter string|array
+ **/
+ function erase($filter=NULL) {
+ if ($filter) {
+ $args=array();
+ if (is_array($filter)) {
+ $args=isset($filter[1]) && is_array($filter[1])?
+ $filter[1]:
+ array_slice($filter,1,NULL,TRUE);
+ $args=is_array($args)?$args:array(1=>$args);
+ list($filter)=$filter;
+ }
+ return $this->db->
+ exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args);
+ }
+ $args=array();
+ $ctr=0;
+ $filter='';
+ $pkeys=array();
+ foreach ($this->fields as $key=>&$field) {
+ if ($field['pkey']) {
+ $filter.=($filter?' AND ':'').$this->db->quotekey($key).'=?';
+ $args[$ctr+1]=array($field['previous'],$field['pdo_type']);
+ $pkeys[$key]=$field['previous'];
+ $ctr++;
+ }
+ $field['value']=NULL;
+ $field['changed']=(bool)$field['default'];
+ if ($field['pkey'])
+ $field['previous']=NULL;
+ unset($field);
+ }
+ foreach ($this->adhoc as &$field) {
+ $field['value']=NULL;
+ unset($field);
+ }
+ parent::erase();
+ $this->skip(0);
+ if (isset($this->trigger['beforeerase']))
+ \Base::instance()->call($this->trigger['beforeerase'],
+ array($this,$pkeys));
+ $out=$this->db->
+ exec('DELETE FROM '.$this->table.' WHERE '.$filter.';',$args);
+ if (isset($this->trigger['aftererase']))
+ \Base::instance()->call($this->trigger['aftererase'],
+ array($this,$pkeys));
+ return $out;
+ }
+
+ /**
+ * Reset cursor
+ * @return NULL
+ **/
+ function reset() {
+ foreach ($this->fields as &$field) {
+ $field['value']=NULL;
+ $field['changed']=FALSE;
+ if ($field['pkey'])
+ $field['previous']=NULL;
+ unset($field);
+ }
+ foreach ($this->adhoc as &$field) {
+ $field['value']=NULL;
+ unset($field);
+ }
+ parent::reset();
+ }
+
+ /**
+ * Hydrate mapper object using hive array variable
+ * @return NULL
+ * @param $key string
+ * @param $func callback
+ **/
+ function copyfrom($key,$func=NULL) {
+ $var=\Base::instance()->get($key);
+ if ($func)
+ $var=$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);
+ }
+ }
+
+ /**
+ * Populate hive array variable with mapper fields
+ * @return NULL
+ * @param $key string
+ **/
+ function copyto($key) {
+ $var=&\Base::instance()->ref($key);
+ foreach ($this->fields+$this->adhoc as $key=>$field)
+ $var[$key]=$field['value'];
+ }
+
+ /**
+ * Return schema
+ * @return array
+ **/
+ function schema() {
+ return $this->fields;
+ }
+
+ /**
+ * Return field names
+ * @return array
+ * @param $adhoc bool
+ **/
+ function fields($adhoc=TRUE) {
+ return array_keys($this->fields+($adhoc?$this->adhoc:array()));
+ }
+
+ /**
+ * Instantiate class
+ * @param $db object
+ * @param $table string
+ * @param $fields array|string
+ * @param $ttl int
+ **/
+ function __construct(\DB\SQL $db,$table,$fields=NULL,$ttl=60) {
+ $this->db=$db;
+ $this->engine=$db->driver();
+ if ($this->engine=='oci')
+ $table=strtoupper($table);
+ $this->source=$table;
+ $this->table=$this->db->quotekey($table);
+ $this->fields=$db->schema($table,$fields,$ttl);
+ $this->reset();
+ }
+
+}
diff --git a/management-interface/lib/db/sql/session.php b/management-interface/lib/db/sql/session.php
new file mode 100644
index 0000000..48050ec
--- /dev/null
+++ b/management-interface/lib/db/sql/session.php
@@ -0,0 +1,187 @@
+<?php
+
+/*
+ Copyright (c) 2009-2014 F3::Factory/Bong Cosca, All rights reserved.
+
+ This file is part of the Fat-Free Framework (http://fatfree.sf.net).
+
+ THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF
+ ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
+ PURPOSE.
+
+ Please see the license.txt file for more information.
+*/
+
+namespace DB\SQL;
+
+//! SQL-managed session handler
+class Session extends Mapper {
+
+ protected
+ //! Session ID
+ $sid;
+
+ /**
+ * Open session
+ * @return TRUE
+ * @param $path string
+ * @param $name string
+ **/
+ function open($path,$name) {
+ return TRUE;
+ }
+
+ /**
+ * Close session
+ * @return TRUE
+ **/
+ function close() {
+ return TRUE;
+ }
+
+ /**
+ * Return session data in serialized format
+ * @return string|FALSE
+ * @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');
+ }
+
+ /**
+ * Write session data
+ * @return TRUE
+ * @param $id string
+ * @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('stamp',time());
+ $this->save();
+ return TRUE;
+ }
+
+ /**
+ * Destroy session
+ * @return TRUE
+ * @param $id string
+ **/
+ 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;
+ }
+
+ /**
+ * Garbage collector
+ * @return TRUE
+ * @param $max int
+ **/
+ function cleanup($max) {
+ $this->erase(array('stamp+?<?',$max,time()));
+ return TRUE;
+ }
+
+ /**
+ * Return anti-CSRF token
+ * @return string|FALSE
+ **/
+ function csrf() {
+ return $this->dry()?FALSE:$this->get('csrf');
+ }
+
+ /**
+ * Return IP address
+ * @return string|FALSE
+ **/
+ function ip() {
+ return $this->dry()?FALSE:$this->get('ip');
+ }
+
+ /**
+ * Return Unix timestamp
+ * @return string|FALSE
+ **/
+ function stamp() {
+ return $this->dry()?FALSE:$this->get('stamp');
+ }
+
+ /**
+ * Return HTTP user agent
+ * @return string|FALSE
+ **/
+ function agent() {
+ return $this->dry()?FALSE:$this->get('agent');
+ }
+
+ /**
+ * Instantiate class
+ * @param $db object
+ * @param $table string
+ * @param $force bool
+ **/
+ function __construct(\DB\SQL $db,$table='sessions',$force=TRUE) {
+ if ($force)
+ $db->exec(
+ (preg_match('/mssql|sqlsrv|sybase/',$db->driver())?
+ ('IF NOT EXISTS (SELECT * FROM sysobjects WHERE '.
+ 'name='.$db->quote($table).' AND xtype=\'U\') '.
+ 'CREATE TABLE dbo.'):
+ ('CREATE TABLE IF NOT EXISTS '.
+ (($name=$db->name())?($name.'.'):''))).
+ $table.' ('.
+ 'session_id VARCHAR(40),'.
+ 'data TEXT,'.
+ 'csrf TEXT,'.
+ 'ip VARCHAR(40),'.
+ 'agent VARCHAR(255),'.
+ 'stamp INTEGER,'.
+ 'PRIMARY KEY(session_id)'.
+ ');'
+ );
+ parent::__construct($db,$table);
+ session_set_save_handler(
+ array($this,'open'),
+ array($this,'close'),
+ array($this,'read'),
+ array($this,'write'),
+ array($this,'destroy'),
+ 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'])) {
+ session_destroy();
+ $fw->error(403);
+ }
+ $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();
+ }
+ }
+
+}