You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
372 lines
7.1 KiB
372 lines
7.1 KiB
<?php |
|
|
|
class SafeMySQL |
|
{ |
|
private $conn; |
|
private $emode; |
|
private $exname; |
|
|
|
private $defaults = array( |
|
'host' => 'localhost', |
|
'user' => 'root', |
|
'pass' => '', |
|
'db' => 'test', |
|
'port' => NULL, |
|
'socket' => NULL, |
|
'pconnect' => FALSE, |
|
'charset' => 'utf8', |
|
'errmode' => 'error', //or exception |
|
'exception' => 'Exception', //Exception class name |
|
); |
|
|
|
const RESULT_ASSOC = MYSQLI_ASSOC; |
|
const RESULT_NUM = MYSQLI_NUM; |
|
|
|
function __construct($opt = array()) |
|
{ |
|
$opt = array_merge($this->defaults,$opt); |
|
|
|
$this->emode = $opt['errmode']; |
|
$this->exname = $opt['exception']; |
|
|
|
if ($opt['pconnect']) |
|
{ |
|
$opt['host'] = "p:".$opt['host']; |
|
} |
|
|
|
@$this->conn = mysqli_connect($opt['host'], $opt['user'], $opt['pass'], $opt['db'], $opt['port'], $opt['socket']); |
|
if ( !$this->conn ) |
|
{ |
|
$this->error(mysqli_connect_errno()." ".mysqli_connect_error()); |
|
} |
|
|
|
mysqli_set_charset($this->conn, $opt['charset']) or $this->error(mysqli_error($this->conn)); |
|
unset($opt); // I am paranoid |
|
} |
|
|
|
public function query() |
|
{ |
|
return $this->rawQuery($this->prepareQuery(func_get_args())); |
|
} |
|
|
|
public function fetch($result,$mode=self::RESULT_ASSOC) |
|
{ |
|
return mysqli_fetch_array($result, $mode); |
|
} |
|
|
|
public function affected_rows() |
|
{ |
|
return mysqli_affected_rows ($this->conn); |
|
} |
|
|
|
public function insert_id() |
|
{ |
|
return mysqli_insert_id($this->conn); |
|
} |
|
|
|
public function num_rows($result) |
|
{ |
|
return mysqli_num_rows($result); |
|
} |
|
|
|
public function free($result) |
|
{ |
|
mysqli_free_result($result); |
|
} |
|
|
|
public function getOne() |
|
{ |
|
$query = $this->prepareQuery(func_get_args()); |
|
if ( !preg_match('~LIMIT\s+\d+\s*(,\s*\d+\s*)?$~', $query) ) |
|
{ |
|
$query .= " LIMIT 1"; |
|
} |
|
if ($res = $this->rawQuery($query)) |
|
{ |
|
$row = $this->fetch($res); |
|
if (is_array($row)) { |
|
return reset($row); |
|
} |
|
$this->free($res); |
|
} |
|
return FALSE; |
|
} |
|
|
|
public function getRow() |
|
{ |
|
$query = $this->prepareQuery(func_get_args()); |
|
if ( !preg_match('~LIMIT\s+\d+\s*(,\s*\d+\s*)?$~',$query) ) |
|
{ |
|
$query.= " LIMIT 1"; |
|
} |
|
|
|
if ($res = $this->rawQuery($query)) { |
|
$ret = $this->fetch($res); |
|
$this->free($res); |
|
return $ret; |
|
} |
|
return FALSE; |
|
} |
|
|
|
public function getCol() |
|
{ |
|
$ret = array(); |
|
$query = $this->prepareQuery(func_get_args()); |
|
if ( $res = $this->rawQuery($query) ) |
|
{ |
|
while($row = $this->fetch($res)) |
|
{ |
|
$ret[] = reset($row); |
|
} |
|
$this->free($res); |
|
} |
|
return $ret; |
|
} |
|
|
|
public function getAll() |
|
{ |
|
$ret = array(); |
|
$query = $this->prepareQuery(func_get_args()); |
|
if ( $res = $this->rawQuery($query) ) |
|
{ |
|
while($row = $this->fetch($res)) |
|
{ |
|
$ret[] = $row; |
|
} |
|
$this->free($res); |
|
} |
|
return $ret; |
|
} |
|
|
|
public function getInd() |
|
{ |
|
$args = func_get_args(); |
|
$index = array_shift($args); |
|
$query = $this->prepareQuery($args); |
|
|
|
$ret = array(); |
|
if ( $res = $this->rawQuery($query) ) |
|
{ |
|
while($row = $this->fetch($res)) |
|
{ |
|
$ret[$row[$index]] = $row; |
|
} |
|
$this->free($res); |
|
} |
|
return $ret; |
|
} |
|
|
|
public function getIndCol() |
|
{ |
|
$args = func_get_args(); |
|
$index = array_shift($args); |
|
$query = $this->prepareQuery($args); |
|
|
|
$ret = array(); |
|
if ( $res = $this->rawQuery($query) ) |
|
{ |
|
while($row = $res->fetch($res)) |
|
{ |
|
$key = $row[$index]; |
|
unset($row[$index]); |
|
$ret[$key] = reset($row); |
|
} |
|
$this->free($res); |
|
} |
|
return $ret; |
|
} |
|
|
|
public function parse() |
|
{ |
|
return $this->prepareQuery(func_get_args()); |
|
} |
|
|
|
public function whiteList($input,$allowed,$strict=FALSE) |
|
{ |
|
$found = array_search($input); |
|
if ($strict && ($found === FALSE)) |
|
{ |
|
return FALSE; |
|
} else { |
|
return $allowed[$found]; |
|
} |
|
} |
|
|
|
public function filterArray($input,$allowed) |
|
{ |
|
foreach(array_keys($input) as $key ) |
|
{ |
|
if ( !in_array($key,$allowed) ) |
|
{ |
|
unset($input[$key]); |
|
} |
|
} |
|
return $input; |
|
} |
|
|
|
private function rawQuery($query) { |
|
$res = mysqli_query($this->conn, $query) or $this->error(mysqli_error($this->conn).". Full query: [$query]"); |
|
return $res; |
|
} |
|
|
|
private function prepareQuery($args) |
|
{ |
|
$raw = $query = array_shift($args); |
|
preg_match_all('~(\?[a-z?])~',$query,$m,PREG_OFFSET_CAPTURE); |
|
$pholders = $m[1]; |
|
$count = 0; |
|
foreach ($pholders as $i => $p) |
|
{ |
|
if ($p[0] != '??') |
|
{ |
|
$count++; |
|
} |
|
} |
|
if ( $count != count($args) ) |
|
{ |
|
$this->error("Number of args (".count($args).") doesn't match number of placeholders ($count) in [$raw]"); |
|
} |
|
$shift = 0; |
|
$qmarks = 0; |
|
foreach ($pholders as $i => $p) |
|
{ |
|
$pholder = $p[0]; |
|
$offset = $p[1] + $shift; |
|
if ($pholder != '??') |
|
{ |
|
$value = $args[$i-$qmarks]; |
|
} |
|
switch ($pholder) |
|
{ |
|
case '?n': |
|
$value = $this->escapeIdent($value); |
|
break; |
|
case '?s': |
|
$value = $this->escapeString($value); |
|
break; |
|
case '?i': |
|
$value = $this->escapeInt($value); |
|
break; |
|
case '?a': |
|
$value = $this->createIN($value); |
|
break; |
|
case '?u': |
|
$value = $this->createSET($value); |
|
break; |
|
case '??': |
|
$value = '?'; |
|
$qmarks++; |
|
break; |
|
case '?q': |
|
break; |
|
default: |
|
$this->error("Unknown placeholder type ($pholder) in [$raw]"); |
|
} |
|
$query = substr_replace($query,$value,$offset,2); |
|
$shift+= strlen($value) - strlen($pholder); |
|
} |
|
$this->lastquery = $query; |
|
return $query; |
|
} |
|
|
|
private function escapeInt($value) |
|
{ |
|
if (is_float($value)) |
|
{ |
|
return number_format($value, 0, '.', ''); // may lose precision on big numbers |
|
} |
|
elseif(is_numeric($value)) |
|
{ |
|
return (string)$value; |
|
} |
|
else |
|
{ |
|
$this->error("Invalid value for ?i (int) placeholder: [$value](".gettype($value).")"); |
|
} |
|
} |
|
|
|
private function escapeString($value) |
|
{ |
|
return "'".mysqli_real_escape_string($this->conn,$value)."'"; |
|
} |
|
|
|
private function escapeIdent($value) |
|
{ |
|
if ($value) |
|
{ |
|
return "`".str_replace("`","``",$value)."`"; |
|
} else { |
|
$this->error("Empty value for ?n (identifier) placeholder."); |
|
} |
|
} |
|
|
|
private function createIN($data) |
|
{ |
|
if (!is_array($data)) |
|
{ |
|
$this->error("Value for ?a (IN) placeholder should be array."); |
|
return; |
|
} |
|
if (!$data) |
|
{ |
|
return 'NULL'; |
|
} |
|
$query = $comma = ''; |
|
foreach ($data as $key => $value) |
|
{ |
|
$query .= $comma.$this->escapeString($value); |
|
$comma = ","; |
|
} |
|
return $query; |
|
} |
|
|
|
private function createSET($data) |
|
{ |
|
if (!is_array($data)) |
|
{ |
|
$this->error("Value for ?u (SET) placeholder should be an array. ".gettype($data)." passed instead."); |
|
return; |
|
} |
|
if (!$data) |
|
{ |
|
$this->error("Empty array for ?u (SET) placeholder."); |
|
return; |
|
} |
|
$query = $comma = ''; |
|
foreach ($data as $key => $value) |
|
{ |
|
$query .= $comma.$this->escapeIdent($key).'='.$this->escapeString($value); |
|
$comma = ","; |
|
} |
|
return $query; |
|
} |
|
|
|
private function error($err) |
|
{ |
|
$err = __CLASS__.": ".$err; |
|
|
|
if ( $this->emode == 'error' ) |
|
{ |
|
$err .= ". Error initiated in ".$this->caller().", thrown"; |
|
trigger_error($err,E_USER_ERROR); |
|
} else { |
|
throw new $this->exname($err); |
|
} |
|
} |
|
|
|
private function caller() |
|
{ |
|
$trace = debug_backtrace(); |
|
$caller = ''; |
|
foreach ($trace as $t) |
|
{ |
|
if ( isset($t['class']) && $t['class'] == __CLASS__ ) |
|
{ |
|
$caller = $t['file']." on line ".$t['line']; |
|
} else { |
|
break; |
|
} |
|
} |
|
return $caller; |
|
} |
|
}
|
|
|