todo_api/api.php
2017-02-12 19:46:13 +01:00

2394 lines
76 KiB
PHP

<?php
//var_dump($_SERVER['REQUEST_METHOD'],$_SERVER['PATH_INFO']); die();
interface DatabaseInterface {
public function getSql($name);
public function connect($hostname,$username,$password,$database,$port,$socket,$charset);
public function query($sql,$params=array());
public function fetchAssoc($result,$fields=false);
public function fetchRow($result,$fields=false);
public function insertId($result);
public function affectedRows($result);
public function close($result);
public function fetchFields($table);
public function addLimitToSql($sql,$limit,$offset);
public function likeEscape($string);
public function isNumericType($field);
public function isBinaryType($field);
public function isGeometryType($field);
public function getDefaultCharset();
public function beginTransaction();
public function commitTransaction();
public function rollbackTransaction();
}
class MySQL implements DatabaseInterface {
protected $db;
protected $queries;
public function __construct() {
$this->queries = array(
'list_tables'=>'SELECT
"TABLE_NAME","TABLE_COMMENT"
FROM
"INFORMATION_SCHEMA"."TABLES"
WHERE
"TABLE_SCHEMA" = ?',
'reflect_table'=>'SELECT
"TABLE_NAME"
FROM
"INFORMATION_SCHEMA"."TABLES"
WHERE
"TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
"TABLE_SCHEMA" = ?',
'reflect_pk'=>'SELECT
"COLUMN_NAME"
FROM
"INFORMATION_SCHEMA"."COLUMNS"
WHERE
"COLUMN_KEY" = \'PRI\' AND
"TABLE_NAME" = ? AND
"TABLE_SCHEMA" = ?',
'reflect_belongs_to'=>'SELECT
"TABLE_NAME","COLUMN_NAME",
"REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME"
FROM
"INFORMATION_SCHEMA"."KEY_COLUMN_USAGE"
WHERE
"TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
"REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND
"TABLE_SCHEMA" = ? AND
"REFERENCED_TABLE_SCHEMA" = ?',
'reflect_has_many'=>'SELECT
"TABLE_NAME","COLUMN_NAME",
"REFERENCED_TABLE_NAME","REFERENCED_COLUMN_NAME"
FROM
"INFORMATION_SCHEMA"."KEY_COLUMN_USAGE"
WHERE
"TABLE_NAME" COLLATE \'utf8_bin\' IN ? AND
"REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
"TABLE_SCHEMA" = ? AND
"REFERENCED_TABLE_SCHEMA" = ?',
'reflect_habtm'=>'SELECT
k1."TABLE_NAME", k1."COLUMN_NAME",
k1."REFERENCED_TABLE_NAME", k1."REFERENCED_COLUMN_NAME",
k2."TABLE_NAME", k2."COLUMN_NAME",
k2."REFERENCED_TABLE_NAME", k2."REFERENCED_COLUMN_NAME"
FROM
"INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k1,
"INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" k2
WHERE
k1."TABLE_SCHEMA" = ? AND
k2."TABLE_SCHEMA" = ? AND
k1."REFERENCED_TABLE_SCHEMA" = ? AND
k2."REFERENCED_TABLE_SCHEMA" = ? AND
k1."TABLE_NAME" COLLATE \'utf8_bin\' = k2."TABLE_NAME" COLLATE \'utf8_bin\' AND
k1."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' = ? AND
k2."REFERENCED_TABLE_NAME" COLLATE \'utf8_bin\' IN ?'
);
}
public function getSql($name) {
return isset($this->queries[$name])?$this->queries[$name]:false;
}
public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
$db = mysqli_init();
if (defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
mysqli_options($db,MYSQLI_OPT_INT_AND_FLOAT_NATIVE,true);
}
$success = mysqli_real_connect($db,$hostname,$username,$password,$database,$port,$socket,MYSQLI_CLIENT_FOUND_ROWS);
if (!$success) {
throw new \Exception('Connect failed. '.mysqli_connect_error());
}
if (!mysqli_set_charset($db,$charset)) {
throw new \Exception('Error setting charset. '.mysqli_error($db));
}
if (!mysqli_query($db,'SET SESSION sql_mode = \'ANSI_QUOTES\';')) {
throw new \Exception('Error setting ANSI quotes. '.mysqli_error($db));
}
$this->db = $db;
}
public function query($sql,$params=array()) {
$db = $this->db;
$sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
$param = array_shift($params);
if ($matches[0]=='!') {
$key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
if (is_object($param) && $param->type=='base64') {
return "TO_BASE64(\"$key\") as \"$key\"";
}
if (is_object($param) && $param->type=='wkt') {
return "ST_AsText(\"$key\") as \"$key\"";
}
return '"'.$key.'"';
} else {
if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
return "'".mysqli_real_escape_string($db,$v)."'";
},$param)).')';
if (is_object($param) && $param->type=='base64') {
return "x'".bin2hex(base64_decode($param->value))."'";
}
if (is_object($param) && $param->type=='wkt') {
return "ST_GeomFromText('".mysqli_real_escape_string($db,$param->value)."')";
}
if ($param===null) return 'NULL';
return "'".mysqli_real_escape_string($db,$param)."'";
}
}, $sql);
//if (!strpos($sql,'INFORMATION_SCHEMA')) echo "\n$sql\n";
//if (!strpos($sql,'INFORMATION_SCHEMA')) file_put_contents('log.txt',"\n$sql\n",FILE_APPEND);
return mysqli_query($db,$sql);
}
protected function convertFloatAndInt($result,&$values,&$fields) {
array_walk($values, function(&$v,$i) use ($result,$fields){
if (is_string($v) && $this->isNumericType($fields[$i])) {
$v+=0;
}
});
}
public function fetchAssoc($result,$fields=false) {
$values = mysqli_fetch_assoc($result);
if ($values && $fields && !defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
$this->convertFloatAndInt($result,$values,$fields);
}
return $values;
}
public function fetchRow($result,$fields=false) {
$values = mysqli_fetch_row($result);
if ($values && $fields && !defined('MYSQLI_OPT_INT_AND_FLOAT_NATIVE')) {
$fields = array_values($fields);
$this->convertFloatAndInt($result,$values,$fields);
}
return $values;
}
public function insertId($result) {
return mysqli_insert_id($this->db);
}
public function affectedRows($result) {
return mysqli_affected_rows($this->db);
}
public function close($result) {
return mysqli_free_result($result);
}
public function fetchFields($table) {
$result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table));
return mysqli_fetch_fields($result);
}
public function addLimitToSql($sql,$limit,$offset) {
return "$sql LIMIT $limit OFFSET $offset";
}
public function likeEscape($string) {
return addcslashes($string,'%_');
}
public function convertFilter($field, $comparator, $value) {
return false;
}
public function isNumericType($field) {
return in_array($field->type,array(1,2,3,4,5,6,8,9));
}
public function isBinaryType($field) {
//echo "$field->name: $field->type ($field->flags)\n";
return (($field->flags & 128) && ($field->type>=249) && ($field->type<=252));
}
public function isGeometryType($field) {
return ($field->type==255);
}
public function getDefaultCharset() {
return 'utf8';
}
public function beginTransaction() {
mysqli_query($this->db,'BEGIN');
//return mysqli_begin_transaction($this->db);
}
public function commitTransaction() {
mysqli_query($this->db,'COMMIT');
//return mysqli_commit($this->db);
}
public function rollbackTransaction() {
mysqli_query($this->db,'ROLLBACK');
//return mysqli_rollback($this->db);
}
}
class PostgreSQL implements DatabaseInterface {
protected $db;
protected $queries;
public function __construct() {
$this->queries = array(
'list_tables'=>'select
"table_name","table_comment"
from
"information_schema"."tables"
where
"table_catalog" = ?',
'reflect_table'=>'select
"table_name"
from
"information_schema"."tables"
where
"table_name" like ? and
"table_catalog" = ?',
'reflect_pk'=>'select
"column_name"
from
"information_schema"."table_constraints" tc,
"information_schema"."key_column_usage" ku
where
tc."constraint_type" = \'PRIMARY KEY\' and
tc."constraint_name" = ku."constraint_name" and
ku."table_name" = ? and
ku."table_catalog" = ?',
'reflect_belongs_to'=>'select
cu1."table_name",cu1."column_name",
cu2."table_name",cu2."column_name"
from
"information_schema".referential_constraints rc,
"information_schema".key_column_usage cu1,
"information_schema".key_column_usage cu2
where
cu1."constraint_name" = rc."constraint_name" and
cu2."constraint_name" = rc."unique_constraint_name" and
cu1."table_name" = ? and
cu2."table_name" in ? and
cu1."table_catalog" = ? and
cu2."table_catalog" = ?',
'reflect_has_many'=>'select
cu1."table_name",cu1."column_name",
cu2."table_name",cu2."column_name"
from
"information_schema".referential_constraints rc,
"information_schema".key_column_usage cu1,
"information_schema".key_column_usage cu2
where
cu1."constraint_name" = rc."constraint_name" and
cu2."constraint_name" = rc."unique_constraint_name" and
cu1."table_name" in ? and
cu2."table_name" = ? and
cu1."table_catalog" = ? and
cu2."table_catalog" = ?',
'reflect_habtm'=>'select
cua1."table_name",cua1."column_name",
cua2."table_name",cua2."column_name",
cub1."table_name",cub1."column_name",
cub2."table_name",cub2."column_name"
from
"information_schema".referential_constraints rca,
"information_schema".referential_constraints rcb,
"information_schema".key_column_usage cua1,
"information_schema".key_column_usage cua2,
"information_schema".key_column_usage cub1,
"information_schema".key_column_usage cub2
where
cua1."constraint_name" = rca."constraint_name" and
cua2."constraint_name" = rca."unique_constraint_name" and
cub1."constraint_name" = rcb."constraint_name" and
cub2."constraint_name" = rcb."unique_constraint_name" and
cua1."table_catalog" = ? and
cub1."table_catalog" = ? and
cua2."table_catalog" = ? and
cub2."table_catalog" = ? and
cua1."table_name" = cub1."table_name" and
cua2."table_name" = ? and
cub2."table_name" in ?'
);
}
public function getSql($name) {
return isset($this->queries[$name])?$this->queries[$name]:false;
}
public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
$e = function ($v) { return str_replace(array('\'','\\'),array('\\\'','\\\\'),$v); };
$conn_string = '';
if ($hostname || $socket) {
if ($socket) $hostname = $e($socket);
else $hostname = $e($hostname);
$conn_string.= " host='$hostname'";
}
if ($port) {
$port = ($port+0);
$conn_string.= " port='$port'";
}
if ($database) {
$database = $e($database);
$conn_string.= " dbname='$database'";
}
if ($username) {
$username = $e($username);
$conn_string.= " user='$username'";
}
if ($password) {
$password = $e($password);
$conn_string.= " password='$password'";
}
if ($charset) {
$charset = $e($charset);
$conn_string.= " options='--client_encoding=$charset'";
}
$db = pg_connect($conn_string);
$this->db = $db;
}
public function query($sql,$params=array()) {
$db = $this->db;
$sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
$param = array_shift($params);
if ($matches[0]=='!') {
$key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
if (is_object($param) && $param->type=='base64') {
return "encode(\"$key\",'base64') as \"$key\"";
}
if (is_object($param) && $param->type=='wkt') {
return "ST_AsText(\"$key\") as \"$key\"";
}
return '"'.$key.'"';
} else {
if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
return "'".pg_escape_string($db,$v)."'";
},$param)).')';
if (is_object($param) && $param->type=='base64') {
return "'\x".bin2hex(base64_decode($param->value))."'";
}
if (is_object($param) && $param->type=='wkt') {
return "ST_GeomFromText('".pg_escape_string($db,$param->value)."')";
}
if ($param===null) return 'NULL';
return "'".pg_escape_string($db,$param)."'";
}
}, $sql);
if (strtoupper(substr($sql,0,6))=='INSERT') {
$sql .= ' RETURNING id;';
}
//echo "\n$sql\n";
return @pg_query($db,$sql);
}
protected function convertFloatAndInt($result,&$values,&$fields) {
array_walk($values, function(&$v,$i) use ($result,$fields){
if (is_string($v) && $this->isNumericType($fields[$i])) {
$v+=0;
}
});
}
public function fetchAssoc($result,$fields=false) {
$values = pg_fetch_assoc($result);
if ($values && $fields) {
$this->convertFloatAndInt($result,$values,$fields);
}
return $values;
}
public function fetchRow($result,$fields=false) {
$values = pg_fetch_row($result);
if ($values && $fields) {
$fields = array_values($fields);
$this->convertFloatAndInt($result,$values,$fields);
}
return $values;
}
public function insertId($result) {
list($id) = pg_fetch_row($result);
return (int)$id;
}
public function affectedRows($result) {
return pg_affected_rows($result);
}
public function close($result) {
return pg_free_result($result);
}
public function fetchFields($table) {
$result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table));
$keys = array();
for($i=0;$i<pg_num_fields($result);$i++) {
$field = array();
$field['name'] = pg_field_name($result,$i);
$field['type'] = pg_field_type($result,$i);
$keys[$i] = (object)$field;
}
return $keys;
}
public function addLimitToSql($sql,$limit,$offset) {
return "$sql LIMIT $limit OFFSET $offset";
}
public function likeEscape($string) {
return addcslashes($string,'%_');
}
public function convertFilter($field, $comparator, $value) {
return false;
}
public function isNumericType($field) {
return in_array($field->type, array('int2', 'int4', 'int8', 'float4', 'float8'));
}
public function isBinaryType($field) {
return $field->type == 'bytea';
}
public function isGeometryType($field) {
return $field->type == 'geometry';
}
public function getDefaultCharset() {
return 'UTF8';
}
public function beginTransaction() {
return $this->query('BEGIN');
}
public function commitTransaction() {
return $this->query('COMMIT');
}
public function rollbackTransaction() {
return $this->query('ROLLBACK');
}
}
class SQLServer implements DatabaseInterface {
protected $db;
protected $queries;
public function __construct() {
$this->queries = array(
'list_tables'=>'SELECT
"TABLE_NAME",\'\' as "TABLE_COMMENT"
FROM
"INFORMATION_SCHEMA"."TABLES"
WHERE
"TABLE_CATALOG" = ?',
'reflect_table'=>'SELECT
"TABLE_NAME"
FROM
"INFORMATION_SCHEMA"."TABLES"
WHERE
"TABLE_NAME" LIKE ? AND
"TABLE_CATALOG" = ?',
'reflect_pk'=>'SELECT
"COLUMN_NAME"
FROM
"INFORMATION_SCHEMA"."TABLE_CONSTRAINTS" tc,
"INFORMATION_SCHEMA"."KEY_COLUMN_USAGE" ku
WHERE
tc."CONSTRAINT_TYPE" = \'PRIMARY KEY\' AND
tc."CONSTRAINT_NAME" = ku."CONSTRAINT_NAME" AND
ku."TABLE_NAME" = ? AND
ku."TABLE_CATALOG" = ?',
'reflect_belongs_to'=>'SELECT
cu1."TABLE_NAME",cu1."COLUMN_NAME",
cu2."TABLE_NAME",cu2."COLUMN_NAME"
FROM
"INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2
WHERE
cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND
cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND
cu1."TABLE_NAME" = ? AND
cu2."TABLE_NAME" IN ? AND
cu1."TABLE_CATALOG" = ? AND
cu2."TABLE_CATALOG" = ?',
'reflect_has_many'=>'SELECT
cu1."TABLE_NAME",cu1."COLUMN_NAME",
cu2."TABLE_NAME",cu2."COLUMN_NAME"
FROM
"INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rc,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu1,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cu2
WHERE
cu1."CONSTRAINT_NAME" = rc."CONSTRAINT_NAME" AND
cu2."CONSTRAINT_NAME" = rc."UNIQUE_CONSTRAINT_NAME" AND
cu1."TABLE_NAME" IN ? AND
cu2."TABLE_NAME" = ? AND
cu1."TABLE_CATALOG" = ? AND
cu2."TABLE_CATALOG" = ?',
'reflect_habtm'=>'SELECT
cua1."TABLE_NAME",cua1."COLUMN_NAME",
cua2."TABLE_NAME",cua2."COLUMN_NAME",
cub1."TABLE_NAME",cub1."COLUMN_NAME",
cub2."TABLE_NAME",cub2."COLUMN_NAME"
FROM
"INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rca,
"INFORMATION_SCHEMA".REFERENTIAL_CONSTRAINTS rcb,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua1,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cua2,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub1,
"INFORMATION_SCHEMA".CONSTRAINT_COLUMN_USAGE cub2
WHERE
cua1."CONSTRAINT_NAME" = rca."CONSTRAINT_NAME" AND
cua2."CONSTRAINT_NAME" = rca."UNIQUE_CONSTRAINT_NAME" AND
cub1."CONSTRAINT_NAME" = rcb."CONSTRAINT_NAME" AND
cub2."CONSTRAINT_NAME" = rcb."UNIQUE_CONSTRAINT_NAME" AND
cua1."TABLE_CATALOG" = ? AND
cub1."TABLE_CATALOG" = ? AND
cua2."TABLE_CATALOG" = ? AND
cub2."TABLE_CATALOG" = ? AND
cua1."TABLE_NAME" = cub1."TABLE_NAME" AND
cua2."TABLE_NAME" = ? AND
cub2."TABLE_NAME" IN ?'
);
}
public function getSql($name) {
return isset($this->queries[$name])?$this->queries[$name]:false;
}
public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
$connectionInfo = array();
if ($port) $hostname.=','.$port;
if ($username) $connectionInfo['UID']=$username;
if ($password) $connectionInfo['PWD']=$password;
if ($database) $connectionInfo['Database']=$database;
if ($charset) $connectionInfo['CharacterSet']=$charset;
$connectionInfo['QuotedId']=1;
$connectionInfo['ReturnDatesAsStrings']=1;
$db = sqlsrv_connect($hostname, $connectionInfo);
if (!$db) {
throw new \Exception('Connect failed. '.print_r( sqlsrv_errors(), true));
}
if ($socket) {
throw new \Exception('Socket connection is not supported.');
}
$this->db = $db;
}
public function query($sql,$params=array()) {
$args = array();
$db = $this->db;
$sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params,&$args) {
static $i=-1;
$i++;
$param = $params[$i];
if ($matches[0]=='!') {
$key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
if (is_object($param) && $param->type=='base64') {
return "CAST(N'' AS XML).value('xs:base64Binary(xs:hexBinary(sql:column(\"$key\")))', 'VARCHAR(MAX)') as \"$key\"";
}
if (is_object($param) && $param->type=='wkt') {
return "\"$key\".STAsText() as \"$key\"";
}
return '"'.$key.'"';
} else {
// This is workaround because SQLSRV cannot accept NULL in a param
if ($matches[0]=='?' && is_null($param)) {
return 'NULL';
}
if (is_array($param)) {
$args = array_merge($args,$param);
return '('.implode(',',str_split(str_repeat('?',count($param)))).')';
}
if (is_object($param) && $param->type=='base64') {
$args[] = bin2hex(base64_decode($param->value));
return 'CONVERT(VARBINARY(MAX),?,2)';
}
if (is_object($param) && $param->type=='wkt') {
$args[] = $param->value;
return 'geometry::STGeomFromText(?,0)';
}
$args[] = $param;
return '?';
}
}, $sql);
//var_dump($params);
//echo "\n$sql\n";
//var_dump($args);
//file_put_contents('sql.txt',"\n$sql\n".var_export($args,true)."\n",FILE_APPEND);
if (strtoupper(substr($sql,0,6))=='INSERT') {
$sql .= ';SELECT SCOPE_IDENTITY()';
}
return sqlsrv_query($db,$sql,$args)?:null;
}
public function fetchAssoc($result,$fields=false) {
return sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC);
}
public function fetchRow($result,$fields=false) {
return sqlsrv_fetch_array($result, SQLSRV_FETCH_NUMERIC);
}
public function insertId($result) {
sqlsrv_next_result($result);
sqlsrv_fetch($result);
return (int)sqlsrv_get_field($result, 0);
}
public function affectedRows($result) {
return sqlsrv_rows_affected($result);
}
public function close($result) {
return sqlsrv_free_stmt($result);
}
public function fetchFields($table) {
$result = $this->query('SELECT * FROM ! WHERE 1=2;',array($table));
//var_dump(sqlsrv_field_metadata($result));
return array_map(function($a){
$p = array();
foreach ($a as $k=>$v) {
$p[strtolower($k)] = $v;
}
return (object)$p;
},sqlsrv_field_metadata($result));
}
public function addLimitToSql($sql,$limit,$offset) {
return "$sql OFFSET $offset ROWS FETCH NEXT $limit ROWS ONLY";
}
public function likeEscape($string) {
return str_replace(array('%','_'),array('[%]','[_]'),$string);
}
public function convertFilter($field, $comparator, $value) {
$comparator = strtolower($comparator);
if ($comparator[0]!='n') {
switch ($comparator) {
case 'sco': return array('!.STContains(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'scr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'sdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'seq': return array('!.STEquals(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'sin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'sov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'sto': return array('!.STTouches(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'swi': return array('!.STWithin(geometry::STGeomFromText(?,0))=1',$field,$value);
case 'sic': return array('!.STIsClosed()=1',$field);
case 'sis': return array('!.STIsSimple()=1',$field);
case 'siv': return array('!.STIsValid()=1',$field);
}
} else {
switch ($comparator) {
case 'nsco': return array('!.STContains(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nscr': return array('!.STCrosses(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nsdi': return array('!.STDisjoint(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nseq': return array('!.STEquals(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nsin': return array('!.STIntersects(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nsov': return array('!.STOverlaps(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nsto': return array('!.STTouches(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nswi': return array('!.STWithin(geometry::STGeomFromText(?,0))=0',$field,$value);
case 'nsic': return array('!.STIsClosed()=0',$field);
case 'nsis': return array('!.STIsSimple()=0',$field);
case 'nsiv': return array('!.STIsValid()=0',$field);
}
}
return false;
}
public function isNumericType($field) {
return in_array($field->type,array(-6,-5,4,5,2,6,7));
}
public function isBinaryType($field) {
return ($field->type>=-4 && $field->type<=-2);
}
public function isGeometryType($field) {
return ($field->type==-151);
}
public function getDefaultCharset() {
return 'UTF-8';
}
public function beginTransaction() {
return sqlsrv_begin_transaction($this->db);
}
public function commitTransaction() {
return sqlsrv_commit($this->db);
}
public function rollbackTransaction() {
return sqlsrv_rollback($this->db);
}
}
class SQLite implements DatabaseInterface {
protected $db;
protected $queries;
public function __construct() {
$this->queries = array(
'list_tables'=>'SELECT
"name", ""
FROM
"sys/tables"',
'reflect_table'=>'SELECT
"name"
FROM
"sys/tables"
WHERE
"name"=?',
'reflect_pk'=>'SELECT
"name"
FROM
"sys/columns"
WHERE
"pk"=1 AND
"self"=?',
'reflect_belongs_to'=>'SELECT
"self", "from",
"table", "to"
FROM
"sys/foreign_keys"
WHERE
"self" = ? AND
"table" IN ? AND
? like "%" AND
? like "%"',
'reflect_has_many'=>'SELECT
"self", "from",
"table", "to"
FROM
"sys/foreign_keys"
WHERE
"self" IN ? AND
"table" = ? AND
? like "%" AND
? like "%"',
'reflect_habtm'=>'SELECT
k1."self", k1."from",
k1."table", k1."to",
k2."self", k2."from",
k2."table", k2."to"
FROM
"sys/foreign_keys" k1,
"sys/foreign_keys" k2
WHERE
? like "%" AND
? like "%" AND
? like "%" AND
? like "%" AND
k1."self" = k2."self" AND
k1."table" = ? AND
k2."table" IN ?'
);
}
public function getSql($name) {
return isset($this->queries[$name])?$this->queries[$name]:false;
}
public function connect($hostname,$username,$password,$database,$port,$socket,$charset) {
$this->db = new SQLite3($database);
// optimizations
$this->db->querySingle('PRAGMA synchronous = NORMAL');
$this->db->querySingle('PRAGMA foreign_keys = on');
$reflection = $this->db->querySingle('SELECT name FROM sqlite_master WHERE type = "table" and name like "sys/%"');
if (!$reflection) {
//create reflection tables
$this->query('CREATE table "sys/version" ("version" integer)');
$this->query('CREATE table "sys/tables" ("name" text)');
$this->query('CREATE table "sys/columns" ("self" text,"cid" integer,"name" text,"type" integer,"notnull" integer,"dflt_value" integer,"pk" integer)');
$this->query('CREATE table "sys/foreign_keys" ("self" text,"id" integer,"seq" integer,"table" text,"from" text,"to" text,"on_update" text,"on_delete" text,"match" text)');
}
$version = $this->db->querySingle('pragma schema_version');
if ($version != $this->db->querySingle('SELECT "version" from "sys/version"')) {
// reflection may take a while
set_time_limit(3600);
// update version data
$this->query('DELETE FROM "sys/version"');
$this->query('INSERT into "sys/version" ("version") VALUES (?)',array($version));
// update tables data
$this->query('DELETE FROM "sys/tables"');
$result = $this->query('SELECT * FROM sqlite_master WHERE (type = "table" or type = "view") and name not like "sys/%" and name<>"sqlite_sequence"');
$tables = array();
while ($row = $this->fetchAssoc($result)) {
$tables[] = $row['name'];
$this->query('INSERT into "sys/tables" ("name") VALUES (?)',array($row['name']));
}
// update columns and foreign_keys data
$this->query('DELETE FROM "sys/columns"');
$this->query('DELETE FROM "sys/foreign_keys"');
foreach ($tables as $table) {
$result = $this->query('pragma table_info(!)',array($table));
while ($row = $this->fetchRow($result)) {
array_unshift($row, $table);
$this->query('INSERT into "sys/columns" ("self","cid","name","type","notnull","dflt_value","pk") VALUES (?,?,?,?,?,?,?)',$row);
}
$result = $this->query('pragma foreign_key_list(!)',array($table));
while ($row = $this->fetchRow($result)) {
array_unshift($row, $table);
$this->query('INSERT into "sys/foreign_keys" ("self","id","seq","table","from","to","on_update","on_delete","match") VALUES (?,?,?,?,?,?,?,?,?)',$row);
}
}
}
}
public function query($sql,$params=array()) {
$db = $this->db;
$sql = preg_replace_callback('/\!|\?/', function ($matches) use (&$db,&$params) {
$param = array_shift($params);
if ($matches[0]=='!') {
$key = preg_replace('/[^a-zA-Z0-9\-_=<> ]/','',is_object($param)?$param->key:$param);
return '"'.$key.'"';
} else {
if (is_array($param)) return '('.implode(',',array_map(function($v) use (&$db) {
return "'".$db->escapeString($v)."'";
},$param)).')';
if (is_object($param) && $param->type=='base64') {
return "'".$db->escapeString($param->value)."'";
}
if ($param===null) return 'NULL';
return "'".$db->escapeString($param)."'";
}
}, $sql);
//echo "\n$sql\n";
try { $result=$db->query($sql); } catch(\Exception $e) { $result=null; }
return $result;
}
public function fetchAssoc($result,$fields=false) {
return $result->fetchArray(SQLITE3_ASSOC);
}
public function fetchRow($result,$fields=false) {
return $result->fetchArray(SQLITE3_NUM);
}
public function insertId($result) {
return $this->db->lastInsertRowID();
}
public function affectedRows($result) {
return $this->db->changes();
}
public function close($result) {
return $result->finalize();
}
public function fetchFields($table) {
$result = $this->query('SELECT * FROM "sys/columns" WHERE "self"=?;',array($table));
$fields = array();
while ($row = $this->fetchAssoc($result)){
$fields[strtolower($row['name'])] = (object)$row;
}
return $fields;
}
public function addLimitToSql($sql,$limit,$offset) {
return "$sql LIMIT $limit OFFSET $offset";
}
public function likeEscape($string) {
return addcslashes($string,'%_');
}
public function convertFilter($field, $comparator, $value) {
return false;
}
public function isNumericType($field) {
return in_array($field->type,array('integer','real'));
}
public function isBinaryType($field) {
return (substr($field->type,0,4)=='data');
}
public function isGeometryType($field) {
return false;
}
public function getDefaultCharset() {
return 'utf8';
}
public function beginTransaction() {
return $this->query('BEGIN');
}
public function commitTransaction() {
return $this->query('COMMIT');
}
public function rollbackTransaction() {
return $this->query('ROLLBACK');
}
}
class PHP_CRUD_API {
protected $db;
protected $settings;
protected function mapMethodToAction($method,$key) {
switch ($method) {
case 'OPTIONS': return 'headers';
case 'GET': return ($key===false)?'list':'read';
case 'PUT': return 'update';
case 'POST': return 'create';
case 'DELETE': return 'delete';
case 'PATCH': return 'increment';
default: $this->exitWith404('method');
}
return false;
}
protected function parseRequestParameter(&$request,$characters) {
if ($request==='') return false;
$pos = strpos($request,'/');
$value = $pos?substr($request,0,$pos):$request;
$request = $pos?substr($request,$pos+1):'';
if (!$characters) return $value;
return preg_replace("/[^$characters]/",'',$value);
}
protected function parseGetParameter($get,$name,$characters) {
$value = isset($get[$name])?$get[$name]:false;
return $characters?preg_replace("/[^$characters]/",'',$value):$value;
}
protected function parseGetParameterArray($get,$name,$characters) {
$values = isset($get[$name])?$get[$name]:false;
if (!is_array($values)) $values = array($values);
if ($characters) {
foreach ($values as &$value) {
$value = preg_replace("/[^$characters]/",'',$value);
}
}
return $values;
}
protected function applyTableAuthorizer($callback,$action,$database,&$tables) {
if (is_callable($callback,true)) foreach ($tables as $i=>$table) {
if (!$callback($action,$database,$table)) {
unset($tables[$i]);
}
}
}
protected function applyRecordFilter($callback,$action,$database,$tables,&$filters) {
if (is_callable($callback,true)) foreach ($tables as $i=>$table) {
$this->addFilters($filters,$table,array($table=>'and'),$callback($action,$database,$table));
}
}
protected function applyTenancyFunction($callback,$action,$database,$fields,&$filters) {
if (is_callable($callback,true)) foreach ($fields as $table=>$keys) {
foreach ($keys as $field) {
$v = $callback($action,$database,$table,$field->name);
if ($v!==null) {
if (is_array($v)) $this->addFilter($filters,$table,'and',$field->name,'in',implode(',',$v));
else $this->addFilter($filters,$table,'and',$field->name,'eq',$v);
}
}
}
}
protected function applyColumnAuthorizer($callback,$action,$database,&$fields) {
if (is_callable($callback,true)) foreach ($fields as $table=>$keys) {
foreach ($keys as $field) {
if (!$callback($action,$database,$table,$field->name)) {
unset($fields[$table][$field->name]);
}
}
}
}
protected function applyInputTenancy($callback,$action,$database,$table,&$input,$keys) {
if (is_callable($callback,true)) foreach ($keys as $key=>$field) {
$v = $callback($action,$database,$table,$key);
if ($v!==null && (isset($input->$key) || $action=='create')) {
if (is_array($v)) {
if (!count($v)) {
$input->$key = null;
} elseif (!isset($input->$key)) {
$input->$key = $v[0];
} elseif (!in_array($input->$key,$v)) {
$input->$key = null;
}
} else {
$input->$key = $v;
}
}
}
}
protected function applyInputSanitizer($callback,$action,$database,$table,&$input,$keys) {
if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) {
if (isset($keys[$key])) {
$input->$key = $callback($action,$database,$table,$key,$keys[$key]->type,$value);
}
}
}
protected function applyInputValidator($callback,$action,$database,$table,$input,$keys,$context) {
$errors = array();
if (is_callable($callback,true)) foreach ((array)$input as $key=>$value) {
if (isset($keys[$key])) {
$error = $callback($action,$database,$table,$key,$keys[$key]->type,$value,$context);
if ($error!==true && $error!==null) $errors[$key] = $error;
}
}
if (!empty($errors)) $this->exitWith422($errors);
}
protected function processTableAndIncludeParameters($database,$table,$include,$action) {
$blacklist = array('information_schema','mysql','sys','pg_catalog');
if (in_array(strtolower($database), $blacklist)) return array();
$table_list = array();
if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) {
while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0];
$this->db->close($result);
}
if (empty($table_list)) $this->exitWith404('entity');
if ($action=='list') {
foreach (explode(',',$include) as $table) {
if ($result = $this->db->query($this->db->getSql('reflect_table'),array($table,$database))) {
while ($row = $this->db->fetchRow($result)) $table_list[] = $row[0];
$this->db->close($result);
}
}
}
return $table_list;
}
protected function exitWith404($type) {
if (isset($_SERVER['REQUEST_METHOD'])) {
header('Content-Type:',true,404);
die("Not found ($type)");
} else {
throw new \Exception("Not found ($type)");
}
}
protected function exitWith422($object) {
if (isset($_SERVER['REQUEST_METHOD'])) {
header('Content-Type:',true,422);
die(json_encode($object));
} else {
throw new \Exception(json_encode($object));
}
}
protected function headersCommand($parameters) {
$headers = array();
$headers[]='Access-Control-Allow-Headers: Content-Type';
$headers[]='Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, PATCH';
$headers[]='Access-Control-Allow-Credentials: true';
$headers[]='Access-Control-Max-Age: 1728000';
if (isset($_SERVER['REQUEST_METHOD'])) {
foreach ($headers as $header) header($header);
} else {
echo json_encode($headers);
}
}
protected function startOutput() {
if (isset($_SERVER['REQUEST_METHOD'])) {
header('Content-Type: application/json; charset=utf-8');
}
}
protected function findPrimaryKeys($table,$database) {
$fields = array();
if ($result = $this->db->query($this->db->getSql('reflect_pk'),array($table,$database))) {
while ($row = $this->db->fetchRow($result)) {
$fields[] = $row[0];
}
$this->db->close($result);
}
return $fields;
}
protected function processKeyParameter($key,$tables,$database) {
if ($key===false) return false;
$fields = $this->findPrimaryKeys($tables[0],$database);
if (count($fields)!=1) $this->exitWith404('1pk');
return array($key,$fields[0]);
}
protected function processOrderingsParameter($orderings) {
if (!$orderings) return false;
foreach ($orderings as &$order) {
$order = explode(',',$order,2);
if (count($order)<2) $order[1]='ASC';
if (!strlen($order[0])) return false;
$direction = strtoupper($order[1]);
if (in_array($direction,array('ASC','DESC'))) {
$order[1] = $direction;
}
}
return $orderings;
}
protected function convertFilter($field, $comparator, $value) {
$result = $this->db->convertFilter($field,$comparator,$value);
if ($result) return $result;
// default behavior
$comparator = strtolower($comparator);
if ($comparator[0]!='n') {
if (strlen($comparator)==2) {
switch ($comparator) {
case 'cs': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value).'%');
case 'sw': return array('! LIKE ?',$field,$this->db->likeEscape($value).'%');
case 'ew': return array('! LIKE ?',$field,'%'.$this->db->likeEscape($value));
case 'eq': return array('! = ?',$field,$value);
case 'lt': return array('! < ?',$field,$value);
case 'le': return array('! <= ?',$field,$value);
case 'ge': return array('! >= ?',$field,$value);
case 'gt': return array('! > ?',$field,$value);
case 'bt': $v = explode(',',$value); if (count($v)<2) return false;
return array('! BETWEEN ? AND ?',$field,$v[0],$v[1]);
case 'in': return array('! IN ?',$field,explode(',',$value));
case 'is': return array('! IS NULL',$field);
}
} else {
switch ($comparator) {
case 'sco': return array('ST_Contains(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'scr': return array('ST_Crosses(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'sdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'seq': return array('ST_Equals(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'sin': return array('ST_Intersects(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'sov': return array('ST_Overlaps(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'sto': return array('ST_Touches(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'swi': return array('ST_Within(!,ST_GeomFromText(?))=TRUE',$field,$value);
case 'sic': return array('ST_IsClosed(!)=TRUE',$field);
case 'sis': return array('ST_IsSimple(!)=TRUE',$field);
case 'siv': return array('ST_IsValid(!)=TRUE',$field);
}
}
} else {
if (strlen($comparator)==2) {
switch ($comparator) {
case 'ne': return $this->convertFilter($field, 'neq', $value); // deprecated
case 'ni': return $this->convertFilter($field, 'nin', $value); // deprecated
case 'no': return $this->convertFilter($field, 'nis', $value); // deprecated
}
} elseif (strlen($comparator)==3) {
switch ($comparator) {
case 'ncs': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value).'%');
case 'nsw': return array('! NOT LIKE ?',$field,$this->db->likeEscape($value).'%');
case 'new': return array('! NOT LIKE ?',$field,'%'.$this->db->likeEscape($value));
case 'neq': return array('! <> ?',$field,$value);
case 'nlt': return array('! >= ?',$field,$value);
case 'nle': return array('! > ?',$field,$value);
case 'nge': return array('! < ?',$field,$value);
case 'ngt': return array('! <= ?',$field,$value);
case 'nbt': $v = explode(',',$value); if (count($v)<2) return false;
return array('! NOT BETWEEN ? AND ?',$field,$v[0],$v[1]);
case 'nin': return array('! NOT IN ?',$field,explode(',',$value));
case 'nis': return array('! IS NOT NULL',$field);
}
} else {
switch ($comparator) {
case 'nsco': return array('ST_Contains(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nscr': return array('ST_Crosses(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nsdi': return array('ST_Disjoint(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nseq': return array('ST_Equals(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nsin': return array('ST_Intersects(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nsov': return array('ST_Overlaps(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nsto': return array('ST_Touches(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nswi': return array('ST_Within(!,ST_GeomFromText(?))=FALSE',$field,$value);
case 'nsic': return array('ST_IsClosed(!)=FALSE',$field);
case 'nsis': return array('ST_IsSimple(!)=FALSE',$field);
case 'nsiv': return array('ST_IsValid(!)=FALSE',$field);
}
}
}
return false;
}
public function addFilter(&$filters,$table,$and,$field,$comparator,$value) {
if (!isset($filters[$table])) $filters[$table] = array();
if (!isset($filters[$table][$and])) $filters[$table][$and] = array();
$filter = $this->convertFilter($field,$comparator,$value);
if ($filter) $filters[$table][$and][] = $filter;
}
public function addFilters(&$filters,$table,$satisfy,$filterStrings) {
if ($filterStrings) {
for ($i=0;$i<count($filterStrings);$i++) {
$parts = explode(',',$filterStrings[$i],3);
if (count($parts)>=2) {
if (strpos($parts[0],'.')) list($t,$f) = explode('.',$parts[0],2);
else list($t,$f) = array($table,$parts[0]);
$comparator = $parts[1];
$value = isset($parts[2])?$parts[2]:null;
$and = isset($satisfy[$t])?$satisfy[$t]:'and';
$this->addFilter($filters,$t,$and,$f,$comparator,$value);
}
}
}
}
protected function processSatisfyParameter($tables,$satisfyString) {
$satisfy = array();
foreach (explode(',',$satisfyString) as $str) {
if (strpos($str,'.')) list($t,$s) = explode('.',$str,2);
else list($t,$s) = array($tables[0],$str);
$and = ($s && strtolower($s)=='any')?'or':'and';
$satisfy[$t] = $and;
}
return $satisfy;
}
protected function processFiltersParameter($tables,$satisfy,$filterStrings) {
$filters = array();
$this->addFilters($filters,$tables[0],$satisfy,$filterStrings);
return $filters;
}
protected function processPageParameter($page) {
if (!$page) return false;
$page = explode(',',$page,2);
if (count($page)<2) $page[1]=20;
$page[0] = ($page[0]-1)*$page[1];
return $page;
}
protected function retrieveObject($key,$fields,$filters,$tables) {
if (!$key) return false;
$table = $tables[0];
$params = array();
$sql = 'SELECT ';
$this->convertOutputs($sql,$params,$fields[$table]);
$sql .= ' FROM !';
$params[] = $table;
$this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
$this->addWhereFromFilters($filters[$table],$sql,$params);
$object = null;
if ($result = $this->db->query($sql,$params)) {
$object = $this->db->fetchAssoc($result,$fields[$table]);
$this->db->close($result);
}
return $object;
}
protected function retrieveObjects($key,$fields,$filters,$tables) {
$keyField = $key[1];
$keys = explode(',',$key[0]);
$rows = array();
foreach ($keys as $key) {
$result = $this->retrieveObject(array($key,$keyField),$fields,$filters,$tables);
if ($result===null) {
return null;
}
$rows[] = $result;
}
return $rows;
}
protected function createObject($input,$tables) {
if (!$input) return false;
$input = (array)$input;
$keys = implode(',',str_split(str_repeat('!', count($input))));
$values = implode(',',str_split(str_repeat('?', count($input))));
$params = array_merge(array_keys($input),array_values($input));
array_unshift($params, $tables[0]);
$result = $this->db->query('INSERT INTO ! ('.$keys.') VALUES ('.$values.')',$params);
if (!$result) return null;
return $this->db->insertId($result);
}
protected function createObjects($inputs,$tables) {
if (!$inputs) return false;
$ids = array();
$this->db->beginTransaction();
foreach ($inputs as $input) {
$result = $this->createObject($input,$tables);
if ($result===null) {
$this->db->rollbackTransaction();
return null;
}
$ids[] = $result;
}
$this->db->commitTransaction();
return $ids;
}
protected function updateObject($key,$input,$filters,$tables) {
if (!$input) return false;
$input = (array)$input;
$table = $tables[0];
$sql = 'UPDATE ! SET ';
$params = array($table);
foreach (array_keys($input) as $j=>$k) {
if ($j) $sql .= ',';
$v = $input[$k];
$sql .= '!=?';
$params[] = $k;
$params[] = $v;
}
$this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
$this->addWhereFromFilters($filters[$table],$sql,$params);
$result = $this->db->query($sql,$params);
if (!$result) return null;
return $this->db->affectedRows($result);
}
protected function updateObjects($key,$inputs,$filters,$tables) {
if (!$inputs) return false;
$keyField = $key[1];
$keys = explode(',',$key[0]);
if (count($inputs)!=count($keys)) {
$this->exitWith404('subject');
}
$rows = array();
$this->db->beginTransaction();
foreach ($inputs as $i=>$input) {
$result = $this->updateObject(array($keys[$i],$keyField),$input,$filters,$tables);
if ($result===null) {
$this->db->rollbackTransaction();
return null;
}
$rows[] = $result;
}
$this->db->commitTransaction();
return $rows;
}
protected function deleteObject($key,$filters,$tables) {
$table = $tables[0];
$sql = 'DELETE FROM !';
$params = array($table);
$this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
$this->addWhereFromFilters($filters[$table],$sql,$params);
$result = $this->db->query($sql,$params);
if (!$result) return null;
return $this->db->affectedRows($result);
}
protected function deleteObjects($key,$filters,$tables) {
$keyField = $key[1];
$keys = explode(',',$key[0]);
$rows = array();
$this->db->beginTransaction();
foreach ($keys as $key) {
$result = $this->deleteObject(array($key,$keyField),$filters,$tables);
if ($result===null) {
$this->db->rollbackTransaction();
return null;
}
$rows[] = $result;
}
$this->db->commitTransaction();
return $rows;
}
protected function incrementObject($key,$input,$filters,$tables,$fields) {
if (!$input) return false;
$input = (array)$input;
$table = $tables[0];
$sql = 'UPDATE ! SET ';
$params = array($table);
foreach (array_keys($input) as $j=>$k) {
if ($j) $sql .= ',';
$v = $input[$k];
if ($this->db->isNumericType($fields[$table][$k])) {
$sql .= '!=!+?';
$params[] = $k;
$params[] = $k;
$params[] = $v;
} else {
$sql .= '!=!';
$params[] = $k;
$params[] = $k;
}
}
$this->addFilter($filters,$table,'and',$key[1],'eq',$key[0]);
$this->addWhereFromFilters($filters[$table],$sql,$params);
$result = $this->db->query($sql,$params);
if (!$result) return null;
return $this->db->affectedRows($result);
}
protected function incrementObjects($key,$inputs,$filters,$tables,$fields) {
if (!$inputs) return false;
$keyField = $key[1];
$keys = explode(',',$key[0]);
if (count($inputs)!=count($keys)) {
$this->exitWith404('subject');
}
$rows = array();
$this->db->beginTransaction();
foreach ($inputs as $i=>$input) {
$result = $this->incrementObject(array($keys[$i],$keyField),$input,$filters,$tables,$fields);
if ($result===null) {
$this->db->rollbackTransaction();
return null;
}
$rows[] = $result;
}
$this->db->commitTransaction();
return $rows;
}
protected function findRelations($tables,$database,$auto_include) {
$tableset = array();
$collect = array();
$select = array();
while (count($tables)>1) {
$table0 = array_shift($tables);
$tableset[] = $table0;
$result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table0,$tables,$database,$database));
while ($row = $this->db->fetchRow($result)) {
if (!$auto_include && !in_array($row[0],array_merge($tables,$tableset))) continue;
$collect[$row[0]][$row[1]]=array();
$select[$row[2]][$row[3]]=array($row[0],$row[1]);
if (!in_array($row[0],$tableset)) $tableset[] = $row[0];
}
$result = $this->db->query($this->db->getSql('reflect_has_many'),array($tables,$table0,$database,$database));
while ($row = $this->db->fetchRow($result)) {
if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue;
$collect[$row[2]][$row[3]]=array();
$select[$row[0]][$row[1]]=array($row[2],$row[3]);
if (!in_array($row[2],$tableset)) $tableset[] = $row[2];
}
$result = $this->db->query($this->db->getSql('reflect_habtm'),array($database,$database,$database,$database,$table0,$tables));
while ($row = $this->db->fetchRow($result)) {
if (!$auto_include && !in_array($row[2],array_merge($tables,$tableset))) continue;
if (!$auto_include && !in_array($row[4],array_merge($tables,$tableset))) continue;
$collect[$row[2]][$row[3]]=array();
$select[$row[0]][$row[1]]=array($row[2],$row[3]);
$collect[$row[4]][$row[5]]=array();
$select[$row[6]][$row[7]]=array($row[4],$row[5]);
if (!in_array($row[2],$tableset)) $tableset[] = $row[2];
if (!in_array($row[4],$tableset)) $tableset[] = $row[4];
}
}
$tableset[] = array_shift($tables);
$tableset = array_unique($tableset);
return array($tableset,$collect,$select);
}
protected function retrieveInputs($data) {
$input = (object)array();
if (strlen($data)>0) {
if ($data[0]=='{' || $data[0]=='[') {
$input = json_decode($data);
} else {
parse_str($data, $input);
foreach ($input as $key => $value) {
if (substr($key,-9)=='__is_null') {
$input[substr($key,0,-9)] = null;
unset($input[$key]);
}
}
$input = (object)$input;
}
}
return is_array($input)?$input:array($input);
}
protected function addRelationColumns($columns,$select) {
if ($columns) {
foreach ($select as $table=>$keys) {
foreach ($keys as $key=>$other) {
$columns.=",$table.$key,".implode('.',$other);
}
}
}
return $columns;
}
protected function findFields($tables,$columns,$database) {
$fields = array();
foreach ($tables as $i=>$table) {
$fields[$table] = $this->findTableFields($table,$database);
$fields[$table] = $this->filterFieldsByColumns($fields[$table],$columns,$i==0,$table);
}
return $fields;
}
protected function filterFieldsByColumns($fields,$columns,$first,$table) {
if ($columns) {
$columns = explode(',',$columns);
foreach (array_keys($fields) as $key) {
$delete = true;
foreach ($columns as $column) {
if (strpos($column,'.')) {
if ($column=="$table.$key" || $column=="$table.*") {
$delete = false;
}
} elseif ($first) {
if ($column==$key || $column=="*") {
$delete = false;
}
}
}
if ($delete) unset($fields[$key]);
}
}
return $fields;
}
protected function findTableFields($table,$database) {
$fields = array();
foreach ($this->db->fetchFields($table) as $field) {
$fields[$field->name] = $field;
}
return $fields;
}
protected function filterInputByFields($input,$fields) {
if ($fields) foreach (array_keys((array)$input) as $key) {
if (!isset($fields[$key])) {
unset($input->$key);
}
}
return $input;
}
protected function convertInputs(&$input,$fields) {
foreach ($fields as $key=>$field) {
if (isset($input->$key) && $input->$key && $this->db->isBinaryType($field)) {
$value = $input->$key;
$value = str_pad(strtr($value, '-_', '+/'), ceil(strlen($value) / 4) * 4, '=', STR_PAD_RIGHT);
$input->$key = (object)array('type'=>'base64','value'=>$value);
}
if (isset($input->$key) && $input->$key && $this->db->isGeometryType($field)) {
$input->$key = (object)array('type'=>'wkt','value'=>$input->$key);
}
}
}
protected function convertOutputs(&$sql, &$params, $fields) {
$sql .= implode(',',str_split(str_repeat('!',count($fields))));
foreach ($fields as $key=>$field) {
if ($this->db->isBinaryType($field)) {
$params[] = (object)array('type'=>'base64','key'=>$key);
}
else if ($this->db->isGeometryType($field)) {
$params[] = (object)array('type'=>'wkt','key'=>$key);
}
else {
$params[] = $key;
}
}
}
protected function getParameters($settings) {
extract($settings);
$table = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_');
$key = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_,'); // auto-increment or uuid
$action = $this->mapMethodToAction($method,$key);
$include = $this->parseGetParameter($get, 'include', 'a-zA-Z0-9\-_,');
$page = $this->parseGetParameter($get, 'page', '0-9,');
$filters = $this->parseGetParameterArray($get, 'filter', false);
$satisfy = $this->parseGetParameter($get, 'satisfy', 'a-zA-Z0-9\-_,.');
$columns = $this->parseGetParameter($get, 'columns', 'a-zA-Z0-9\-_,.*');
$orderings = $this->parseGetParameterArray($get, 'order', 'a-zA-Z0-9\-_,');
$transform = $this->parseGetParameter($get, 'transform', 't1');
$tables = $this->processTableAndIncludeParameters($database,$table,$include,$action);
$key = $this->processKeyParameter($key,$tables,$database);
$satisfy = $this->processSatisfyParameter($tables,$satisfy);
$filters = $this->processFiltersParameter($tables,$satisfy,$filters);
$page = $this->processPageParameter($page);
$orderings = $this->processOrderingsParameter($orderings);
// reflection
list($tables,$collect,$select) = $this->findRelations($tables,$database,$auto_include);
$columns = $this->addRelationColumns($columns,$select);
$fields = $this->findFields($tables,$columns,$database);
// permissions
if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action,$database,$tables);
if (!isset($tables[0])) $this->exitWith404('entity');
if ($record_filter) $this->applyRecordFilter($record_filter,$action,$database,$tables,$filters);
if ($tenancy_function) $this->applyTenancyFunction($tenancy_function,$action,$database,$fields,$filters);
if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action,$database,$fields);
$multi = strpos($key[0],',')!==false;
if (strlen($post)) {
// input
$multi = $post[0]=='[';
$contexts = $this->retrieveInputs($post);
$inputs = array();
foreach ($contexts as $context) {
$input = $this->filterInputByFields($context,$fields[$tables[0]]);
if ($tenancy_function) $this->applyInputTenancy($tenancy_function,$action,$database,$tables[0],$input,$fields[$tables[0]]);
if ($input_sanitizer) $this->applyInputSanitizer($input_sanitizer,$action,$database,$tables[0],$input,$fields[$tables[0]]);
if ($input_validator) $this->applyInputValidator($input_validator,$action,$database,$tables[0],$input,$fields[$tables[0]],$context);
$this->convertInputs($input,$fields[$tables[0]]);
$inputs[] = $input;
}
}
return compact('action','database','tables','key','page','filters','fields','orderings','transform','multi','inputs','collect','select');
}
protected function addWhereFromFilters($filters,&$sql,&$params) {
$first = true;
if (isset($filters['or'])) {
$first = false;
$sql .= ' WHERE (';
foreach ($filters['or'] as $i=>$filter) {
$sql .= $i==0?'':' OR ';
$sql .= $filter[0];
for ($i=1;$i<count($filter);$i++) {
$params[] = $filter[$i];
}
}
$sql .= ')';
}
if (isset($filters['and'])) {
foreach ($filters['and'] as $i=>$filter) {
$sql .= $first?' WHERE ':' AND ';
$sql .= $filter[0];
for ($i=1;$i<count($filter);$i++) {
$params[] = $filter[$i];
}
$first = false;
}
}
}
protected function addOrderByFromOrderings($orderings,&$sql,&$params) {
foreach ($orderings as $i=>$ordering) {
$sql .= $i==0?' ORDER BY ':', ';
$sql .= '! '.$ordering[1];
$params[] = $ordering[0];
}
}
protected function listCommandInternal($parameters) {
extract($parameters);
echo '{';
$table = array_shift($tables);
// first table
$count = false;
echo '"'.$table.'":{';
if (is_array($orderings) && is_array($page)) {
$params = array();
$sql = 'SELECT COUNT(*) FROM !';
$params[] = $table;
if (isset($filters[$table])) {
$this->addWhereFromFilters($filters[$table],$sql,$params);
}
if ($result = $this->db->query($sql,$params)) {
while ($pages = $this->db->fetchRow($result)) {
$count = $pages[0];
}
}
}
$params = array();
$sql = 'SELECT ';
$this->convertOutputs($sql,$params,$fields[$table]);
$sql .= ' FROM !';
$params[] = $table;
if (isset($filters[$table])) {
$this->addWhereFromFilters($filters[$table],$sql,$params);
}
if (is_array($orderings)) {
$this->addOrderByFromOrderings($orderings,$sql,$params);
}
if (is_array($orderings) && is_array($page)) {
$sql = $this->db->addLimitToSql($sql,$page[1],$page[0]);
}
if ($result = $this->db->query($sql,$params)) {
echo '"columns":';
$keys = array_keys($fields[$table]);
echo json_encode($keys);
$keys = array_flip($keys);
echo ',"records":[';
$first_row = true;
while ($row = $this->db->fetchRow($result,$fields[$table])) {
if ($first_row) $first_row = false;
else echo ',';
if (isset($collect[$table])) {
foreach (array_keys($collect[$table]) as $field) {
$collect[$table][$field][] = $row[$keys[$field]];
}
}
echo json_encode($row);
}
$this->db->close($result);
echo ']';
if ($count) echo ',';
}
if ($count) echo '"results":'.$count;
echo '}';
// other tables
foreach ($tables as $t=>$table) {
echo ',';
echo '"'.$table.'":{';
$params = array();
$sql = 'SELECT ';
$this->convertOutputs($sql,$params,$fields[$table]);
$sql .= ' FROM !';
$params[] = $table;
if (isset($select[$table])) {
echo '"relations":{';
$first_row = true;
foreach ($select[$table] as $field => $path) {
$values = $collect[$path[0]][$path[1]];
if ($values) {
$this->addFilter($filters,$table,'and',$field,'in',implode(',',$values));
}
if ($first_row) $first_row = false;
else echo ',';
echo '"'.$field.'":"'.implode('.',$path).'"';
}
echo '}';
}
if (isset($filters[$table])) {
$this->addWhereFromFilters($filters[$table],$sql,$params);
}
if ($result = $this->db->query($sql,$params)) {
if (isset($select[$table])) echo ',';
echo '"columns":';
$keys = array_keys($fields[$table]);
echo json_encode($keys);
$keys = array_flip($keys);
echo ',"records":[';
$first_row = true;
while ($row = $this->db->fetchRow($result,$fields[$table])) {
if ($first_row) $first_row = false;
else echo ',';
if (isset($collect[$table])) {
foreach (array_keys($collect[$table]) as $field) {
$collect[$table][$field][]=$row[$keys[$field]];
}
}
echo json_encode($row);
}
$this->db->close($result);
echo ']';
}
echo '}';
}
echo '}';
}
protected function readCommand($parameters) {
extract($parameters);
if ($multi) $object = $this->retrieveObjects($key,$fields,$filters,$tables);
else $object = $this->retrieveObject($key,$fields,$filters,$tables);
if (!$object) $this->exitWith404('object');
$this->startOutput();
echo json_encode($object);
}
protected function createCommand($parameters) {
extract($parameters);
if (!$inputs || !$inputs[0]) $this->exitWith404('input');
$this->startOutput();
if ($multi) echo json_encode($this->createObjects($inputs,$tables));
else echo json_encode($this->createObject($inputs[0],$tables));
}
protected function updateCommand($parameters) {
extract($parameters);
if (!$inputs || !$inputs[0]) $this->exitWith404('subject');
$this->startOutput();
if ($multi) echo json_encode($this->updateObjects($key,$inputs,$filters,$tables));
else echo json_encode($this->updateObject($key,$inputs[0],$filters,$tables));
}
protected function deleteCommand($parameters) {
extract($parameters);
$this->startOutput();
if ($multi) echo json_encode($this->deleteObjects($key,$filters,$tables));
else echo json_encode($this->deleteObject($key,$filters,$tables));
}
protected function incrementCommand($parameters) {
extract($parameters);
if (!$inputs || !$inputs[0]) $this->exitWith404('subject');
$this->startOutput();
if ($multi) echo json_encode($this->incrementObjects($key,$inputs,$filters,$tables,$fields));
else echo json_encode($this->incrementObject($key,$inputs[0],$filters,$tables,$fields));
}
protected function listCommand($parameters) {
extract($parameters);
$this->startOutput();
if ($transform) {
ob_start();
}
$this->listCommandInternal($parameters);
if ($transform) {
$content = ob_get_contents();
ob_end_clean();
$data = json_decode($content,true);
echo json_encode(self::php_crud_api_transform($data));
}
}
protected function retrievePostData() {
if ($_FILES) {
$files = array();
foreach ($_FILES as $name => $file) {
foreach ($file as $key => $value) {
switch ($key) {
case 'tmp_name': $files[$name] = $value?base64_encode(file_get_contents($value)):''; break;
default: $files[$name.'_'.$key] = $value;
}
}
}
return http_build_query(array_merge($files,$_POST));
}
return file_get_contents('php://input');
}
public function __construct($config) {
extract($config);
// initialize
$dbengine = isset($dbengine)?$dbengine:null;
$hostname = isset($hostname)?$hostname:null;
$username = isset($username)?$username:null;
$password = isset($password)?$password:null;
$database = isset($database)?$database:null;
$port = isset($port)?$port:null;
$socket = isset($socket)?$socket:null;
$charset = isset($charset)?$charset:null;
$table_authorizer = isset($table_authorizer)?$table_authorizer:null;
$record_filter = isset($record_filter)?$record_filter:null;
$column_authorizer = isset($column_authorizer)?$column_authorizer:null;
$tenancy_function = isset($tenancy_function)?$tenancy_function:null;
$input_sanitizer = isset($input_sanitizer)?$input_sanitizer:null;
$input_validator = isset($input_validator)?$input_validator:null;
$extensions = isset($extensions)?$extensions:null;
$auto_include = isset($auto_include)?$auto_include:null;
$allow_origin = isset($allow_origin)?$allow_origin:null;
$db = isset($db)?$db:null;
$method = isset($method)?$method:null;
$request = isset($request)?$request:null;
$get = isset($get)?$get:null;
$post = isset($post)?$post:null;
$origin = isset($origin)?$origin:null;
// defaults
if (!$dbengine) {
$dbengine = 'MySQL';
}
if (!$method) {
$method = $_SERVER['REQUEST_METHOD'];
}
if (!$request) {
$request = isset($_SERVER['PATH_INFO'])?$_SERVER['PATH_INFO']:'';
if (!$request) {
$request = isset($_SERVER['ORIG_PATH_INFO'])?$_SERVER['ORIG_PATH_INFO']:'';
}
}
if (!$get) {
$get = $_GET;
}
if (!$post) {
$post = $this->retrievePostData();
}
if (!$origin) {
$origin = isset($_SERVER['HTTP_ORIGIN'])?$_SERVER['HTTP_ORIGIN']:'';
}
// connect
$request = trim($request,'/');
if (!$database) {
$database = $this->parseRequestParameter($request, 'a-zA-Z0-9\-_');
}
if (!$db) {
$db = new $dbengine();
if (!$charset) {
$charset = $db->getDefaultCharset();
}
$db->connect($hostname,$username,$password,$database,$port,$socket,$charset);
}
if ($extensions===null) {
$extensions = true;
}
if ($auto_include===null) {
$auto_include = true;
}
if ($allow_origin===null) {
$allow_origin = '*';
}
$this->db = $db;
$this->settings = compact('method', 'request', 'get', 'post', 'origin', 'database', 'table_authorizer', 'record_filter', 'column_authorizer', 'tenancy_function', 'input_sanitizer', 'input_validator', 'extensions', 'auto_include', 'allow_origin');
}
public static function php_crud_api_transform(&$tables) {
$get_objects = function (&$tables,$table_name,$where_index=false,$match_value=false) use (&$get_objects) {
$objects = array();
if (isset($tables[$table_name]['records'])) {
foreach ($tables[$table_name]['records'] as $record) {
if ($where_index===false || $record[$where_index]==$match_value) {
$object = array();
foreach ($tables[$table_name]['columns'] as $index=>$column) {
$object[$column] = $record[$index];
foreach ($tables as $relation=>$reltable) {
if (isset($reltable['relations'])) {
foreach ($reltable['relations'] as $key=>$target) {
if ($target == "$table_name.$column") {
$column_indices = array_flip($reltable['columns']);
$object[$relation] = $get_objects($tables,$relation,$column_indices[$key],$record[$index]);
}
}
}
}
}
$objects[] = $object;
}
}
}
return $objects;
};
$tree = array();
foreach ($tables as $name=>$table) {
if (!isset($table['relations'])) {
$tree[$name] = $get_objects($tables,$name);
if (isset($table['results'])) {
$tree['_results'] = $table['results'];
}
}
}
return $tree;
}
protected function swagger($settings) {
extract($settings);
$tables = array();
if ($result = $this->db->query($this->db->getSql('list_tables'),array($database))) {
while ($row = $this->db->fetchRow($result)) {
$table = array(
'name'=>$row[0],
'comments'=>$row[1],
'root_actions'=>array(
array('name'=>'list','method'=>'get'),
array('name'=>'create','method'=>'post'),
),
'id_actions'=>array(
array('name'=>'read','method'=>'get'),
array('name'=>'update','method'=>'put'),
array('name'=>'delete','method'=>'delete'),
array('name'=>'increment','method'=>'patch'),
),
);
$tables[] = $table;
}
$this->db->close($result);
}
foreach ($tables as $t=>$table) {
$table_list = array($table['name']);
$table_fields = $this->findFields($table_list,false,$database);
$table_names = array_map(function($v){ return $v['name'];},$tables);
if ($extensions) {
$result = $this->db->query($this->db->getSql('reflect_belongs_to'),array($table_list[0],$table_names,$database,$database));
while ($row = $this->db->fetchRow($result)) {
$table_fields[$table['name']][$row[1]]->references=array($row[2],$row[3]);
}
$result = $this->db->query($this->db->getSql('reflect_has_many'),array($table_names,$table_list[0],$database,$database));
while ($row = $this->db->fetchRow($result)) {
$table_fields[$table['name']][$row[3]]->referenced[]=array($row[0],$row[1]);
}
$primaryKeys = $this->findPrimaryKeys($table_list[0],$database);
foreach ($primaryKeys as $primaryKey) {
$table_fields[$table['name']][$primaryKey]->primaryKey = true;
}
}
foreach (array('root_actions','id_actions') as $path) {
foreach ($table[$path] as $i=>$action) {
$table_list = array($table['name']);
$fields = $table_fields;
if ($table_authorizer) $this->applyTableAuthorizer($table_authorizer,$action['name'],$database,$table_list);
if ($column_authorizer) $this->applyColumnAuthorizer($column_authorizer,$action['name'],$database,$fields);
if (!$table_list || !$fields[$table['name']]) $tables[$t][$path][$i] = false;
else $tables[$t][$path][$i]['fields'] = $fields[$table['name']];
}
// remove unauthorized tables and tables without fields
$tables[$t][$path] = array_values(array_filter($tables[$t][$path]));
}
if (!$tables[$t]['root_actions']&&!$tables[$t]['id_actions']) $tables[$t] = false;
}
$tables = array_merge(array_filter($tables));
//var_dump($tables);die();
header('Content-Type: application/json; charset=utf-8');
echo '{"swagger":"2.0",';
echo '"info":{';
echo '"title":"'.$database.'",';
echo '"description":"API generated with [PHP-CRUD-API](https://github.com/mevdschee/php-crud-api)",';
echo '"version":"1.0.0"';
echo '},';
echo '"host":"'.$_SERVER['HTTP_HOST'].'",';
echo '"basePath":"'.$_SERVER['SCRIPT_NAME'].'",';
echo '"schemes":["http'.((!empty($_SERVER['HTTPS'])&&$_SERVER['HTTPS']!=='off')?'s':'').'"],';
echo '"consumes":["application/json"],';
echo '"produces":["application/json"],';
echo '"tags":[';
foreach ($tables as $i=>$table) {
if ($i>0) echo ',';
echo '{';
echo '"name":"'.$table['name'].'",';
echo '"description":"'.$table['comments'].'"';
echo '}';
}
echo '],';
echo '"paths":{';
foreach ($tables as $i=>$table) {
if ($table['root_actions']) {
if ($i>0) echo ',';
echo '"/'.$table['name'].'":{';
foreach ($table['root_actions'] as $j=>$action) {
if ($j>0) echo ',';
echo '"'.$action['method'].'":{';
echo '"tags":["'.$table['name'].'"],';
echo '"summary":"'.ucfirst($action['name']).'",';
if ($action['name']=='list') {
echo '"parameters":[';
echo '{';
echo '"name":"include",';
echo '"in":"query",';
echo '"description":"One or more related entities (comma separated).",';
echo '"required":false,';
echo '"type":"string"';
echo '},';
echo '{';
echo '"name":"order",';
echo '"in":"query",';
echo '"description":"Column you want to sort on and the sort direction (comma separated). Example: id,desc",';
echo '"required":false,';
echo '"type":"string"';
echo '},';
echo '{';
echo '"name":"page",';
echo '"in":"query",';
echo '"description":"Page number and page size (comma separated). NB: You cannot use \"page\" without \"order\"! Example: 1,10",';
echo '"required":false,';
echo '"type":"string"';
echo '},';
echo '{';
echo '"name":"transform",';
echo '"in":"query",';
echo '"description":"Transform the records to object format. NB: This can also be done client-side in JavaScript!",';
echo '"required":false,';
echo '"type":"boolean"';
echo '},';
echo '{';
echo '"name":"columns",';
echo '"in":"query",';
echo '"description":"The table columns you want to retrieve (comma separated). Example: posts.*,categories.name",';
echo '"required":false,';
echo '"type":"string"';
echo '},';
echo '{';
echo '"name":"filter[]",';
echo '"in":"query",';
echo '"description":"Filters to be applied. Each filter consists of a column, an operator and a value (comma separated). Example: id,eq,1",';
echo '"required":false,';
echo '"type":"array",';
echo '"collectionFormat":"multi",';
echo '"items":{"type":"string"}';
echo '},';
echo '{';
echo '"name":"satisfy",';
echo '"in":"query",';
echo '"description":"Should all filters match (default)? Or any?",';
echo '"required":false,';
echo '"type":"string",';
echo '"enum":["any"]';
echo '}';
echo '],';
echo '"responses":{';
echo '"200":{';
echo '"description":"An array of '.$table['name'].'",';
echo '"schema":{';
echo '"type":"array",';
echo '"items":{';
echo '"type": "object",';
echo '"properties": {';
foreach (array_keys($action['fields']) as $k=>$field) {
if ($k>0) echo ',';
echo '"'.$field.'": {';
echo '"type": "string"';
if (isset($action['fields'][$field]->referenced)) {
echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
}
if (isset($action['fields'][$field]->references)) {
echo ',"x-references": '.json_encode($action['fields'][$field]->references);
}
if (isset($action['fields'][$field]->primaryKey)) {
echo ',"x-primary-key": true';
}
echo '}';
}
echo '}'; //properties
echo '}'; //items
echo '}'; //schema
echo '}'; //200
echo '}'; //responses
}
if ($action['name']=='create') {
echo '"parameters":[{';
echo '"name":"item",';
echo '"in":"body",';
echo '"description":"Item to create.",';
echo '"required":false,';
echo '"schema":{';
echo '"type": "object",';
echo '"properties": {';
foreach (array_keys($action['fields']) as $k=>$field) {
if ($k>0) echo ',';
echo '"'.$field.'": {';
echo '"type": "string"';
if (isset($action['fields'][$field]->referenced)) {
echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
}
if (isset($action['fields'][$field]->references)) {
echo ',"x-references": '.json_encode($action['fields'][$field]->references);
}
if (isset($action['fields'][$field]->primaryKey)) {
echo ',"x-primary-key": true';
}
echo '}';
}
echo '}'; //properties
echo '}'; //schema
echo '}],';
echo '"responses":{';
echo '"200":{';
echo '"description":"Identifier of created item.",';
echo '"schema":{';
echo '"type":"integer"';
echo '}';//schema
echo '}';//200
echo '}';//responses
}
echo '}';//method
}
echo '}';
}
if ($table['id_actions']) {
if ($i>0 || $table['root_actions']) echo ',';
echo '"/'.$table['name'].'/{id}":{';
foreach ($table['id_actions'] as $j=>$action) {
if ($j>0) echo ',';
echo '"'.$action['method'].'":{';
echo '"tags":["'.$table['name'].'"],';
echo '"summary":"'.ucfirst($action['name']).'",';
echo '"parameters":[';
echo '{';
echo '"name":"id",';
echo '"in":"path",';
echo '"description":"Identifier for item.",';
echo '"required":true,';
echo '"type":"string"';
echo '}';
if ($action['name']=='update') {
echo ',{';
echo '"name":"item",';
echo '"in":"body",';
echo '"description":"Properties of item to update.",';
echo '"required":false,';
echo '"schema":{';
echo '"type": "object",';
echo '"properties": {';
foreach (array_keys($action['fields']) as $k=>$field) {
if ($k>0) echo ',';
echo '"'.$field.'": {';
echo '"type": "string"';
if (isset($action['fields'][$field]->referenced)) {
echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
}
if (isset($action['fields'][$field]->references)) {
echo ',"x-references": '.json_encode($action['fields'][$field]->references);
}
if (isset($action['fields'][$field]->primaryKey)) {
echo ',"x-primary-key": true';
}
echo '}';
}
echo '}'; //properties
echo '}'; //schema
echo '}';
}
echo '],';
if ($action['name']=='read') {
echo '"responses":{';
echo '"200":{';
echo '"description":"The requested item.",';
echo '"schema":{';
echo '"type": "object",';
echo '"properties": {';
foreach (array_keys($action['fields']) as $k=>$field) {
if ($k>0) echo ',';
echo '"'.$field.'": {';
echo '"type": "string"';
if (isset($action['fields'][$field]->referenced)) {
echo ',"x-referenced": '.json_encode($action['fields'][$field]->referenced);
}
if (isset($action['fields'][$field]->references)) {
echo ',"x-references": '.json_encode($action['fields'][$field]->references);
}
if (isset($action['fields'][$field]->primaryKey)) {
echo ',"x-primary-key": true';
}
echo '}';
}
echo '}'; //properties
echo '}'; //schema
echo '}';
echo '}';
} else {
echo '"responses":{';
echo '"200":{';
echo '"description":"Number of affected rows.",';
echo '"schema":{';
echo '"type":"integer"';
echo '}';
echo '}';
echo '}';
}
echo '}';
}
echo '}';
}
}
echo '}';
echo '}';
}
protected function allowOrigin($origin,$allowOrigins) {
if ($allowOrigins=='*') {
header('Access-Control-Allow-Origin: *');
} else {
if ($origin) foreach (explode(',',$allowOrigins) as $o) {
if (preg_match('/^'.str_replace('\*','.*',preg_quote(strtolower(trim($o)))).'$/',$origin)) {
header('Access-Control-Allow-Origin: '.$origin);
break;
}
}
}
}
public function executeCommand() {
if (isset($_SERVER['REQUEST_METHOD'])) {
$this->allowOrigin($this->settings['origin'],$this->settings['allow_origin']);
}
if (!$this->settings['request']) {
$this->swagger($this->settings);
} else {
$parameters = $this->getParameters($this->settings);
switch($parameters['action']){
case 'list': $this->listCommand($parameters); break;
case 'read': $this->readCommand($parameters); break;
case 'create': $this->createCommand($parameters); break;
case 'update': $this->updateCommand($parameters); break;
case 'delete': $this->deleteCommand($parameters); break;
case 'increment': $this->incrementCommand($parameters); break;
case 'headers': $this->headersCommand($parameters); break;
}
}
}
}
// require 'auth.php'; // from the PHP-API-AUTH project, see: https://github.com/mevdschee/php-api-auth
// uncomment the lines below for token+session based authentication (see "login_token.html" + "login_token.php"):
// $auth = new PHP_API_AUTH(array(
// 'secret'=>'someVeryLongPassPhraseChangeMe',
// ));
// if ($auth->executeCommand()) exit(0);
// if (empty($_SESSION['user']) || $_GET['csrf']!=$_SESSION['csrf']) {
// header('HTTP/1.0 401 Unauthorized');
// exit(0);
// }
// uncomment the lines below for form+session based authentication (see "login.html"):
// $auth = new PHP_API_AUTH(array(
// 'authenticator'=>function($user,$pass){ $_SESSION['user']=($user=='admin' && $pass=='admin'); }
// ));
// if ($auth->executeCommand()) exit(0);
// if (empty($_SESSION['user']) || $_GET['csrf']!=$_SESSION['csrf']) {
// header('HTTP/1.0 401 Unauthorized');
// exit(0);
// }
// uncomment the lines below when running in stand-alone mode:
// $api = new PHP_CRUD_API(array(
// 'dbengine'=>'MySQL',
// 'hostname'=>'localhost',
// 'username'=>'',
// 'password'=>'',
// 'database'=>'',
// 'charset'=>'utf8'
// ));
// $api->executeCommand();
// For Microsoft SQL Server 2012 use:
// $api = new PHP_CRUD_API(array(
// 'dbengine'=>'SQLServer',
// 'hostname'=>'(local)',
// 'username'=>'',
// 'password'=>'',
// 'database'=>'xxx',
// 'charset'=>'UTF-8'
// ));
// $api->executeCommand();
// For PostgreSQL 9 use:
// $api = new PHP_CRUD_API(array(
// 'dbengine'=>'PostgreSQL',
// 'hostname'=>'localhost',
// 'username'=>'xxx',
// 'password'=>'xxx',
// 'database'=>'xxx',
// 'charset'=>'UTF8'
// ));
// $api->executeCommand();
// For SQLite 3 use:
// $api = new PHP_CRUD_API(array(
// 'dbengine'=>'SQLite',
// 'database'=>'data/blog.db',
// ));
// $api->executeCommand();