diff --git a/safemysql.class.php b/safemysql.class.php index 85e759b..d022fd5 100644 --- a/safemysql.class.php +++ b/safemysql.class.php @@ -1,4 +1,5 @@ 'user', - * 'pass' => 'pass', - * 'db' => 'db', - * 'charset' => 'latin1' + * 'user' => 'user', + * 'pass' => 'pass', + * 'db' => 'db', + * 'charset' => 'latin1' * ); * $db = new SafeMySQL($opts); // with some of the default settings overwritten * @@ -65,592 +66,577 @@ * $data = $db->getAll("SELECT * FROM table WHERE ?p", $bar, $sqlpart); * */ +class SafeMySQL { +#########################ТВИКИ ОТ ЗУМА###################### + + /** + * удаляет строки из таблицы, принимает массив вида + * 0=>имя таблицы + * 1=>поле в таблице + * 2=>чему равно + * + * @param array $array + */ + public function removeRows($array) { + foreach ($array as $task) { + $this->query("DELETE FROM ?n WHERE ?n = ?i", $task[0], $task[1], $task[2]); + //$wpdb->delete($task[0], array($task[1] => $task[2])); + } + } + + /** + * Возвращает sql для объединения таблиц + * Принимает массива вида + * array(array('name'=>'table1','field'=>'field1'),array('name'=>'table2','field'=>'field2')); + * + * @param string $sql + */ + public function selectLeftJoin($tables) { + $sql = "SELECT * FROM `" . $tables . "`"; + foreach ($ons as $k => $table) { + $sql .= " LEFT JOIN `" . $table[0]['name'] . "` ON `" . $table[1]['name'] . "`.`" . $table[1]['field'] . "`=`" . $table[0]['name'] . "`.`" . $table[0]['field'] . "`"; + } + return $sql; + } + + /** + * Возвращает стандартную функцию обновления + * + * @return string UPDATE ?n SET ?u WHERE `id` = ?i + */ + public function updTempl() { + return "UPDATE ?n SET ?u WHERE `id` = ?i"; + } + + /** + * Возвращает стандартную функцию вставки + * + * @return string INSERT INTO ?n SET ?u + */ + public function insTempl() { + return "INSERT INTO ?n SET ?u"; + } + +############################################################ + + protected $conn; + protected $stats; + protected $emode; + protected $exname; + protected $defaults = array( + 'host' => 'localhost', + 'user' => 'root', + 'pass' => '', + 'db' => 'test', + 'port' => NULL, + 'socket' => NULL, + 'pconnect' => FALSE, + 'charset' => 'utf8', + 'errmode' => 'exception', //or 'error' + '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 (isset($opt['mysqli'])) { + if ($opt['mysqli'] instanceof mysqli) { + $this->conn = $opt['mysqli']; + return; + } else { + + $this->error("mysqli option must be valid instance of mysqli class"); + } + } + + 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 + } + + /** + * Conventional function to run a query with placeholders. A mysqli_query wrapper with placeholders support + * + * Examples: + * $db->query("DELETE FROM table WHERE id=?i", $id); + * + * @param string $query - an SQL query with placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the query + * @return resource|FALSE whatever mysqli_query returns + */ + public function query() { + return $this->rawQuery($this->prepareQuery(func_get_args())); + } + + /** + * Conventional function to fetch single row. + * + * @param resource $result - myqli result + * @param int $mode - optional fetch mode, RESULT_ASSOC|RESULT_NUM, default RESULT_ASSOC + * @return array|FALSE whatever mysqli_fetch_array returns + */ + public function fetch($result, $mode = self::RESULT_ASSOC) { + return mysqli_fetch_array($result, $mode); + } + + /** + * Conventional function to get number of affected rows. + * + * @return int whatever mysqli_affected_rows returns + */ + public function affectedRows() { + return mysqli_affected_rows($this->conn); + } + + /** + * Conventional function to get last insert id. + * + * @return int whatever mysqli_insert_id returns + */ + public function insertId() { + return mysqli_insert_id($this->conn); + } + + /** + * Conventional function to get number of rows in the resultset. + * + * @param resource $result - myqli result + * @return int whatever mysqli_num_rows returns + */ + public function numRows($result) { + return mysqli_num_rows($result); + } + + /** + * Conventional function to free the resultset. + */ + public function free($result) { + mysqli_free_result($result); + } + + /** + * Helper function to get scalar value right out of query and optional arguments + * + * Examples: + * $name = $db->getOne("SELECT name FROM table WHERE id=1"); + * $name = $db->getOne("SELECT name FROM table WHERE id=?i", $id); + * + * @param string $query - an SQL query with placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the query + * @return string|FALSE either first column of the first row of resultset or FALSE if none found + */ + public function getOne() { + $query = $this->prepareQuery(func_get_args()); + if ($res = $this->rawQuery($query)) { + $row = $this->fetch($res); + if (is_array($row)) { + return reset($row); + } + $this->free($res); + } + return FALSE; + } + + /** + * Helper function to get single row right out of query and optional arguments + * + * Examples: + * $data = $db->getRow("SELECT * FROM table WHERE id=1"); + * $data = $db->getRow("SELECT * FROM table WHERE id=?i", $id); + * + * @param string $query - an SQL query with placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the query + * @return array|FALSE either associative array contains first row of resultset or FALSE if none found + */ + public function getRow() { + $query = $this->prepareQuery(func_get_args()); + if ($res = $this->rawQuery($query)) { + $ret = $this->fetch($res); + $this->free($res); + return $ret; + } + return FALSE; + } + + /** + * Helper function to get single column right out of query and optional arguments + * + * Examples: + * $ids = $db->getCol("SELECT id FROM table WHERE cat=1"); + * $ids = $db->getCol("SELECT id FROM tags WHERE tagname = ?s", $tag); + * + * @param string $query - an SQL query with placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the query + * @return array|FALSE either enumerated array of first fields of all rows of resultset or FALSE if none found + */ + 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; + } + + /** + * Helper function to get all the rows of resultset right out of query and optional arguments + * + * Examples: + * $data = $db->getAll("SELECT * FROM table"); + * $data = $db->getAll("SELECT * FROM table LIMIT ?i,?i", $start, $rows); + * + * @param string $query - an SQL query with placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the query + * @return array enumerated 2d array contains the resultset. Empty if no rows found. + */ + 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; + } + + /** + * Helper function to get all the rows of resultset into indexed array right out of query and optional arguments + * + * Examples: + * $data = $db->getInd("id", "SELECT * FROM table"); + * $data = $db->getInd("id", "SELECT * FROM table LIMIT ?i,?i", $start, $rows); + * + * @param string $index - name of the field which value is used to index resulting array + * @param string $query - an SQL query with placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the query + * @return array - associative 2d array contains the resultset. Empty if no rows found. + */ + 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; + } + + /** + * Helper function to get a dictionary-style array right out of query and optional arguments + * + * Examples: + * $data = $db->getIndCol("name", "SELECT name, id FROM cities"); + * + * @param string $index - name of the field which value is used to index resulting array + * @param string $query - an SQL query with placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the query + * @return array - associative array contains key=value pairs out of resultset. Empty if no rows found. + */ + public function getIndCol() { + $args = func_get_args(); + $index = array_shift($args); + $query = $this->prepareQuery($args); + + $ret = array(); + if ($res = $this->rawQuery($query)) { + while ($row = $this->fetch($res)) { + $key = $row[$index]; + unset($row[$index]); + $ret[$key] = reset($row); + } + $this->free($res); + } + return $ret; + } + + /** + * Function to parse placeholders either in the full query or a query part + * unlike native prepared statements, allows ANY query part to be parsed + * + * useful for debug + * and EXTREMELY useful for conditional query building + * like adding various query parts using loops, conditions, etc. + * already parsed parts have to be added via ?p placeholder + * + * Examples: + * $query = $db->parse("SELECT * FROM table WHERE foo=?s AND bar=?s", $foo, $bar); + * echo $query; + * + * if ($foo) { + * $qpart = $db->parse(" AND foo=?s", $foo); + * } + * $data = $db->getAll("SELECT * FROM table WHERE bar=?s ?p", $bar, $qpart); + * + * @param string $query - whatever expression contains placeholders + * @param mixed $arg,... unlimited number of arguments to match placeholders in the expression + * @return string - initial expression with placeholders substituted with data. + */ + public function parse() { + return $this->prepareQuery(func_get_args()); + } + + /** + * function to implement whitelisting feature + * sometimes we can't allow a non-validated user-supplied data to the query even through placeholder + * especially if it comes down to SQL OPERATORS + * + * Example: + * + * $order = $db->whiteList($_GET['order'], array('name','price')); + * $dir = $db->whiteList($_GET['dir'], array('ASC','DESC')); + * if (!$order || !dir) { + * throw new http404(); //non-expected values should cause 404 or similar response + * } + * $sql = "SELECT * FROM table ORDER BY ?p ?p LIMIT ?i,?i" + * $data = $db->getArr($sql, $order, $dir, $start, $per_page); + * + * @param string $iinput - field name to test + * @param array $allowed - an array with allowed variants + * @param string $default - optional variable to set if no match found. Default to false. + * @return string|FALSE - either sanitized value or FALSE + */ + public function whiteList($input, $allowed, $default = FALSE) { + $found = array_search($input, $allowed); + return ($found === FALSE) ? $default : $allowed[$found]; + } + + /** + * function to filter out arrays, for the whitelisting purposes + * useful to pass entire superglobal to the INSERT or UPDATE query + * OUGHT to be used for this purpose, + * as there could be fields to which user should have no access to. + * + * Example: + * $allowed = array('title','url','body','rating','term','type'); + * $data = $db->filterArray($_POST,$allowed); + * $sql = "INSERT INTO ?n SET ?u"; + * $db->query($sql,$table,$data); + * + * @param array $input - source array + * @param array $allowed - an array with allowed field names + * @return array filtered out source array + */ + public function filterArray($input, $allowed) { + foreach (array_keys($input) as $key) { + if (!in_array($key, $allowed)) { + unset($input[$key]); + } + } + return $input; + } + + /** + * Function to get last executed query. + * + * @return string|NULL either last executed query or NULL if were none + */ + public function lastQuery() { + $last = end($this->stats); + return $last['query']; + } + + /** + * Function to get all query statistics. + * + * @return array contains all executed queries with timings and errors + */ + public function getStats() { + return $this->stats; + } + + /** + * protected function which actually runs a query against Mysql server. + * also logs some stats like profiling info and error message + * + * @param string $query - a regular SQL query + * @return mysqli result resource or FALSE on error + */ + protected function rawQuery($query) { + $start = microtime(TRUE); + $res = mysqli_query($this->conn, $query); + $timer = microtime(TRUE) - $start; + + $this->stats[] = array( + 'query' => $query, + 'start' => $start, + 'timer' => $timer, + ); + if (!$res) { + $error = mysqli_error($this->conn); + + end($this->stats); + $key = key($this->stats); + $this->stats[$key]['error'] = $error; + $this->cutStats(); + + $this->error("$error. Full query: [$query]"); + } + $this->cutStats(); + return $res; + } + + protected function prepareQuery($args) { + $query = ''; + $raw = array_shift($args); + $array = preg_split('~(\?[nsiuap])~u', $raw, null, PREG_SPLIT_DELIM_CAPTURE); + $anum = count($args); + $pnum = floor(count($array) / 2); + if ($pnum != $anum) { + $this->error("Number of args ($anum) doesn't match number of placeholders ($pnum) in [$raw]"); + } + + foreach ($array as $i => $part) { + if (($i % 2) == 0) { + $query .= $part; + continue; + } + + $value = array_shift($args); + switch ($part) { + case '?n': + $part = $this->escapeIdent($value); + break; + case '?s': + $part = $this->escapeString($value); + break; + case '?i': + $part = $this->escapeInt($value); + break; + case '?a': + $part = $this->createIN($value); + break; + case '?u': + $part = $this->createSET($value); + break; + case '?p': + $part = $value; + break; + } + $query .= $part; + } + return $query; + } + + protected function escapeInt($value) { + if ($value === NULL) { + return 'NULL'; + } + if (!is_numeric($value)) { + $this->error("Integer (?i) placeholder expects numeric value, " . gettype($value) . " given"); + return FALSE; + } + if (is_float($value)) { + $value = number_format($value, 0, '.', ''); // may lose precision on big numbers + } + return $value; + } + + protected function escapeString($value) { + if ($value === NULL) { + return 'NULL'; + } + return "'" . mysqli_real_escape_string($this->conn, $value) . "'"; + } + + protected function escapeIdent($value) { + if ($value) { + return "`" . str_replace("`", "``", $value) . "`"; + } else { + $this->error("Empty value for identifier (?n) placeholder"); + } + } + + protected function createIN($data) { + if (!is_array($data)) { + $this->error("Value for IN (?a) placeholder should be array"); + return; + } + if (!$data) { + return 'NULL'; + } + $query = $comma = ''; + foreach ($data as $value) { + $query .= $comma . $this->escapeString($value); + $comma = ","; + } + return $query; + } + + protected function createSET($data) { + if (!is_array($data)) { + $this->error("SET (?u) placeholder expects array, " . gettype($data) . " given"); + return; + } + if (!$data) { + $this->error("Empty array for SET (?u) placeholder"); + return; + } + $query = $comma = ''; + foreach ($data as $key => $value) { + $query .= $comma . $this->escapeIdent($key) . '=' . $this->escapeString($value); + $comma = ","; + } + return $query; + } + + protected 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); + } + } + + protected 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; + } + + /** + * On a long run we can eat up too much memory with mere statsistics + * Let's keep it at reasonable size, leaving only last 100 entries. + */ + protected function cutStats() { + if (count($this->stats) > 100) { + reset($this->stats); + $first = key($this->stats); + unset($this->stats[$first]); + } + } -class SafeMySQL -{ - - protected $conn; - protected $stats; - protected $emode; - protected $exname; - - protected $defaults = array( - 'host' => 'localhost', - 'user' => 'root', - 'pass' => '', - 'db' => 'test', - 'port' => NULL, - 'socket' => NULL, - 'pconnect' => FALSE, - 'charset' => 'utf8', - 'errmode' => 'exception', //or 'error' - '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 (isset($opt['mysqli'])) - { - if ($opt['mysqli'] instanceof mysqli) - { - $this->conn = $opt['mysqli']; - return; - - } else { - - $this->error("mysqli option must be valid instance of mysqli class"); - } - } - - 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 - } - - /** - * Conventional function to run a query with placeholders. A mysqli_query wrapper with placeholders support - * - * Examples: - * $db->query("DELETE FROM table WHERE id=?i", $id); - * - * @param string $query - an SQL query with placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the query - * @return resource|FALSE whatever mysqli_query returns - */ - public function query() - { - return $this->rawQuery($this->prepareQuery(func_get_args())); - } - - /** - * Conventional function to fetch single row. - * - * @param resource $result - myqli result - * @param int $mode - optional fetch mode, RESULT_ASSOC|RESULT_NUM, default RESULT_ASSOC - * @return array|FALSE whatever mysqli_fetch_array returns - */ - public function fetch($result,$mode=self::RESULT_ASSOC) - { - return mysqli_fetch_array($result, $mode); - } - - /** - * Conventional function to get number of affected rows. - * - * @return int whatever mysqli_affected_rows returns - */ - public function affectedRows() - { - return mysqli_affected_rows ($this->conn); - } - - /** - * Conventional function to get last insert id. - * - * @return int whatever mysqli_insert_id returns - */ - public function insertId() - { - return mysqli_insert_id($this->conn); - } - - /** - * Conventional function to get number of rows in the resultset. - * - * @param resource $result - myqli result - * @return int whatever mysqli_num_rows returns - */ - public function numRows($result) - { - return mysqli_num_rows($result); - } - - /** - * Conventional function to free the resultset. - */ - public function free($result) - { - mysqli_free_result($result); - } - - /** - * Helper function to get scalar value right out of query and optional arguments - * - * Examples: - * $name = $db->getOne("SELECT name FROM table WHERE id=1"); - * $name = $db->getOne("SELECT name FROM table WHERE id=?i", $id); - * - * @param string $query - an SQL query with placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the query - * @return string|FALSE either first column of the first row of resultset or FALSE if none found - */ - public function getOne() - { - $query = $this->prepareQuery(func_get_args()); - if ($res = $this->rawQuery($query)) - { - $row = $this->fetch($res); - if (is_array($row)) { - return reset($row); - } - $this->free($res); - } - return FALSE; - } - - /** - * Helper function to get single row right out of query and optional arguments - * - * Examples: - * $data = $db->getRow("SELECT * FROM table WHERE id=1"); - * $data = $db->getRow("SELECT * FROM table WHERE id=?i", $id); - * - * @param string $query - an SQL query with placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the query - * @return array|FALSE either associative array contains first row of resultset or FALSE if none found - */ - public function getRow() - { - $query = $this->prepareQuery(func_get_args()); - if ($res = $this->rawQuery($query)) { - $ret = $this->fetch($res); - $this->free($res); - return $ret; - } - return FALSE; - } - - /** - * Helper function to get single column right out of query and optional arguments - * - * Examples: - * $ids = $db->getCol("SELECT id FROM table WHERE cat=1"); - * $ids = $db->getCol("SELECT id FROM tags WHERE tagname = ?s", $tag); - * - * @param string $query - an SQL query with placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the query - * @return array|FALSE either enumerated array of first fields of all rows of resultset or FALSE if none found - */ - 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; - } - - /** - * Helper function to get all the rows of resultset right out of query and optional arguments - * - * Examples: - * $data = $db->getAll("SELECT * FROM table"); - * $data = $db->getAll("SELECT * FROM table LIMIT ?i,?i", $start, $rows); - * - * @param string $query - an SQL query with placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the query - * @return array enumerated 2d array contains the resultset. Empty if no rows found. - */ - 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; - } - - /** - * Helper function to get all the rows of resultset into indexed array right out of query and optional arguments - * - * Examples: - * $data = $db->getInd("id", "SELECT * FROM table"); - * $data = $db->getInd("id", "SELECT * FROM table LIMIT ?i,?i", $start, $rows); - * - * @param string $index - name of the field which value is used to index resulting array - * @param string $query - an SQL query with placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the query - * @return array - associative 2d array contains the resultset. Empty if no rows found. - */ - 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; - } - - /** - * Helper function to get a dictionary-style array right out of query and optional arguments - * - * Examples: - * $data = $db->getIndCol("name", "SELECT name, id FROM cities"); - * - * @param string $index - name of the field which value is used to index resulting array - * @param string $query - an SQL query with placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the query - * @return array - associative array contains key=value pairs out of resultset. Empty if no rows found. - */ - public function getIndCol() - { - $args = func_get_args(); - $index = array_shift($args); - $query = $this->prepareQuery($args); - - $ret = array(); - if ( $res = $this->rawQuery($query) ) - { - while($row = $this->fetch($res)) - { - $key = $row[$index]; - unset($row[$index]); - $ret[$key] = reset($row); - } - $this->free($res); - } - return $ret; - } - - /** - * Function to parse placeholders either in the full query or a query part - * unlike native prepared statements, allows ANY query part to be parsed - * - * useful for debug - * and EXTREMELY useful for conditional query building - * like adding various query parts using loops, conditions, etc. - * already parsed parts have to be added via ?p placeholder - * - * Examples: - * $query = $db->parse("SELECT * FROM table WHERE foo=?s AND bar=?s", $foo, $bar); - * echo $query; - * - * if ($foo) { - * $qpart = $db->parse(" AND foo=?s", $foo); - * } - * $data = $db->getAll("SELECT * FROM table WHERE bar=?s ?p", $bar, $qpart); - * - * @param string $query - whatever expression contains placeholders - * @param mixed $arg,... unlimited number of arguments to match placeholders in the expression - * @return string - initial expression with placeholders substituted with data. - */ - public function parse() - { - return $this->prepareQuery(func_get_args()); - } - - /** - * function to implement whitelisting feature - * sometimes we can't allow a non-validated user-supplied data to the query even through placeholder - * especially if it comes down to SQL OPERATORS - * - * Example: - * - * $order = $db->whiteList($_GET['order'], array('name','price')); - * $dir = $db->whiteList($_GET['dir'], array('ASC','DESC')); - * if (!$order || !dir) { - * throw new http404(); //non-expected values should cause 404 or similar response - * } - * $sql = "SELECT * FROM table ORDER BY ?p ?p LIMIT ?i,?i" - * $data = $db->getArr($sql, $order, $dir, $start, $per_page); - * - * @param string $iinput - field name to test - * @param array $allowed - an array with allowed variants - * @param string $default - optional variable to set if no match found. Default to false. - * @return string|FALSE - either sanitized value or FALSE - */ - public function whiteList($input,$allowed,$default=FALSE) - { - $found = array_search($input,$allowed); - return ($found === FALSE) ? $default : $allowed[$found]; - } - - /** - * function to filter out arrays, for the whitelisting purposes - * useful to pass entire superglobal to the INSERT or UPDATE query - * OUGHT to be used for this purpose, - * as there could be fields to which user should have no access to. - * - * Example: - * $allowed = array('title','url','body','rating','term','type'); - * $data = $db->filterArray($_POST,$allowed); - * $sql = "INSERT INTO ?n SET ?u"; - * $db->query($sql,$table,$data); - * - * @param array $input - source array - * @param array $allowed - an array with allowed field names - * @return array filtered out source array - */ - public function filterArray($input,$allowed) - { - foreach(array_keys($input) as $key ) - { - if ( !in_array($key,$allowed) ) - { - unset($input[$key]); - } - } - return $input; - } - - /** - * Function to get last executed query. - * - * @return string|NULL either last executed query or NULL if were none - */ - public function lastQuery() - { - $last = end($this->stats); - return $last['query']; - } - - /** - * Function to get all query statistics. - * - * @return array contains all executed queries with timings and errors - */ - public function getStats() - { - return $this->stats; - } - - /** - * protected function which actually runs a query against Mysql server. - * also logs some stats like profiling info and error message - * - * @param string $query - a regular SQL query - * @return mysqli result resource or FALSE on error - */ - protected function rawQuery($query) - { - $start = microtime(TRUE); - $res = mysqli_query($this->conn, $query); - $timer = microtime(TRUE) - $start; - - $this->stats[] = array( - 'query' => $query, - 'start' => $start, - 'timer' => $timer, - ); - if (!$res) - { - $error = mysqli_error($this->conn); - - end($this->stats); - $key = key($this->stats); - $this->stats[$key]['error'] = $error; - $this->cutStats(); - - $this->error("$error. Full query: [$query]"); - } - $this->cutStats(); - return $res; - } - - protected function prepareQuery($args) - { - $query = ''; - $raw = array_shift($args); - $array = preg_split('~(\?[nsiuap])~u',$raw,null,PREG_SPLIT_DELIM_CAPTURE); - $anum = count($args); - $pnum = floor(count($array) / 2); - if ( $pnum != $anum ) - { - $this->error("Number of args ($anum) doesn't match number of placeholders ($pnum) in [$raw]"); - } - - foreach ($array as $i => $part) - { - if ( ($i % 2) == 0 ) - { - $query .= $part; - continue; - } - - $value = array_shift($args); - switch ($part) - { - case '?n': - $part = $this->escapeIdent($value); - break; - case '?s': - $part = $this->escapeString($value); - break; - case '?i': - $part = $this->escapeInt($value); - break; - case '?a': - $part = $this->createIN($value); - break; - case '?u': - $part = $this->createSET($value); - break; - case '?p': - $part = $value; - break; - } - $query .= $part; - } - return $query; - } - - protected function escapeInt($value) - { - if ($value === NULL) - { - return 'NULL'; - } - if(!is_numeric($value)) - { - $this->error("Integer (?i) placeholder expects numeric value, ".gettype($value)." given"); - return FALSE; - } - if (is_float($value)) - { - $value = number_format($value, 0, '.', ''); // may lose precision on big numbers - } - return $value; - } - - protected function escapeString($value) - { - if ($value === NULL) - { - return 'NULL'; - } - return "'".mysqli_real_escape_string($this->conn,$value)."'"; - } - - protected function escapeIdent($value) - { - if ($value) - { - return "`".str_replace("`","``",$value)."`"; - } else { - $this->error("Empty value for identifier (?n) placeholder"); - } - } - - protected function createIN($data) - { - if (!is_array($data)) - { - $this->error("Value for IN (?a) placeholder should be array"); - return; - } - if (!$data) - { - return 'NULL'; - } - $query = $comma = ''; - foreach ($data as $value) - { - $query .= $comma.$this->escapeString($value); - $comma = ","; - } - return $query; - } - - protected function createSET($data) - { - if (!is_array($data)) - { - $this->error("SET (?u) placeholder expects array, ".gettype($data)." given"); - return; - } - if (!$data) - { - $this->error("Empty array for SET (?u) placeholder"); - return; - } - $query = $comma = ''; - foreach ($data as $key => $value) - { - $query .= $comma.$this->escapeIdent($key).'='.$this->escapeString($value); - $comma = ","; - } - return $query; - } - - protected 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); - } - } - - protected 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; - } - - /** - * On a long run we can eat up too much memory with mere statsistics - * Let's keep it at reasonable size, leaving only last 100 entries. - */ - protected function cutStats() - { - if ( count($this->stats) > 100 ) - { - reset($this->stats); - $first = key($this->stats); - unset($this->stats[$first]); - } - } }