527 lines
13 KiB
PHP
527 lines
13 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Класс для доступа к базе данных
|
|||
|
|
*
|
|||
|
|
* @copyright 2011 Denis Pikusov
|
|||
|
|
* @link http://simplacms.ru
|
|||
|
|
* @author Denis Pikusov
|
|||
|
|
*
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
require_once('Simpla.php');
|
|||
|
|
|
|||
|
|
class Database extends Simpla
|
|||
|
|
{
|
|||
|
|
private $link;
|
|||
|
|
private $res_id;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* В конструкторе подключаем базу
|
|||
|
|
*/
|
|||
|
|
public function __construct()
|
|||
|
|
{
|
|||
|
|
parent::__construct();
|
|||
|
|
|
|||
|
|
$this->connect();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* В деструкторе отсоединяемся от базы
|
|||
|
|
*/
|
|||
|
|
public function __destruct()
|
|||
|
|
{
|
|||
|
|
$this->disconnect();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Подключение к базе данных
|
|||
|
|
*/
|
|||
|
|
public function connect()
|
|||
|
|
{
|
|||
|
|
// При повторном вызове возвращаем существующий линк
|
|||
|
|
if(!empty($this->link))
|
|||
|
|
return $this->link;
|
|||
|
|
|
|||
|
|
// Иначе пытаемся подключиться
|
|||
|
|
if(!$this->link = mysql_connect($this->config->db_server, $this->config->db_user, $this->config->db_password))
|
|||
|
|
{
|
|||
|
|
trigger_error("Could not connect to the database. Check the config file.", E_USER_WARNING);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if(!mysql_select_db($this->config->db_name, $this->link))
|
|||
|
|
{
|
|||
|
|
trigger_error("Could not select the database.", E_USER_WARNING);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Настраиваем соединение
|
|||
|
|
if($this->config->db_charset)
|
|||
|
|
mysql_query('SET NAMES '.$this->config->db_charset, $this->link);
|
|||
|
|
if($this->config->db_sql_mode)
|
|||
|
|
mysql_query('SET SESSION SQL_MODE = "'.$this->config->db_sql_mode.'"', $this->link);
|
|||
|
|
if($this->config->timezone)
|
|||
|
|
mysql_query('SET SESSION time_zone = "'.$this->config->db_timezone.'"', $this->link);
|
|||
|
|
|
|||
|
|
return $this->link;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Закрываем подключение к базе данных
|
|||
|
|
*/
|
|||
|
|
public function disconnect()
|
|||
|
|
{
|
|||
|
|
if(!@mysql_close($this->link))
|
|||
|
|
return true;
|
|||
|
|
else
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Запрос к базе. Обазятелен первый аргумент - текст запроса.
|
|||
|
|
* При указании других аргументов автоматически выполняется placehold() для запроса с подстановкой этих аргументов
|
|||
|
|
*/
|
|||
|
|
public function query()
|
|||
|
|
{
|
|||
|
|
$time_start = microtime(true);
|
|||
|
|
|
|||
|
|
$args = func_get_args();
|
|||
|
|
|
|||
|
|
$q = call_user_func_array(array($this, 'placehold'), $args);
|
|||
|
|
if($this->link)
|
|||
|
|
{
|
|||
|
|
$this->res_id = mysql_query($q, $this->link);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
$error_msg = "Could not execute query to database, wrong database link. [$q]";
|
|||
|
|
trigger_error($error_msg, E_USER_WARNING);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
if(!$this->res_id)
|
|||
|
|
{
|
|||
|
|
$error_msg = mysql_error($this->link).' ['.$q.']';
|
|||
|
|
trigger_error($error_msg, E_USER_WARNING);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$time_end = microtime(true);
|
|||
|
|
$exec_time = round(($time_end-$time_start)*1000, 0);
|
|||
|
|
//print "$exec_time ms <br>$q<br><br>";
|
|||
|
|
|
|||
|
|
return $this->res_id;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Плейсхолдер для запросов. Пример работы: $query = $db->placehold('SELECT name FROM products WHERE id=?', $id);
|
|||
|
|
*/
|
|||
|
|
public function placehold()
|
|||
|
|
{
|
|||
|
|
$args = func_get_args();
|
|||
|
|
$tmpl = array_shift($args);
|
|||
|
|
// Заменяем все __ на префикс, но только необрамленные кавычками
|
|||
|
|
$tmpl = preg_replace('/([^"\'0-9a-z_])__([a-z_]+[^"\'])/ui', "\$1".$this->config->db_prefix."\$2", $tmpl);
|
|||
|
|
if(!empty($args))
|
|||
|
|
{
|
|||
|
|
$result = $this->sql_placeholder_ex($tmpl, $args, $error);
|
|||
|
|
if ($result === false)
|
|||
|
|
{
|
|||
|
|
$error = "Placeholder substitution error. Diagnostics: \"$error\"";
|
|||
|
|
trigger_error($error, E_USER_WARNING);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return $result;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
return $tmpl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Возвращает результаты запроса. Необязательный второй аргумент указывает какую колонку возвращать вместо всего массива колонок
|
|||
|
|
*/
|
|||
|
|
public function results($field = null)
|
|||
|
|
{
|
|||
|
|
$results = array();
|
|||
|
|
if(!$this->res_id)
|
|||
|
|
{
|
|||
|
|
trigger_error(mysql_error($this->link), E_USER_WARNING);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if($this->num_rows() == 0)
|
|||
|
|
return array();
|
|||
|
|
|
|||
|
|
while($row = mysql_fetch_object($this->res_id))
|
|||
|
|
{
|
|||
|
|
if(!empty($field) && isset($row->$field))
|
|||
|
|
array_push($results, $row->$field);
|
|||
|
|
else
|
|||
|
|
array_push($results, $row);
|
|||
|
|
}
|
|||
|
|
return $results;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Возвращает первый результат запроса. Необязательный второй аргумент указывает какую колонку возвращать вместо всего массива колонок
|
|||
|
|
*/
|
|||
|
|
public function result($field = null)
|
|||
|
|
{
|
|||
|
|
$result = array();
|
|||
|
|
if(!$this->res_id)
|
|||
|
|
{
|
|||
|
|
$this->error_msg = "Could not execute query to database, wrong result id";
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
$row = mysql_fetch_object($this->res_id);
|
|||
|
|
if(!empty($field) && isset($row->$field))
|
|||
|
|
return $row->$field;
|
|||
|
|
elseif(!empty($field) && !isset($row->$field))
|
|||
|
|
return false;
|
|||
|
|
else
|
|||
|
|
return $row;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Возвращает последний вставленный id
|
|||
|
|
*/
|
|||
|
|
public function insert_id()
|
|||
|
|
{
|
|||
|
|
return mysql_insert_id($this->link);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Возвращает количество выбранных строк
|
|||
|
|
*/
|
|||
|
|
public function num_rows()
|
|||
|
|
{
|
|||
|
|
return mysql_num_rows($this->res_id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Возвращает количество затронутых строк
|
|||
|
|
*/
|
|||
|
|
public function affected_rows()
|
|||
|
|
{
|
|||
|
|
return mysql_affected_rows($this->link);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Компиляция плейсхолдера
|
|||
|
|
*/
|
|||
|
|
private function sql_compile_placeholder($tmpl)
|
|||
|
|
{
|
|||
|
|
$compiled = array();
|
|||
|
|
$p = 0; // текущая позиция в строке
|
|||
|
|
$i = 0; // счетчик placeholder-ов
|
|||
|
|
$has_named = false;
|
|||
|
|
while(false !== ($start = $p = strpos($tmpl, "?", $p)))
|
|||
|
|
{
|
|||
|
|
// Определяем тип placeholder-а.
|
|||
|
|
switch ($c = substr($tmpl, ++$p, 1))
|
|||
|
|
{
|
|||
|
|
case '%': case '@': case '#':
|
|||
|
|
$type = $c; ++$p; break;
|
|||
|
|
default:
|
|||
|
|
$type = ''; break;
|
|||
|
|
}
|
|||
|
|
// Проверяем, именованный ли это placeholder: "?keyname"
|
|||
|
|
if (preg_match('/^((?:[^\s[:punct:]]|_)+)/', substr($tmpl, $p), $pock))
|
|||
|
|
{
|
|||
|
|
$key = $pock[1];
|
|||
|
|
if ($type != '#')
|
|||
|
|
$has_named = true;
|
|||
|
|
$p += strlen($key);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
$key = $i;
|
|||
|
|
if ($type != '#')
|
|||
|
|
$i++;
|
|||
|
|
}
|
|||
|
|
// Сохранить запись о placeholder-е.
|
|||
|
|
$compiled[] = array($key, $type, $start, $p - $start);
|
|||
|
|
}
|
|||
|
|
return array($compiled, $tmpl, $has_named);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Выполнение плейсхолдера
|
|||
|
|
*/
|
|||
|
|
private function sql_placeholder_ex($tmpl, $args, &$errormsg)
|
|||
|
|
{
|
|||
|
|
// Запрос уже разобран?.. Если нет, разбираем.
|
|||
|
|
if (is_array($tmpl))
|
|||
|
|
$compiled = $tmpl;
|
|||
|
|
else
|
|||
|
|
$compiled = $this->sql_compile_placeholder($tmpl);
|
|||
|
|
|
|||
|
|
list ($compiled, $tmpl, $has_named) = $compiled;
|
|||
|
|
|
|||
|
|
// Если есть хотя бы один именованный placeholder, используем
|
|||
|
|
// первый аргумент в качестве ассоциативного массива.
|
|||
|
|
if ($has_named)
|
|||
|
|
$args = @$args[0];
|
|||
|
|
|
|||
|
|
// Выполняем все замены в цикле.
|
|||
|
|
$p = 0; // текущее положение в строке
|
|||
|
|
$out = ''; // результирующая строка
|
|||
|
|
$error = false; // были ошибки?
|
|||
|
|
|
|||
|
|
foreach ($compiled as $num=>$e)
|
|||
|
|
{
|
|||
|
|
list ($key, $type, $start, $length) = $e;
|
|||
|
|
|
|||
|
|
// Pre-string.
|
|||
|
|
$out .= substr($tmpl, $p, $start - $p);
|
|||
|
|
$p = $start + $length;
|
|||
|
|
|
|||
|
|
$repl = ''; // текст для замены текущего placeholder-а
|
|||
|
|
$errmsg = ''; // сообщение об ошибке для этого placeholder-а
|
|||
|
|
do {
|
|||
|
|
// Это placeholder-константа?
|
|||
|
|
if ($type === '#')
|
|||
|
|
{
|
|||
|
|
$repl = @constant($key);
|
|||
|
|
if (NULL === $repl)
|
|||
|
|
$error = $errmsg = "UNKNOWN_CONSTANT_$key";
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
// Обрабатываем ошибку.
|
|||
|
|
if (!isset($args[$key]))
|
|||
|
|
{
|
|||
|
|
$error = $errmsg = "UNKNOWN_PLACEHOLDER_$key";
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
// Вставляем значение в соответствии с типом placeholder-а.
|
|||
|
|
$a = $args[$key];
|
|||
|
|
if ($type === '')
|
|||
|
|
{
|
|||
|
|
// Скалярный placeholder.
|
|||
|
|
if (is_array($a))
|
|||
|
|
{
|
|||
|
|
$error = $errmsg = "NOT_A_SCALAR_PLACEHOLDER_$key";
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
$repl = is_int($a) || is_float($a) ? str_replace(',', '.', $a) : "'".addslashes($a)."'";
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
// Иначе это массив или список.
|
|||
|
|
if(is_object($a))
|
|||
|
|
$a = get_object_vars($a);
|
|||
|
|
|
|||
|
|
if (!is_array($a))
|
|||
|
|
{
|
|||
|
|
$error = $errmsg = "NOT_AN_ARRAY_PLACEHOLDER_$key";
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
if ($type === '@')
|
|||
|
|
{
|
|||
|
|
// Это список.
|
|||
|
|
foreach ($a as $v)
|
|||
|
|
{
|
|||
|
|
if(is_null($v))
|
|||
|
|
$r = "NULL";
|
|||
|
|
else
|
|||
|
|
$r = "'".@addslashes($v)."'";
|
|||
|
|
|
|||
|
|
$repl .= ($repl===''? "" : ",").$r;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
elseif ($type === '%')
|
|||
|
|
{
|
|||
|
|
// Это набор пар ключ=>значение.
|
|||
|
|
$lerror = array();
|
|||
|
|
foreach ($a as $k=>$v)
|
|||
|
|
{
|
|||
|
|
if (!is_string($k))
|
|||
|
|
$lerror[$k] = "NOT_A_STRING_KEY_{$k}_FOR_PLACEHOLDER_$key";
|
|||
|
|
else
|
|||
|
|
$k = preg_replace('/[^a-zA-Z0-9_]/', '_', $k);
|
|||
|
|
|
|||
|
|
if(is_null($v))
|
|||
|
|
$r = "=NULL";
|
|||
|
|
else
|
|||
|
|
$r = "='".@addslashes($v)."'";
|
|||
|
|
|
|||
|
|
$repl .= ($repl===''? "" : ", ").$k.$r;
|
|||
|
|
}
|
|||
|
|
// Если была ошибка, составляем сообщение.
|
|||
|
|
if (count($lerror))
|
|||
|
|
{
|
|||
|
|
$repl = '';
|
|||
|
|
foreach ($a as $k=>$v)
|
|||
|
|
{
|
|||
|
|
if (isset($lerror[$k]))
|
|||
|
|
{
|
|||
|
|
$repl .= ($repl===''? "" : ", ").$lerror[$k];
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
$k = preg_replace('/[^a-zA-Z0-9_-]/', '_', $k);
|
|||
|
|
$repl .= ($repl===''? "" : ", ").$k."=?";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
$error = $errmsg = $repl;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} while (false);
|
|||
|
|
if ($errmsg) $compiled[$num]['error'] = $errmsg;
|
|||
|
|
if (!$error) $out .= $repl;
|
|||
|
|
}
|
|||
|
|
$out .= substr($tmpl, $p);
|
|||
|
|
|
|||
|
|
// Если возникла ошибка, переделываем результирующую строку
|
|||
|
|
// в сообщение об ошибке (расставляем диагностические строки
|
|||
|
|
// вместо ошибочных placeholder-ов).
|
|||
|
|
if ($error)
|
|||
|
|
{
|
|||
|
|
$out = '';
|
|||
|
|
$p = 0; // текущая позиция
|
|||
|
|
foreach ($compiled as $num=>$e)
|
|||
|
|
{
|
|||
|
|
list ($key, $type, $start, $length) = $e;
|
|||
|
|
$out .= substr($tmpl, $p, $start - $p);
|
|||
|
|
$p = $start + $length;
|
|||
|
|
if (isset($e['error']))
|
|||
|
|
{
|
|||
|
|
$out .= $e['error'];
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
$out .= substr($tmpl, $start, $length);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Последняя часть строки.
|
|||
|
|
$out .= substr($tmpl, $p);
|
|||
|
|
$errormsg = $out;
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
$errormsg = false;
|
|||
|
|
return $out;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public function dump($filename)
|
|||
|
|
{
|
|||
|
|
$h = fopen($filename, 'w');
|
|||
|
|
$q = $this->placehold("SHOW FULL TABLES LIKE '__%';");
|
|||
|
|
$result = mysql_query($q, $this->link);
|
|||
|
|
while($row = mysql_fetch_row($result))
|
|||
|
|
{
|
|||
|
|
if($row[1] == 'BASE TABLE')
|
|||
|
|
$this->dump_table($row[0], $h);
|
|||
|
|
}
|
|||
|
|
fclose($h);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function restore($filename)
|
|||
|
|
{
|
|||
|
|
$templine = '';
|
|||
|
|
$h = fopen($filename, 'r');
|
|||
|
|
|
|||
|
|
// Loop through each line
|
|||
|
|
if($h)
|
|||
|
|
{
|
|||
|
|
while(!feof($h))
|
|||
|
|
{
|
|||
|
|
$line = fgets($h);
|
|||
|
|
// Only continue if it's not a comment
|
|||
|
|
if (substr($line, 0, 2) != '--' && $line != '')
|
|||
|
|
{
|
|||
|
|
// Add this line to the current segment
|
|||
|
|
$templine .= $line;
|
|||
|
|
// If it has a semicolon at the end, it's the end of the query
|
|||
|
|
if (substr(trim($line), -1, 1) == ';')
|
|||
|
|
{
|
|||
|
|
// Perform the query
|
|||
|
|
mysql_query($templine, $this->link) or print('Error performing query \'<b>' . $templine . '</b>\': ' . mysql_error() . '<br /><br />');
|
|||
|
|
// Reset temp variable to empty
|
|||
|
|
$templine = '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
fclose($h);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
private function dump_table($table, $h)
|
|||
|
|
{
|
|||
|
|
$sql = "SELECT * FROM `$table`;";
|
|||
|
|
$result = mysql_query($sql, $this->link);
|
|||
|
|
if($result)
|
|||
|
|
{
|
|||
|
|
fwrite($h, "/* Data for table $table */\n");
|
|||
|
|
fwrite($h, "TRUNCATE TABLE `$table`;\n");
|
|||
|
|
|
|||
|
|
$num_rows = mysql_num_rows($result);
|
|||
|
|
$num_fields = mysql_num_fields($result);
|
|||
|
|
|
|||
|
|
if($num_rows > 0)
|
|||
|
|
{
|
|||
|
|
$field_type=array();
|
|||
|
|
$field_name = array();
|
|||
|
|
$i=0;
|
|||
|
|
while( $i < $num_fields)
|
|||
|
|
{
|
|||
|
|
$meta= mysql_fetch_field($result, $i);
|
|||
|
|
array_push($field_type, $meta->type);
|
|||
|
|
array_push($field_name, $meta->name);
|
|||
|
|
$i++;
|
|||
|
|
}
|
|||
|
|
$fields = implode('`, `', $field_name);
|
|||
|
|
fwrite($h, "INSERT INTO `$table` (`$fields`) VALUES\n");
|
|||
|
|
$index=0;
|
|||
|
|
while( $row= mysql_fetch_row($result))
|
|||
|
|
{
|
|||
|
|
fwrite($h, "(");
|
|||
|
|
for( $i=0; $i < $num_fields; $i++)
|
|||
|
|
{
|
|||
|
|
if( is_null( $row[$i]))
|
|||
|
|
fwrite($h, "null");
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
switch( $field_type[$i])
|
|||
|
|
{
|
|||
|
|
case 'int':
|
|||
|
|
fwrite($h, $row[$i]);
|
|||
|
|
break;
|
|||
|
|
case 'string':
|
|||
|
|
case 'blob' :
|
|||
|
|
default:
|
|||
|
|
fwrite($h, "'".mysql_real_escape_string($row[$i])."'");
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if( $i < $num_fields-1)
|
|||
|
|
fwrite($h, ",");
|
|||
|
|
}
|
|||
|
|
fwrite($h, ")");
|
|||
|
|
|
|||
|
|
if( $index < $num_rows-1)
|
|||
|
|
fwrite($h, ",");
|
|||
|
|
else
|
|||
|
|
fwrite($h, ";");
|
|||
|
|
fwrite($h, "\n");
|
|||
|
|
|
|||
|
|
$index++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
mysql_free_result($result);
|
|||
|
|
fwrite($h, "\n");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
public function escape($str)
|
|||
|
|
{
|
|||
|
|
return mysql_real_escape_string($str);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|