502 lines
13 KiB
PHP
502 lines
13 KiB
PHP
|
<?php
|
||
|
/* Copyright (c) by Hugo Leisink <hugo@leisink.net>
|
||
|
* This file is part of the Banshee PHP framework
|
||
|
* https://www.banshee-php.org/
|
||
|
*
|
||
|
* Licensed under The MIT License
|
||
|
*/
|
||
|
namespace lib\db;
|
||
|
|
||
|
abstract class database_connection {
|
||
|
protected $link = null;
|
||
|
protected $db_close = null;
|
||
|
protected $db_escape_string = null;
|
||
|
protected $db_query = null;
|
||
|
protected $db_fetch = null;
|
||
|
protected $db_free_result = null;
|
||
|
protected $db_insert_id = null;
|
||
|
protected $db_affected_rows = null;
|
||
|
protected $db_error = null;
|
||
|
protected $db_errno = null;
|
||
|
protected $id_delim = null;
|
||
|
private $read_only = false;
|
||
|
private $insert_id_history = array();
|
||
|
|
||
|
/* Desctructor
|
||
|
*
|
||
|
* INPUT: -
|
||
|
* OUTPUT: -
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
public function __destruct() {
|
||
|
if ($this->link !== null) {
|
||
|
call_user_func($this->db_close, $this->link);
|
||
|
$this->link = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Magic method get
|
||
|
*
|
||
|
* INPUT: string key
|
||
|
* OUTPUT: mixed value
|
||
|
* ERROR: null
|
||
|
*/
|
||
|
public function __get($key) {
|
||
|
switch ($key) {
|
||
|
case "connected":
|
||
|
return $this->link !== null;
|
||
|
case "last_insert_id":
|
||
|
return $this->last_insert_id(0);
|
||
|
case "affected_rows":
|
||
|
if ($this->db_affected_rows !== null) {
|
||
|
return call_user_func($this->db_affected_rows, $this->link);
|
||
|
}
|
||
|
break;
|
||
|
case "error":
|
||
|
if ($this->db_error !== null) {
|
||
|
return call_user_func($this->db_error, $this->link);
|
||
|
}
|
||
|
break;
|
||
|
case "errno":
|
||
|
if ($this->db_errno !== null) {
|
||
|
return call_user_func($this->db_errno, $this->link);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/* Make database connection read-only
|
||
|
*/
|
||
|
public function make_read_only() {
|
||
|
$this->read_only = true;
|
||
|
}
|
||
|
|
||
|
/* Flatten array to new array with depth 1
|
||
|
*
|
||
|
* INPUT: array data
|
||
|
* OUTPUT: array data
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
protected function flatten_array($data) {
|
||
|
$result = array();
|
||
|
foreach ($data as $item) {
|
||
|
if (is_array($item)) {
|
||
|
$result = array_merge($result, $this->flatten_array($item));
|
||
|
} else {
|
||
|
array_push($result, $item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/* Delimit an identifier
|
||
|
*
|
||
|
* INPUT: string identifier
|
||
|
* OUTPUT: string delimited identifier
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
protected function delimit_identifier($identifier) {
|
||
|
if ($this->id_delim === null) {
|
||
|
return $identifier;
|
||
|
} else if (is_array($this->id_delim)) {
|
||
|
return $this->id_delim[0].$identifier.$this->id_delim[1];
|
||
|
} else {
|
||
|
return $this->id_delim.$identifier.$this->id_delim;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Validates a table name
|
||
|
*
|
||
|
* INPUT: string table
|
||
|
* OUTPUT: bool valid table name
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
private function valid_table_name($table) {
|
||
|
static $table_name_chars = null;
|
||
|
|
||
|
if ($table == "") {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($table_name_chars === null) {
|
||
|
$table_name_chars = str_split("-_".
|
||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".
|
||
|
"abcdefghijklmnopqrstuvwxyz");
|
||
|
}
|
||
|
|
||
|
$diff = array_diff(str_split($table), $table_name_chars);
|
||
|
|
||
|
return count($diff) == 0;
|
||
|
}
|
||
|
|
||
|
/* Return printf format for variable
|
||
|
*
|
||
|
* INPUT: mixed variable
|
||
|
* OUTPUT: string printf format
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
private function type_to_format($variable) {
|
||
|
if (is_integer($variable)) {
|
||
|
return "%d";
|
||
|
}
|
||
|
if (is_float($variable)) {
|
||
|
return "%f";
|
||
|
}
|
||
|
if (is_bool($variable)) {
|
||
|
return "%d";
|
||
|
}
|
||
|
|
||
|
return "%s";
|
||
|
}
|
||
|
|
||
|
/* Returns the id of the latest created record
|
||
|
*
|
||
|
* INPUT: [int history offset, [resource query resource]
|
||
|
* OUTPUT: int insert identifier
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function last_insert_id($history = null, $resource = null) {
|
||
|
$param = ($resource !== null) ? $resource : $this->link;
|
||
|
|
||
|
if ($history !== null) {
|
||
|
$size = count($this->insert_id_history);
|
||
|
return $history < $size ? $this->insert_id_history[(int)$history] : 0;
|
||
|
} else if ($this->db_insert_id == null) {
|
||
|
return false;
|
||
|
} else if (($last_id = call_user_func($this->db_insert_id, $param)) == 0) {
|
||
|
return $this->last_insert_id(0);
|
||
|
} else {
|
||
|
return $last_id;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Create an SQL query by securely inserting the parameters in the query string
|
||
|
*
|
||
|
* INPUT: string query[, mixed query parameter, ...]
|
||
|
* OUTPUT: string
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
protected function make_query() {
|
||
|
if (func_num_args() == 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$args = func_get_args();
|
||
|
$format = array_shift($args);
|
||
|
$values = $this->flatten_array($args);
|
||
|
unset($args);
|
||
|
|
||
|
/* Security checks
|
||
|
*/
|
||
|
foreach (array("'", "`", "\"") as $char) {
|
||
|
if (strpos($format, $char) !== false) {
|
||
|
print $format."\nQuery not accepted.\n";
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$format = str_replace("%s", "'%s'", $format);
|
||
|
$format = str_replace("%S", $this->delimit_identifier("%s"), $format);
|
||
|
|
||
|
/* Escape arguments
|
||
|
*/
|
||
|
foreach ($values as $key => $value) {
|
||
|
$values[$key] = call_user_func($this->db_escape_string, $value);
|
||
|
}
|
||
|
|
||
|
/* Apply arguments to format
|
||
|
*/
|
||
|
return vsprintf($format, $values);
|
||
|
}
|
||
|
|
||
|
/* Execute an SQL query and return the resource identifier
|
||
|
*
|
||
|
* INPUT: string query[, mixed query parameter, ...]
|
||
|
* OUTPUT: mixed resource identifier
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function query() {
|
||
|
static $notified = false;
|
||
|
|
||
|
if ($this->connected == false) {
|
||
|
if ($notified == false) {
|
||
|
printf("%s: not connected to the database!\n", get_class($this));
|
||
|
$notified = true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$args = func_get_args();
|
||
|
if (($query = call_user_func_array(array($this, "make_query"), $args)) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($this->read_only) {
|
||
|
$parts = explode(" ", ltrim($args[0]), 2);
|
||
|
$statement = strtolower(array_shift($parts));
|
||
|
if (in_array($statement, array("select", "show")) == false) {
|
||
|
print "Dropped query that tried to alter the database via a read-only database connection.\n";
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$result = call_user_func($this->db_query, $query, $this->link);
|
||
|
if ($result === false) {
|
||
|
//print "SQL query: ".$query."\n";
|
||
|
//print "Error message: ".$this->error."\n";
|
||
|
} else if (($insert_id = $this->last_insert_id()) != 0) {
|
||
|
array_unshift($this->insert_id_history, $insert_id);
|
||
|
}
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/* Fetch one item from result by resource
|
||
|
*
|
||
|
* INPUT: mixed resource identifier
|
||
|
* OUTPUT: array( string key => string value[, ...] )|null
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function fetch($resource) {
|
||
|
if (in_array($resource, array(null, false, true), true)) {
|
||
|
$result = false;
|
||
|
}
|
||
|
|
||
|
return call_user_func($this->db_fetch, $resource);
|
||
|
}
|
||
|
|
||
|
/* Free query result memory
|
||
|
*
|
||
|
* INPUT: mixed resource identifier
|
||
|
* OUTPUT: true
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function free_result($resource) {
|
||
|
if ($this->db_free_result === null) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return call_user_func($this->db_free_result, $resource);
|
||
|
}
|
||
|
|
||
|
/* Execute an SQL query and return the result
|
||
|
*
|
||
|
* INPUT: string query[, mixed query parameter, ...]
|
||
|
* OUTPUT: mixed result for 'select' and 'show' queries / int affected rows
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function execute() {
|
||
|
$args = func_get_args();
|
||
|
|
||
|
if (($resource = call_user_func_array(array($this, "query"), $args)) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$parts = explode(" ", ltrim($args[0]), 2);
|
||
|
$statement = strtolower(array_shift($parts));
|
||
|
|
||
|
if (in_array($statement, array("select", "show", "describe", "explain"))) {
|
||
|
$result = array();
|
||
|
while (($data = $this->fetch($resource)) != false) {
|
||
|
array_push($result, $data);
|
||
|
}
|
||
|
$this->free_result($resource);
|
||
|
|
||
|
return $result;
|
||
|
} else if ($this->db_affected_rows !== null) {
|
||
|
return call_user_func($this->db_affected_rows, $this->link);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Execute queries via transaction
|
||
|
*
|
||
|
* INPUT: array( array( string query[, mixed query parameter, ...] )[, ...] )
|
||
|
* OUTPUT: boolean transaction succesful
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
public function transaction($queries) {
|
||
|
$this->query("begin");
|
||
|
|
||
|
foreach ($queries as $args) {
|
||
|
if (is_array($args) == false) {
|
||
|
$query = $args;
|
||
|
$args = array();
|
||
|
} else {
|
||
|
$query = array_shift($args);
|
||
|
}
|
||
|
|
||
|
/* Handle insert ids
|
||
|
*/
|
||
|
if (strpos($query, "INSERT_ID") !== false) {
|
||
|
$query = str_replace("{LAST_INSERT_ID}", $this->last_insert_id, $query);
|
||
|
|
||
|
$history_size = count($this->insert_id_history);
|
||
|
for ($q = 0; $q < $history_size; $q++) {
|
||
|
$query = str_replace("{INSERT_ID_".$q."}", $this->last_insert_id($q), $query);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Execute query
|
||
|
*/
|
||
|
if ($this->query($query, $args) === false) {
|
||
|
$this->query("rollback");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $this->query("commit") !== false;
|
||
|
}
|
||
|
|
||
|
/* Retrieve an entry from a table
|
||
|
*
|
||
|
* INPUT: string table, int entry identifier
|
||
|
* OUTPUT: array( value[, ....] )
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function entry($table, $id, $col = "id") {
|
||
|
$type = (is_int($id) || ($col == "id")) ? "%d" : "%s";
|
||
|
|
||
|
$query = "select * from %S where %S=".$type." limit 1";
|
||
|
if (($resource = $this->query($query, $table, $col, $id)) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return $this->fetch($resource);
|
||
|
}
|
||
|
|
||
|
/* Retorna true si un campo existe
|
||
|
*
|
||
|
* INPUT: string table, array( string column name[, ...] ) , array( column => value, ... ), string , int
|
||
|
* OUTPUT: mixed string or false
|
||
|
* ERROR: -
|
||
|
*/
|
||
|
public function exist($table, $uniques, $target, $id_target = false, $id = false ) {
|
||
|
if(is_array($uniques)){
|
||
|
foreach ($uniques as $key => $value) {
|
||
|
if(isset($target[$value])){
|
||
|
$type = ( is_int($target[$value]) ) ? "%d" : "%s";
|
||
|
$query = "select %S from %S where %S=".$type." ";
|
||
|
if ( $id_target and $id ) {
|
||
|
$type = ( is_int($id) ) ? "%d" : "%s";
|
||
|
$query .="and %S!=".$type." limit 1";
|
||
|
$resource = $this->query($query, $value, $table, $value, $target[$value], $id_target, $id);
|
||
|
}else{
|
||
|
$query .= " limit 1";
|
||
|
$resource = $this->query($query, $value, $table, $value, $target[$value]);
|
||
|
}
|
||
|
if ( $this->fetch($resource)[$value] ) {
|
||
|
return $value;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Insert new entry in table
|
||
|
*
|
||
|
* INPUT: string table, array( string value[, ...] ), array( string column name[, ...] )
|
||
|
* OUTPUT: true|integer inserted rows
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function insert($table, $data, $keys = null) {
|
||
|
// if ($this->valid_table_name($table) == false) {
|
||
|
// return false;
|
||
|
// }
|
||
|
|
||
|
if ($keys == null) {
|
||
|
$keys = array_keys($data);
|
||
|
}
|
||
|
|
||
|
$format = $values = array();
|
||
|
foreach ($keys as $key) {
|
||
|
if ($data[$key] === null) {
|
||
|
array_push($format, "null");
|
||
|
} else {
|
||
|
array_push($format, $this->type_to_format($data[$key]));
|
||
|
array_push($values, $data[$key]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$columns = implode(", ", array_fill(1, count($keys), "%S"));
|
||
|
|
||
|
$query = "insert into %S (".$columns.") values (".implode(", ", $format).")";
|
||
|
if ($this->query($query, $table, $keys, $values) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($this->db_affected_rows != null) {
|
||
|
return call_user_func($this->db_affected_rows, $this->link);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Update entry in table
|
||
|
*
|
||
|
* INPUT: string table, int entry identifier, array( mixed value[, ...] ), array( string column name[, ...] )
|
||
|
* OUTPUT: true|integer updated rows
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function update($table, $id, $index, $data, $keys = null) {
|
||
|
// if ($this->valid_table_name($table) == false) {
|
||
|
// return false;
|
||
|
// }
|
||
|
|
||
|
if ($keys === null) {
|
||
|
$keys = array_keys($data);
|
||
|
}
|
||
|
|
||
|
$format = $values = array();
|
||
|
foreach ($keys as $key) {
|
||
|
if (empty($data[$key]) or $data[$key] === null) {
|
||
|
array_push($format, "%S=null");
|
||
|
array_push($values, $key);
|
||
|
} else {
|
||
|
array_push($format, "%S=".$this->type_to_format($data[$key]));
|
||
|
array_push($values, $key, $data[$key]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$query = "update %S set ".implode(", ", $format)." where $index=%d";
|
||
|
if ($this->query($query, $table, $values, $id) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($this->db_affected_rows != null) {
|
||
|
return call_user_func($this->db_affected_rows, $this->link);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Delete entry from table
|
||
|
*
|
||
|
* INPUT: string table, int entry identifier
|
||
|
* OUTPUT: true|integer deleted rows
|
||
|
* ERROR: false
|
||
|
*/
|
||
|
public function delete($table, $index, $id) {
|
||
|
if ($this->valid_table_name($table) == false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$query = "delete from %S where $index=%d";
|
||
|
if ($this->query($query, $table, $id) === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ($this->db_affected_rows != null) {
|
||
|
return call_user_func($this->db_affected_rows, $this->link);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
?>
|