mirror of https://github.com/avecms/AVE.cms.git
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.
1592 lines
40 KiB
1592 lines
40 KiB
<?php |
|
|
|
/** |
|
* AVE.cms |
|
* |
|
* Класс предназначен для создания обертки над MySql запросами к БД. |
|
* |
|
* @package AVE.cms |
|
* @version 3.x |
|
* @filesource |
|
* @copyright © 2007-2014 AVE.cms, http://www.ave-cms.ru |
|
* |
|
* @license GPL v.2 |
|
*/ |
|
|
|
/*************************************************************************** |
|
* Класс, предназначенный для работы с ошибками |
|
***************************************************************************/ |
|
class AVE_DB_Exception extends Exception |
|
{ |
|
/** |
|
* @param string $e |
|
*/ |
|
function __construct($e) |
|
{ |
|
parent::__construct($e); |
|
} |
|
} |
|
|
|
/*************************************************************************** |
|
* Класс, предназначенный для работы с результатами выполнения MySQL-запроса |
|
***************************************************************************/ |
|
class AVE_DB_Result |
|
{ |
|
/** |
|
* Конечный результат выполнения запроса |
|
* |
|
* @var resource |
|
*/ |
|
public $_result = null; |
|
|
|
|
|
/** |
|
* Конструктор, возвращает объект с указателем на результат выполнения SQL-запроса |
|
* |
|
* @param $_result |
|
* |
|
* @internal param resource $result указателем на результат выполнения SQL-запроса |
|
* @return \AVE_DB_Result object |
|
*/ |
|
public function __construct($_result) |
|
{ |
|
$this->_result = $_result; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для обработки результата запроса. |
|
* Возвращает как ассоциативный, так и численный массив. |
|
* |
|
* @return array |
|
*/ |
|
public function FetchArray() |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$a = current($this->_result); |
|
|
|
next($this->_result); |
|
|
|
$b = array(); |
|
|
|
if (! is_array($a)) |
|
return false; |
|
|
|
foreach($a as $k => $v) |
|
$b[] = $v; |
|
|
|
return array_merge($b, $a); |
|
} |
|
|
|
return mysqli_fetch_array($this->_result); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для обработки результата запроса. |
|
* Возвращает только ассоциативный массив. |
|
* |
|
* @return array |
|
*/ |
|
public function FetchAssocArray() |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$a = current($this->_result); |
|
|
|
next($this->_result); |
|
|
|
return $a; |
|
} |
|
|
|
return mysqli_fetch_assoc($this->_result); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для обработки результата запроса, возвращая данные в виде объекта. |
|
* |
|
* @return object |
|
*/ |
|
public function FetchRow() |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$a = $this->FetchAssocArray(); |
|
|
|
return array2object($a); |
|
} |
|
|
|
return mysqli_fetch_object($this->_result); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для возвращения данных результата запроса |
|
* |
|
* @return mixed |
|
*/ |
|
public function GetCell() |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$a = current($this->_result); |
|
|
|
if (is_array($a)) |
|
return current($a); |
|
else |
|
return false; |
|
} |
|
|
|
if ($this->NumRows()) |
|
{ |
|
$a = mysqli_fetch_row($this->_result); |
|
return $a[0]; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
/** |
|
* Метод, предназначенный для обработки результата запроса. |
|
* Возвращает полный ассоциативный массив. |
|
* |
|
* @return array |
|
*/ |
|
public function GetArray() |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$data = current($this->_result); |
|
|
|
next($this->_result); |
|
|
|
return $data; |
|
} |
|
|
|
$array = []; |
|
|
|
while ($row = mysqli_fetch_assoc($this->_result)) |
|
array_push($array, $row); |
|
|
|
return $array; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для обработки результата запроса. |
|
* Возвращает данные в виде объекта. |
|
* |
|
* @return array |
|
*/ |
|
public function GetObject() |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$data = $this->FetchAssocArray(); |
|
|
|
return array2object($data); |
|
} |
|
|
|
$array = array(); |
|
|
|
while ($row = mysqli_fetch_object($this->_result)) |
|
array_push($array, $row); |
|
|
|
return $array; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для перемещения внутреннего указателя в результате запроса |
|
* |
|
* @param int $id - номер ряда результатов запроса |
|
* @return bool |
|
*/ |
|
public function DataSeek($id = 0) |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
//не нашел как переместить указатель в массиве на конкретный |
|
reset($this->_result); |
|
|
|
for ($x = 0; $x == $id; $x++) |
|
next ($this->_result); |
|
|
|
return $id; //эээ а что вернуть то надо было? |
|
} |
|
|
|
return mysqli_data_seek($this->_result, $id); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для получения количества рядов результата запроса |
|
* |
|
* @return int |
|
*/ |
|
public function NumRows() |
|
{ |
|
if (is_array($this->_result)) |
|
return (int)count($this->_result); |
|
|
|
return (int)mysqli_num_rows($this->_result); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для получения количества полей результата запроса |
|
* |
|
* @return int |
|
*/ |
|
public function NumFields() |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$a = current($this->_result); |
|
|
|
return count($a); |
|
} |
|
|
|
return (int)mysqli_num_fields($this->_result); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для получения названия указанной колонки результата запроса |
|
* |
|
* @param int $i - индекс колонки |
|
* @return string |
|
*/ |
|
public function FieldName($i) |
|
{ |
|
if (is_array($this->_result)) |
|
{ |
|
$a = current($this->_result); |
|
|
|
$b = array_keys($a); |
|
|
|
return($b[$i]); |
|
} |
|
|
|
mysqli_field_seek($this->_result, $i); |
|
|
|
$field = mysqli_fetch_field($this->_result); |
|
|
|
return $field->name; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для освобождения памяти от результата запроса |
|
* |
|
* @return bool |
|
*/ |
|
public function Close() |
|
{ |
|
if (! is_array($this->_result)) |
|
@mysqli_free_result($this->_result); |
|
|
|
return true; |
|
} |
|
|
|
|
|
/** |
|
* Возвращает объект результата _result. |
|
* |
|
* @internal param $void |
|
* @return resource |
|
*/ |
|
public function getResult() |
|
{ |
|
return $this->_result; |
|
} |
|
|
|
|
|
/** |
|
* Удаляем объект |
|
*/ |
|
public function __destruct() |
|
{ |
|
$this->Close(); |
|
} |
|
} |
|
|
|
|
|
/************************************************************** |
|
* |
|
* Класс, предназначенный для работы непосредственно с MySQL БД |
|
* |
|
**************************************************************/ |
|
class AVE_DB |
|
{ |
|
/** |
|
* Хост |
|
* |
|
* @var string |
|
*/ |
|
protected $db_host; |
|
|
|
/** |
|
* Имя пользователя |
|
* |
|
* @var string |
|
*/ |
|
protected $db_user; |
|
|
|
/** |
|
* Пароль |
|
* |
|
* @var string |
|
*/ |
|
protected $db_pass; |
|
|
|
/** |
|
* Номер порта |
|
* |
|
* @var int |
|
*/ |
|
protected $db_port; |
|
|
|
/** |
|
* Сокет |
|
* |
|
* @var int |
|
*/ |
|
protected $db_socket; |
|
|
|
/** |
|
* Имя текущей БД. |
|
* |
|
* @var string |
|
*/ |
|
protected $db_name; |
|
|
|
/** |
|
* Префикс БД. |
|
* |
|
* @var string |
|
*/ |
|
protected $db_prefix; |
|
|
|
/** |
|
* Стандартный объект соединения сервером MySQL. |
|
* |
|
* @var mysqli |
|
*/ |
|
protected $mysqli; |
|
|
|
/** |
|
* Список выполненных запросов |
|
* |
|
* @var array |
|
*/ |
|
public $_query_list; |
|
|
|
/** |
|
* Метки времени до и после выполнения SQL-запроса |
|
* |
|
* @var array |
|
*/ |
|
public $_time_exec; |
|
|
|
/** |
|
* Последний запрос SQL-запроса |
|
* |
|
* @var array |
|
*/ |
|
public $_last_query; |
|
|
|
|
|
//-- Instance |
|
protected static $instance = null; |
|
|
|
/** |
|
* Конструктор |
|
* |
|
* @param $db |
|
* |
|
* @throws AVE_DB_Exception |
|
* @return \AVE_DB AVE_DB - объект |
|
*/ |
|
private function __construct($db) |
|
{ |
|
$this->db_host = $db['dbhost']; |
|
$this->db_user = $db['dbuser']; |
|
$this->db_password = $db['dbpass']; |
|
$this->db_prefix = $db['dbpref']; |
|
|
|
if (! isset($db['dbport'])) |
|
$this->db_port = ini_get ('mysqli.default_port'); |
|
else |
|
$this->db_port = (isset($db['dbport']) ? $db['dbport'] : null); |
|
|
|
if (! isset($db['dbsock'])) |
|
$this->db_socket = ini_get ('mysqli.default_socket'); |
|
else |
|
$this->db_port = (isset($db['dbsock']) ? $db['dbsock'] : null); |
|
|
|
$this->Connect(); |
|
|
|
// Определяем профилирование |
|
if (defined('SQL_PROFILING') && SQL_PROFILING) |
|
{ |
|
// mysqli_query($this->mysqli, "QUERY_CACHE_TYPE = OFF"); |
|
// mysqli_query($this->mysqli, "FLUSH TABLES"); |
|
if (mysqli_query($this->mysqli, "SET PROFILING_HISTORY_SIZE = 100")) |
|
{ |
|
mysqli_query($this->mysqli,"SET PROFILING = 1"); |
|
} |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Устанавливает соеденение с базой данных. |
|
* |
|
* @throws AVE_DB_Exception |
|
* @internal param void |
|
* @return void |
|
*/ |
|
private function Connect() |
|
{ |
|
if (!is_object($this->mysqli) || !$this->mysqli instanceof mysqli) |
|
{ |
|
$this->mysqli = @new mysqli($this->db_host, $this->db_user, $this->db_password, null, $this->db_port, $this->db_socket); |
|
if ($this->mysqli->connect_error) |
|
{ |
|
throw new AVE_DB_Exception(__METHOD__ . ': ' . $this->mysqli->connect_error); |
|
} |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Задает набор символов по умолчанию. |
|
* |
|
* @param string $charset |
|
* |
|
* @throws AVE_DB_Exception |
|
* @return AVE_DB |
|
*/ |
|
public function setCharset($charset) |
|
{ |
|
if (!$this->mysqli->set_charset($charset)) |
|
{ |
|
throw new AVE_DB_Exception(__METHOD__ . ': ' . $this->mysqli->error); |
|
} |
|
|
|
return $this; |
|
} |
|
|
|
|
|
/** |
|
* Задает Sql Mode |
|
* |
|
* @param string $charset |
|
* |
|
* @throws AVE_DB_Exception |
|
* @return AVE_DB |
|
*/ |
|
public function setSqlMode() |
|
{ |
|
if (!$this->mysqli->query("SET SQL_MODE = ''")) |
|
{ |
|
throw new AVE_DB_Exception(__METHOD__ . ': ' . $this->mysqli->error); |
|
} |
|
|
|
return $this; |
|
} |
|
|
|
|
|
/** |
|
* Устанавливает имя используемой СУБД. |
|
* |
|
* @param string $database_name - имя базы данных |
|
* @throws AVE_DB_Exception |
|
* @return AVE_DB |
|
*/ |
|
public function setDatabaseName($database_name) |
|
{ |
|
if (!$database_name) |
|
{ |
|
throw new AVE_DB_Exception(__METHOD__ . ': Не указано имя базы данных'); |
|
} |
|
|
|
$this->db_name = $database_name; |
|
|
|
if (!$this->mysqli->select_db($this->db_name)) |
|
{ |
|
throw new AVE_DB_Exception(__METHOD__ . ': ' . $this->mysqli->error); |
|
} |
|
|
|
return $this; |
|
} |
|
|
|
|
|
/** |
|
* Создает инстанс данного класса. |
|
* |
|
* @uses $AVE_DB = AVE_DB::getInstance($server, $username, $password, $port, $socket); |
|
* @param $db |
|
* @return object возвращает инстанс данного класса. |
|
*/ |
|
public static function getInstance ($config = array()) |
|
{ |
|
return new self($config); |
|
} |
|
|
|
|
|
/** |
|
* Возвращает префикс БД. |
|
* |
|
* @param void |
|
* @return string |
|
*/ |
|
public function getPrefix() |
|
{ |
|
return $this->db_prefix; |
|
} |
|
|
|
|
|
/** |
|
* Возвращает кодировку по умолчанию, установленную для соединения с БД. |
|
* |
|
* @param void |
|
* @return string |
|
*/ |
|
public function getCharset() |
|
{ |
|
return $this->mysqli->character_set_name(); |
|
} |
|
|
|
|
|
/** |
|
* Возвращает имя текущей БД. |
|
* |
|
* @param void |
|
* @return string |
|
*/ |
|
public function getDatabaseName() |
|
{ |
|
return $this->db_name; |
|
} |
|
|
|
|
|
/** |
|
* Получает количество рядов, задействованных в предыдущей MySQL-операции. |
|
* Возвращает количество рядов, задействованных в последнем запросе INSERT, UPDATE или DELETE. |
|
* Если последним запросом был DELETE без оператора WHERE, |
|
* все записи таблицы будут удалены, но функция возвратит ноль. |
|
* |
|
* @see mysqli_affected_rows |
|
* @param void |
|
* @return int |
|
*/ |
|
public function getAffectedRows() |
|
{ |
|
return $this->mysqli->affected_rows; |
|
} |
|
|
|
|
|
/** |
|
* Возвращает последний выполненный MySQL-запрос. |
|
* |
|
* @param void |
|
* @return string |
|
*/ |
|
public function getQueryString() |
|
{ |
|
return $this->_last_query; |
|
} |
|
|
|
|
|
/** |
|
* Возвращает массив со всеми исполненными SQL-запросами в рамках текущего объекта. |
|
* |
|
* @param void |
|
* @return array |
|
*/ |
|
public function getQueries() |
|
{ |
|
return $this->_query_list; |
|
} |
|
|
|
|
|
public function prepareQuery($string) |
|
{ |
|
$search = array( |
|
"/[\t]/", |
|
'/(\s)+/s', |
|
'/(GROUP BY |STRAIGHT_JOIN |UNION |FROM |WHERE |LIMIT |ORDER BY |LEFT JOIN|INNER JOIN|RIGHT JOIN|JOIN|ON |AND |OR |SET |VALUES)/s' |
|
); |
|
|
|
$replace = array( |
|
" ", |
|
'\\1', |
|
"\r\n$1" |
|
); |
|
|
|
return trim(preg_replace($search, $replace, $string)); |
|
} |
|
|
|
|
|
public function showAllQueries () |
|
{ |
|
if (! is_array($this->_query_list)) |
|
return false; |
|
|
|
$div = ''; |
|
|
|
include_once BASE_DIR . '/lib/debug/sql.php'; |
|
|
|
$SqlFormatter = new SqlFormatter(); |
|
|
|
foreach ($this->_query_list AS $k => $v) |
|
{ |
|
$_caller = ''; |
|
|
|
if (is_array($v['caller'])) |
|
{ |
|
foreach ($v['caller'] AS $caller) |
|
{ |
|
$_caller .= 'File: ' . $caller['call_file'] . PHP_EOL; |
|
$_caller .= 'Func: ' . $caller['call_func'] . PHP_EOL; |
|
$_caller .= 'Line: ' . $caller['call_line'] . PHP_EOL; |
|
$_caller .= PHP_EOL; |
|
} |
|
} |
|
|
|
$_ttl = $v['ttl'] > 0 ? $v['ttl'] : 'None'; |
|
|
|
if (isset($v['cache'])) |
|
$_ttl .= PHP_EOL . $v['cache']; |
|
|
|
$_query = SqlFormatter::format($v['query']); |
|
|
|
$div .= ' |
|
<div style="border: 1px solid #f0f0f0; margin: 5px 0; font-size: 11px; font-family: Consolas, Verdana, Arial; border-radius: 3px;">' . |
|
'<div style="background:#aaa; color: #fff; margin: 0; padding: 5px;">' . |
|
'<strong>' . ($k+1) . '</strong>' . |
|
'</div>' . |
|
'<pre style="background:#f5f5f5; color: #000; margin: 0; padding: 5px; border: 0; font-size: 11px; font-family: Consolas, Verdana, Arial;">' . |
|
'<strong>Trace:</strong>' . PHP_EOL . |
|
$_caller . |
|
'<strong>Cache:</strong>' . PHP_EOL . |
|
$_ttl . PHP_EOL . |
|
PHP_EOL . |
|
'<strong>Query:</strong>' . PHP_EOL . |
|
'<div style="background: #fcfcfc; padding: 10px; margin: 10px; border: 1px solid #ccc; border-radius: 3px;">' . |
|
$_query . |
|
'</div>' . |
|
'</pre>' . |
|
'</div> |
|
'; |
|
} |
|
|
|
return $div; |
|
} |
|
|
|
/** |
|
* Возвращает id, сгенерированный предыдущей операцией INSERT. |
|
* |
|
* @see mysqli_insert_id |
|
* @param void |
|
* @return int |
|
*/ |
|
public function getLastInsertId() |
|
{ |
|
return $this->mysqli->insert_id; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для возвращения ID записи, сгенерированной при последнем INSERT-запросе |
|
* |
|
* @return int |
|
*/ |
|
public function InsertId() |
|
{ |
|
return (int)mysqli_insert_id($this->mysqli); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для получения функции из которой пришел запрос с ошибкой |
|
* |
|
* @return array|string |
|
*/ |
|
public function getCaller() |
|
{ |
|
if (! function_exists('debug_backtrace')) |
|
return ''; |
|
|
|
$stack = debug_backtrace(); |
|
$stack = array_reverse($stack); |
|
|
|
$caller = array(); |
|
|
|
foreach ((array)$stack as $call) |
|
{ |
|
if (isset($call['class']) && $call['class'] == __CLASS__) |
|
continue; |
|
|
|
$function = $call['function']; |
|
|
|
if (isset($call['class'])) |
|
{ |
|
$function = $call['class'] . "->$function"; |
|
} |
|
|
|
$caller[] = [ |
|
'call_file' => (isset($call['file']) ? $call['file'] : 'Unknown'), |
|
'call_func' => $function, |
|
'call_line' => (isset($call['line']) ? $call['line'] : 'Unknown') |
|
]; |
|
} |
|
|
|
return $caller; |
|
} |
|
|
|
|
|
/************************* Внешние методы класса *************************/ |
|
|
|
|
|
/** |
|
* Метод, предназначенный для выполнения запроса к MySQL |
|
* |
|
* @param string $query - текст SQL-запроса |
|
* @param bool $log - записать ошибки в лог? по умолчанию включено |
|
* @return object/bool - объект с указателем на результат выполнения запроса |
|
*/ |
|
public function Real_Query($query, $log = true, $TTL = null) |
|
{ |
|
$result = @mysqli_query($this->mysqli, $query); |
|
|
|
// Запоминаем последний запрос |
|
$this->_last_query = $query; |
|
|
|
// Если стоит в настройках, запоминать все запросы |
|
if (defined('SQL_PROFILING') && SQL_PROFILING) |
|
{ |
|
$_caller = $this->getCaller(); |
|
$this->_query_list[] = array('caller' => $_caller, 'query' => $query, 'ttl' => $TTL); |
|
} |
|
|
|
// Если нет результата и стоит выводить логи, выводим лог ошибки |
|
if (! $result && $log) |
|
$this->_error('query', $query); |
|
|
|
if (is_object($result) && $result instanceof mysqli_result) |
|
return new AVE_DB_Result($result); |
|
|
|
return $result; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для выполнения запроса к MySQL и возвращение результата в виде асоциативного массива с поддержкой кеша |
|
* |
|
* @param string $query - текст SQL-запроса |
|
* @param integer $TTL - время жизни кеша (-1 безусловный кеш) |
|
* @param string $cache_id - Id файла кеша |
|
* @param bool $log - записать ошибки в лог? по умолчанию включено |
|
* @return array|AVE_DB_Result |
|
*/ |
|
public function Query ($query, $TTL = null, $cache_id = '', $log = true, $ext = '') |
|
{ |
|
$cache_id = $this->cacheId($cache_id); |
|
|
|
// Принудительная фильтрация запроса |
|
if (defined(SQL_QUERY_SANITIZE) && SQL_QUERY_SANITIZE) |
|
$query = filter_var($query, FILTER_SANITIZE_STRING); |
|
|
|
$result = []; |
|
|
|
// Если это SELECT - то отслеживаем кеширование |
|
$TTL = strtoupper(substr(trim($query), 0, 6)) == 'SELECT' |
|
? $TTL |
|
: null; |
|
|
|
// Если включен DEV MODE, то отключаем кеширование запросов |
|
if (defined('DEV_MODE') AND DEV_MODE) |
|
$TTL = null; |
|
|
|
if ($TTL && ($TTL != 'nocache' AND $TTL != null)) |
|
{ |
|
$cache_file = md5($query) . $ext; |
|
|
|
$cache_dir = BASE_DIR . '/tmp/cache/sql/' . (trim($cache_id) > '' |
|
? trim($cache_id) . '/' |
|
: substr($cache_file, 0, 2) . '/' . substr($cache_file, 2, 2) . '/' . substr($cache_file, 4, 2) . '/'); |
|
|
|
if (! file_exists($cache_dir)) |
|
mkdir($cache_dir, 0766, true); |
|
|
|
$TTL = ($TTL == -1) |
|
? true |
|
: time() - filemtime($cache_dir . $cache_file) < $TTL; |
|
|
|
if (! (file_exists($cache_dir . $cache_file) && $TTL)) |
|
{ |
|
$res = $this->Real_Query($query, $log); |
|
|
|
while ($mfa = $res->FetchAssocArray()) |
|
$result[] = $mfa; |
|
|
|
if (defined('USE_ENCODE_SERIALIZE') && USE_ENCODE_SERIALIZE) |
|
file_put_contents($cache_dir . $cache_file, _base64_encode(serialize($result))); |
|
else |
|
file_put_contents($cache_dir . $cache_file, serialize($result)); |
|
} |
|
else |
|
{ |
|
// Если стоит в настройках, запоминать все запросы |
|
if (defined('SQL_PROFILING') && SQL_PROFILING) |
|
{ |
|
$_caller = $this->getCaller(); |
|
|
|
$this->_query_list[] = [ |
|
'caller' => $_caller, |
|
'query' => $query, |
|
'ttl' => $TTL, |
|
'cache' => $cache_dir . $cache_file |
|
]; |
|
} |
|
|
|
if (defined('USE_ENCODE_SERIALIZE') && USE_ENCODE_SERIALIZE) |
|
$result = unserialize(_base64_decode(file_get_contents($cache_dir . $cache_file))); |
|
else |
|
$result = unserialize(file_get_contents($cache_dir . $cache_file)); |
|
} |
|
|
|
return new AVE_DB_Result($result); |
|
} |
|
|
|
else |
|
return $this->Real_Query($query, $log, $TTL); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для выполнения запроса к MySQL и возвращение результата в виде асоциативного массива с поддержкой кеша |
|
* |
|
* @param string $query - текст SQL-запроса |
|
* @param integer $TTL - время жизни кеша (-1 безусловный кеш) |
|
* @param string $cache_id - Id файла кеша |
|
* @param bool $log - записать ошибки в лог? по умолчанию включено |
|
* @return void |
|
*/ |
|
public function Queries ($array) |
|
{ |
|
if (is_array($array)) |
|
{ |
|
foreach ($array AS $sql) |
|
{ |
|
// Принудительная фильтрация запроса |
|
if (defined(SQL_QUERY_SANITIZE) && SQL_QUERY_SANITIZE) |
|
$sql = filter_var($sql, FILTER_SANITIZE_STRING); |
|
|
|
$this->Real_Query($sql); |
|
} |
|
} |
|
} |
|
|
|
|
|
/** |
|
* This method is needed for prepared statements. They require |
|
* the data type of the field to be bound with "i" s", etc. |
|
* This function takes the input, determines what type it is, |
|
* and then updates the param_type. |
|
* |
|
* @param mixed $item Input to determine the type. |
|
* |
|
* @return string The joined parameter types. |
|
*/ |
|
protected function DetermineType($item) |
|
{ |
|
switch (gettype($item)) |
|
{ |
|
case 'NULL': |
|
case 'string': |
|
return 's'; |
|
break; |
|
|
|
case 'boolean': |
|
case 'integer': |
|
return 'i'; |
|
break; |
|
|
|
case 'blob': |
|
return 'b'; |
|
break; |
|
|
|
case 'double': |
|
return 'd'; |
|
break; |
|
} |
|
return ''; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для экранирования специальных символов в строках для использования в выражениях SQL |
|
* |
|
* @param mixed $value - обрабатываемое значение |
|
* @return mixed |
|
*/ |
|
public function Escape($value) |
|
{ |
|
if (! is_numeric($value)) |
|
{ |
|
$value = mysqli_real_escape_string($this->mysqli, $value); |
|
} |
|
|
|
return $value; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для экранирования специальных символов в строках для использования в выражениях SQL |
|
* |
|
* @param mixed $value - обрабатываемое значение |
|
* @return mixed - возвращает строку запроса вычещенной |
|
*/ |
|
public function EscStr($value) |
|
{ |
|
global $AVE_DB; |
|
|
|
$search = array( |
|
'&' => '&', |
|
'&gt;' => '>', |
|
'<' => '<', |
|
';' => ':', |
|
'|' => '|', |
|
'>' => '>', |
|
"'" => ''', |
|
'"' => '"', |
|
')' => ')', |
|
'(' => '(', |
|
'{' => '{', |
|
'}' => '}', |
|
'$' => '$' |
|
); |
|
|
|
$value = str_replace(array_keys($search), array_values($search), $value); |
|
$value = str_ireplace('%3Cscript', '', $value); |
|
|
|
$value = htmlspecialchars($value, ENT_QUOTES); |
|
|
|
if (! is_array($value)) |
|
{ |
|
$value = mysqli_real_escape_string($this->mysqli, $value); |
|
} |
|
else |
|
{ |
|
$value = array_map(array($AVE_DB, 'Escape'), $value); |
|
} |
|
|
|
return $value; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для экранирования и очищения значения при поиске url в базе |
|
* |
|
* @param string $value - обрабатываемое значение |
|
* @return string - возвращает строку запроса вычещенной |
|
*/ |
|
function ClearUrl($url) |
|
{ |
|
global $AVE_DB; |
|
|
|
// Убираем пробелы |
|
$url = trim($url); |
|
|
|
// Условия |
|
$search = ['<', ';', '|', '&', '>', "'", '"', ')', '(', '{', '}', '$', '=']; |
|
|
|
// Убираем пробелы |
|
$url = preg_replace('/[\s,]+/i', '', $url); |
|
|
|
// Проходимся условиями |
|
$url = str_replace($search, '', $url); |
|
$url = str_ireplace('%3Cscript', '', $url); |
|
|
|
// Применяем встроенный SANITIZE |
|
$url = filter_var($url, FILTER_SANITIZE_STRING); |
|
|
|
// Переводим html в сущности, если чтото осталось |
|
$url = htmlspecialchars($url); |
|
|
|
// Если это не массив |
|
if (! is_array($url)) |
|
{ |
|
// Проходимся функцией от MySQL |
|
$url = $this->mysqli->real_escape_string($url); |
|
} |
|
// Иначе вообще очищаем строку |
|
else |
|
{ |
|
$url = array_map([$AVE_DB, 'Escape'], $url); |
|
} |
|
|
|
return $url; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для возвращения количества всех найденных записей (после запроса) |
|
* |
|
* @return int |
|
*/ |
|
public function GetFoundRows() |
|
{ |
|
$result = $this->Query('SELECT FOUND_ROWS();'); |
|
$strRow = $result->FetchArray(); |
|
|
|
return (int)$strRow[0]; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для возвращения количества всех найденных записей (после запроса типа "SELECT SQL_CALC_FOUND_ROWS * ...") |
|
* |
|
* @param $query |
|
* @param null $TTL |
|
* @param string $cache_id |
|
* @return int |
|
*/ |
|
public function NumAllRows ($query, $TTL = null, $cache_id = '') |
|
{ |
|
$cache_id = $this->cacheId($cache_id); |
|
|
|
// Если включен DEV MODE, то отключаем кеширование запросов |
|
if (defined('DEV_MODE') AND DEV_MODE) |
|
$TTL = null; |
|
|
|
if ($TTL AND ($TTL != 'nocache' AND $TTL != null)) |
|
{ |
|
// Кол-во |
|
$cache_file = md5($query) . '.count'; |
|
|
|
$cache_dir = BASE_DIR . '/tmp/cache/sql/' . (trim($cache_id) > '' |
|
? trim($cache_id) . '/' |
|
: substr($cache_file, 0, 2) . '/'. substr($cache_file, 2, 2) . '/' . substr($cache_file, 4, 2) . '/'); |
|
|
|
if (! file_exists($cache_dir)) |
|
mkdir($cache_dir, 0777, true); |
|
|
|
if (! (file_exists($cache_dir . $cache_file) && ($TTL == -1 ? true : time() - filemtime($cache_dir . $cache_file) < $TTL))) |
|
{ |
|
if ($query <> $this->_last_query) |
|
{ |
|
$res = $this->Real_Query($query); |
|
} |
|
else |
|
{ |
|
$res = (int)$this->Query('SELECT FOUND_ROWS();')->GetCell(); |
|
|
|
file_put_contents($cache_dir . $cache_file, $res); |
|
} |
|
|
|
return $res; |
|
} |
|
else |
|
{ |
|
// Если стоит в настройках, запоминать все запросы |
|
if (defined('SQL_PROFILING') && SQL_PROFILING) |
|
{ |
|
$_caller = $this->getCaller(); |
|
|
|
$this->_query_list[] = [ |
|
'caller' => $_caller, |
|
'query' => 'SELECT FOUND_ROWS();', |
|
'ttl' => $TTL, |
|
'cache' => $cache_dir . $cache_file |
|
]; |
|
} |
|
|
|
return file_get_contents($cache_dir . $cache_file); |
|
} |
|
} |
|
|
|
return (int)$this->Query('SELECT FOUND_ROWS();')->GetCell(); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для формирования статистики выполнения SQL-запросов. |
|
* |
|
* @param string $type - тип запрашиваемой статистики |
|
* <pre> |
|
* Возможные значения: |
|
* list - список выполненых зпаросов |
|
* time - время исполнения зпросов |
|
* count - количество выполненных запросов |
|
* </pre> |
|
* @return mixed |
|
*/ |
|
public function DBStatisticGet($type = '') |
|
{ |
|
switch ($type) |
|
{ |
|
case 'list': |
|
list($s_dec, $s_sec) = explode(' ', $GLOBALS['start_time']); |
|
|
|
$query_list = ''; |
|
|
|
$nq = 0; |
|
|
|
//$time_exec = 0; |
|
$arr = $this->_time_exec; |
|
|
|
$co = sizeof($arr); |
|
|
|
for ($it = 0; $it < $co;) |
|
{ |
|
list($a_dec, $a_sec) = explode(' ', $arr[$it++]); |
|
list($b_dec, $b_sec) = explode(' ', $arr[$it++]); |
|
|
|
$time_main = ($a_sec - $s_sec + $a_dec - $s_dec)*1000; |
|
$time_exec = ($b_sec - $a_sec + $b_dec - $a_dec)*1000; |
|
|
|
$query = sizeof(array_keys($this->_query_list, $this->_query_list[$nq])) > 1 |
|
? "<span style=\"background-color:#ff9;\">" . $this->_query_list[$nq++] . "</span>" |
|
: $this->_query_list[$nq++]; |
|
|
|
$query_list .= (($time_exec > 1) ? "<li style=\"color:#c00\">(" : "<li>(") |
|
. round($time_main) . " ms) " . $time_exec . " ms " . $query . "</li>\n"; |
|
} |
|
|
|
return $query_list; |
|
break; |
|
|
|
case 'time': |
|
$arr = $this->_time_exec; |
|
|
|
$time_exec = 0; |
|
|
|
$co = sizeof($arr); |
|
|
|
for ($it = 0; $it < $co;) |
|
{ |
|
list($a_dec, $a_sec) = explode(" ", $arr[$it++]); |
|
list($b_dec, $b_sec) = explode(" ", $arr[$it++]); |
|
|
|
$time_exec += $b_sec - $a_sec + $b_dec - $a_dec; |
|
} |
|
|
|
return $time_exec; |
|
break; |
|
|
|
case 'count': |
|
return sizeof($this->_query_list); |
|
break; |
|
|
|
default: |
|
return ''; |
|
break; |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для формирования статистики выполнения SQL-запросов. |
|
* |
|
* @param string $type - тип запрашиваемой статистики |
|
* <pre> |
|
* Возможные значения: |
|
* list - список выполненых зпаросов |
|
* time - время исполнения зпросов |
|
* count - количество выполненных запросов |
|
* </pre> |
|
* @return mixed |
|
*/ |
|
public function DBProfilesGet($type = '') |
|
{ |
|
static $result, $list, $time, $count; |
|
|
|
if (! defined('SQL_PROFILING') OR ! SQL_PROFILING) |
|
return false; |
|
|
|
if (! $result) |
|
{ |
|
$list = "<table width=\"100%\" style=\"color:#000; font-size: 11px; font-family: Consolas, Verdana, Arial;\">" |
|
. "\n\t<col width=\"20\">\n\t<col width=\"70\">"; |
|
|
|
$result = mysqli_query($this->mysqli, "SHOW PROFILES"); |
|
|
|
while (list($qid, $qtime, $qstring) = @mysqli_fetch_row($result)) |
|
{ |
|
$time += $qtime; |
|
|
|
$qstring = preg_replace('/\t+/', '', $qstring); |
|
|
|
$list .= "\n\t<tr style=\"background:#eee; margin:5px; padding:5px; min-width:600px;\">\n\t\t<td><strong>" |
|
. $qid |
|
. "</strong></td>\n\t\t<td><strong>" |
|
. number_format($qtime * 1, 6, ',', '') |
|
. "</strong></td>\n\t\t<td style=\"white-space: pre\"><strong>" |
|
. $qstring |
|
. "</strong></td>\n\t</tr>"; |
|
|
|
$res = mysqli_query($this->mysqli, " |
|
SELECT |
|
STATE, |
|
FORMAT(DURATION, 6) AS DURATION |
|
FROM |
|
INFORMATION_SCHEMA.PROFILING |
|
WHERE |
|
QUERY_ID = " . $qid |
|
); |
|
|
|
while (list($state, $duration) = @mysqli_fetch_row($res)) |
|
{ |
|
$list .= "\n\t<tr>\n\t\t<td> </td><td>" |
|
. number_format($duration * 1, 6, ',', '') |
|
. "</td>\n\t\t<td>" . $state . "</td>\n\t</tr>"; |
|
} |
|
} |
|
|
|
$time = number_format($time * 1, 6, ',', ''); |
|
$list .= "\n</table>"; |
|
$count = @mysqli_num_rows($result); |
|
} |
|
|
|
switch ($type) |
|
{ |
|
case 'list': return $list; break; |
|
case 'time': return $time; break; |
|
case 'count': return $count; break; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
/** |
|
* Закрывает MySQL-соединение. |
|
* |
|
* @param void |
|
* @return AVE_DB |
|
*/ |
|
public function Close() |
|
{ |
|
if (is_object($this->mysqli) && $this->mysqli instanceof mysqli) |
|
{ |
|
@$this->mysqli->close(); |
|
} |
|
|
|
return $this; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для обработки ошибок |
|
* |
|
* @param string $type - тип ошибки (при подключении к БД или при выполнении SQL-запроса) |
|
* @param string $query - текст SQL запроса вызвавшего ошибку |
|
* @access private |
|
*/ |
|
public function _error($type, $query = '') |
|
{ |
|
|
|
|
|
if ($type != 'query') |
|
{ |
|
display_notice('Error ' . $type . ' MySQL database.'); |
|
} |
|
else |
|
{ |
|
$my_error = mysqli_error($this->mysqli); |
|
|
|
$log = array( |
|
'sql_error' => $my_error, |
|
'sql_query' => htmlentities(stripslashes($query), ENT_QUOTES), |
|
'caller' => $this->getCaller(), |
|
'url' => HOST . $_SERVER['SCRIPT_NAME']. '?' . $_SERVER['QUERY_STRING'] |
|
); |
|
|
|
reportSqlLog($log); |
|
|
|
// Если в настройках системы установлен параметр на отправку сообщений на e-mail, тогда |
|
if (SEND_SQL_ERROR) |
|
{ |
|
// Формируем текст сообщения с ошибкой |
|
$mail_body = ( |
|
'SQL ERROR: ' . $my_error . PHP_EOL |
|
. 'TIME: ' . date('d-m-Y, H:i:s') . PHP_EOL |
|
. 'URL: ' . HOST . $_SERVER['SCRIPT_NAME'] |
|
. '?' . $_SERVER['QUERY_STRING'] . PHP_EOL |
|
. $this->getCaller() . PHP_EOL |
|
. 'QUERY: ' . stripslashes($query) . PHP_EOL |
|
); |
|
|
|
// Отправляем сообщение |
|
send_mail( |
|
get_settings('mail_from'), |
|
$mail_body, |
|
'MySQL Error!', |
|
get_settings('mail_from'), |
|
get_settings('mail_from_name'), |
|
'text' |
|
); |
|
} |
|
} |
|
} |
|
|
|
|
|
/** |
|
* Удаляем объект |
|
* |
|
* @param void |
|
*/ |
|
public function __destruct() |
|
{ |
|
$this->Close(); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для получения информации о сервере MySQL |
|
* |
|
* @param void |
|
* @return string |
|
*/ |
|
public function mysql_version() |
|
{ |
|
return @mysqli_get_server_info($this->mysqli); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для определения кеша |
|
* |
|
* @param $cache_id |
|
* @return bool |
|
*/ |
|
public function cacheId ($cache_id) |
|
{ |
|
//-- Если это документ, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'doc') { |
|
$cache_id = (int) str_replace('doc_', '', $cache_id); |
|
return $cache_id = 'documents/' . (floor($cache_id / 1000)) . '/' . $cache_id; |
|
} |
|
|
|
//-- Если это полный документ, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'dat') { |
|
$cache_id = (int) str_replace('dat_', '', $cache_id); |
|
return $cache_id = 'documents/' . (floor($cache_id / 1000)) . '/' . $cache_id; |
|
} |
|
|
|
//-- Если это скомпилированный шаблон документа, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'cmd') { |
|
$cache_id = (int) str_replace('cmd_', '', $cache_id); |
|
return $cache_id = 'documents/' . (floor($cache_id / 1000)) . '/' . $cache_id; |
|
} |
|
|
|
//-- Если это поля документа, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'fld') { |
|
$cache_id = (int) str_replace('fld_', '', $cache_id); |
|
return $cache_id = 'documents/' . (floor($cache_id / 1000)) . '/' . $cache_id; |
|
} |
|
|
|
//-- Если это рубрика, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'rub') { |
|
$cache_id = (int) str_replace('rub_', '', $cache_id); |
|
return $cache_id = 'rubrics/' . $cache_id; |
|
} |
|
|
|
//-- Если это запрос, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'req') { |
|
$cache_id = (int) str_replace('req_', '', $cache_id); |
|
return $cache_id = 'requests/' . $cache_id; |
|
} |
|
|
|
//-- Если это элемент запроса, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'rqe') { |
|
$cache_id = (int) str_replace('rqe_', '', $cache_id); |
|
return $cache_id = 'requests/elements/' . (floor($cache_id / 1000)) . '/' . $cache_id; |
|
} |
|
|
|
//-- Если это настройки запроса, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'rqs') { |
|
$cache_id = str_replace('rqs_', '', $cache_id); |
|
return $cache_id = 'requests/settings/' . $cache_id; |
|
} |
|
|
|
//-- Если это условия запроса, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'rqc') { |
|
$cache_id = str_replace('rqc_', '', $cache_id); |
|
return $cache_id = 'requests/settings/' . $cache_id; |
|
} |
|
|
|
// -- Навигация |
|
if (substr($cache_id, 0, 3) == 'nav') { |
|
$cache_id = explode('_', $cache_id); |
|
return $cache_id = 'navigations/' . $cache_id[1]; |
|
} |
|
|
|
//-- Если это скомпилированный документ, то меняем расположение |
|
if (substr($cache_id, 0, 3) == 'cmp') { |
|
$cache_id = (int) str_replace('cmp_', '', $cache_id); |
|
return $cache_id = 'compile/' . (floor($cache_id / 1000)) . '/' . $cache_id; |
|
} |
|
|
|
if (substr_count($cache_id, '__') > 0) { |
|
return str_replace('__', '/', $cache_id); |
|
} |
|
|
|
return $cache_id; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для очищения кеша документов |
|
* |
|
* @param $cache_id |
|
* @return bool |
|
*/ |
|
public function clearCache($cache_id) |
|
{ |
|
$cache_id = $this->cacheId($cache_id); |
|
|
|
$cache_dir = BASE_DIR . '/tmp/cache/sql/' . (trim($cache_id) > '' |
|
? trim($cache_id) . '/' |
|
: ''); |
|
|
|
return rrmdir($cache_dir); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для очищения кеша документов |
|
* |
|
* @param $cache_id |
|
* @return bool |
|
*/ |
|
public function clearCurrentCache($cache_id, $sql = '', $ext = '') |
|
{ |
|
$cache_id = $this->cacheId($cache_id); |
|
|
|
$cache_file = md5($sql) . $ext; |
|
|
|
$cache_dir = BASE_DIR . '/tmp/cache/sql/' . (trim($cache_id) > '' |
|
? trim($cache_id) . '/' |
|
: substr($cache_file, 0, 2) . '/' . substr($cache_file, 2, 2) . '/' . substr($cache_file, 4, 2) . '/'); |
|
|
|
if (file_exists($cache_dir . $cache_file)) |
|
unlink($cache_dir . $cache_file); |
|
|
|
return true; |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для очищения кеша документов |
|
* |
|
* @param $cache_id |
|
* @return bool |
|
*/ |
|
public function clearCacheUrl($cache_id) |
|
{ |
|
$cache_id = str_replace('url_', '', $cache_id); |
|
$cache_id = 'documents/urls/' . substr($cache_id, 0, 3); |
|
|
|
$cache_dir = BASE_DIR . '/tmp/cache/sql/' . (trim($cache_id) > '' |
|
? trim($cache_id) . '/' |
|
: ''); |
|
|
|
return rrmdir($cache_dir); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для очищения кеша запросов |
|
* |
|
* @param $cache_id |
|
* @return bool |
|
*/ |
|
public function clearCacheRequest($cache_id) |
|
{ |
|
$cache_id = (int)str_replace('req_', '', $cache_id); |
|
$cache_id = 'request/' . (floor($cache_id / 1000)) . '/' . $cache_id; |
|
|
|
$cache_dir = BASE_DIR . '/tmp/cache/sql/' . (trim($cache_id) > '' |
|
? trim($cache_id) . '/' |
|
: ''); |
|
|
|
return rrmdir($cache_dir); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для очищения кеша запросов |
|
* |
|
* @param $cache_id |
|
* @return bool |
|
*/ |
|
public function clearRequest($cache_id) |
|
{ |
|
$request = request_get_settings($cache_id); |
|
|
|
$cache_from_id = BASE_DIR . '/tmp/cache/sql/requests/settings/' . (trim($request->Id) > '' |
|
? trim($request->Id) . '/' |
|
: ''); |
|
|
|
$cache_from_alias = BASE_DIR . '/tmp/cache/sql/requests/settings/' . (trim($request->request_alias) > '' |
|
? trim($request->request_alias) . '/' |
|
: ''); |
|
|
|
return (rrmdir($cache_from_id) AND rrmdir($cache_from_alias)); |
|
} |
|
|
|
|
|
/** |
|
* Метод, предназначенный для очищения кеша запросов |
|
* |
|
* @param int $doc_id |
|
*/ |
|
public function clearDocument($doc_id) |
|
{ |
|
$this->clearCache('doc_' . $doc_id); // Параметры |
|
$this->clearCache('fld_' . $doc_id); // Поля |
|
$this->clearCache('cmd_' . $doc_id); // Компиляция |
|
$this->clearCache('rqe_' . $doc_id); // Элемент запроса |
|
$this->clearCache('cmp_' . $doc_id); // Скомпилированный документ |
|
} |
|
|
|
|
|
/* |
|
|-------------------------------------------------------------------------------------- |
|
| Перефоратирует текст запроса |
|
|-------------------------------------------------------------------------------------- |
|
| |
|
| @param string |
|
| @return string |
|
| |
|
*/ |
|
function queryList ($string) |
|
{ |
|
$search = array( |
|
"/[\t]/", |
|
'/(\s)+/s', |
|
'/(GROUP BY|STRAIGHT_JOIN|UNION|FROM|WHERE|LIMIT|ORDER BY|LEFT JOIN|INNER JOIN|RIGHT JOIN|JOIN|ON|AND|OR|SET)/s' |
|
|
|
); |
|
|
|
$replace = array( |
|
" ", |
|
'\\1', |
|
"\r\n$1" |
|
); |
|
|
|
return trim(preg_replace($search, $replace, $string)); |
|
} |
|
|
|
} // End AVE_DB class |
|
?>
|