1295 lines
39 KiB
PHP
1295 lines
39 KiB
PHP
<?php
|
|
/**
|
|
* This file defines PDOEngine class.
|
|
*
|
|
* @package SQLite Integration
|
|
* @author Kojima Toshiyasu
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* This class extends PDO class and does the real work.
|
|
*
|
|
* It accepts a request from wpdb class, initialize PDO instance,
|
|
* execute SQL statement, and returns the results to WordPress.
|
|
*/
|
|
class PDOEngine extends PDO {
|
|
/**
|
|
* Class variable to check if there is an error.
|
|
*
|
|
* @var boolean
|
|
*/
|
|
public $is_error = false;
|
|
/**
|
|
* Class variable which is used for CALC_FOUND_ROW query.
|
|
*
|
|
* @var unsigned integer
|
|
*/
|
|
public $found_rows_result = null;
|
|
/**
|
|
* Class variable to store the rewritten queries.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $rewritten_query;
|
|
/**
|
|
* Class variable to have what kind of query to execute.
|
|
*
|
|
* @var string
|
|
* @access private
|
|
*/
|
|
private $query_type;
|
|
/**
|
|
* Class variable to store the result of the query.
|
|
*
|
|
* @var reference to the PHP object
|
|
* @access private
|
|
*/
|
|
private $results = null;
|
|
/**
|
|
* Class variable to store the results of the query.
|
|
*
|
|
* This is for the backward compatibility.
|
|
*
|
|
* @var reference to the PHP object
|
|
* @access private
|
|
*/
|
|
private $_results = null;
|
|
/**
|
|
* Class variable to reference to the PDO instance.
|
|
*
|
|
* @var PDO object
|
|
* @access private
|
|
*/
|
|
private $pdo;
|
|
/**
|
|
* Class variable to store the query string prepared to execute.
|
|
*
|
|
* @var string|array
|
|
*/
|
|
private $prepared_query;
|
|
/**
|
|
* Class variable to store the values in the query string.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $extracted_variables = array();
|
|
/**
|
|
* Class variable to store the error messages.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $error_messages = array();
|
|
/**
|
|
* Class variable to store the file name and function to cause error.
|
|
*
|
|
* @var array
|
|
* @access private
|
|
*/
|
|
private $errors;
|
|
/**
|
|
* Class variable to store the query strings.
|
|
*
|
|
* @var array
|
|
*/
|
|
public $queries = array();
|
|
/**
|
|
* Class variable to store the affected row id.
|
|
*
|
|
* @var unsigned integer
|
|
* @access private
|
|
*/
|
|
private $last_insert_id;
|
|
/**
|
|
* Class variable to store the number of rows affected.
|
|
*
|
|
* @var unsigned integer
|
|
*/
|
|
private $affected_rows;
|
|
/**
|
|
* Class variable to store the queried column info.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $column_data;
|
|
/**
|
|
* Variable to emulate MySQL affected row.
|
|
*
|
|
* @var integer
|
|
*/
|
|
private $num_rows;
|
|
/**
|
|
* Return value from query().
|
|
*
|
|
* Each query has its own return value.
|
|
*
|
|
* @var mixed
|
|
*/
|
|
private $return_value;
|
|
/**
|
|
* Variable to determine which insert query to use.
|
|
*
|
|
* Whether VALUES clause in the INSERT query can take multiple values or not
|
|
* depends on the version of SQLite library. We check the version and set
|
|
* this varable to true or false.
|
|
*
|
|
* @var boolean
|
|
*/
|
|
private $can_insert_multiple_rows = false;
|
|
/**
|
|
*
|
|
* @var integer
|
|
*/
|
|
private $param_num;
|
|
/**
|
|
* Varible to check if there is an active transaction.
|
|
* @var boolean
|
|
* @access protected
|
|
*/
|
|
protected $has_active_transaction = false;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param none
|
|
*/
|
|
function __construct() {
|
|
$this->init();
|
|
}
|
|
/**
|
|
* Destructor
|
|
*
|
|
* @return boolean
|
|
*/
|
|
function __destruct() {
|
|
$this->pdo = null;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Function to initialize database, executed in the contructor.
|
|
*
|
|
* It checks if there's a database directory and database file, creates the tables,
|
|
* and binds the user defined function to the pdo object.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
private function init() {
|
|
$dsn = 'sqlite:' . FQDB;
|
|
$result = $this->prepare_directory();
|
|
if (!$result) return false;
|
|
if (is_file(FQDB)) {
|
|
$locked = false;
|
|
do {
|
|
try {
|
|
if ($locked) $locked = false;
|
|
$this->pdo = new PDO(
|
|
$dsn, // data source name
|
|
null, // user name
|
|
null, // user password
|
|
array( // PDO options
|
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
|
));
|
|
$statement = $this->pdo->query("SELECT COUNT(*) FROM sqlite_master WHERE type='table'");
|
|
$number_of_tables = $statement->fetchColumn(0);
|
|
$statement = null;
|
|
if ($number_of_tables == 0) {
|
|
$this->make_sqlite_tables();
|
|
}
|
|
} catch (PDOException $err) {
|
|
$status = $err->getCode();
|
|
// code 5 => The database file is locked
|
|
// code 6 => A table in the database is locked
|
|
if ($status == 5 || $status == 6) {
|
|
$locked = true;
|
|
} else {
|
|
$message = 'Database connection error!<br />';
|
|
$message .= sprintf("Error message is: %s", $err->getMessage());
|
|
$this->set_error(__LINE__, __FUNCTION__, $message);
|
|
return false;
|
|
}
|
|
}
|
|
} while ($locked);
|
|
require_once UDF_FILE;
|
|
new PDOSQLiteUDFS($this->pdo);
|
|
if (version_compare($this->get_sqlite_version(), '3.7.11', '>=')) {
|
|
$this->can_insert_multiple_rows = true;
|
|
}
|
|
} else { // database file is not found, so we make it and create tables...
|
|
try {
|
|
$this->pdo = new PDO($dsn, null, null, array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
|
|
} catch (PDOException $err) {
|
|
$message = 'Database initialization error!<br />';
|
|
$message .= sprintf("Error message is: %s", $err->getMessage());
|
|
$this->set_error(__LINE__, __FUNCTION__, $message);
|
|
return false;
|
|
}
|
|
$this->make_sqlite_tables();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function makes database direcotry and .htaccess file.
|
|
*
|
|
* It is executed only once when the installation begins.
|
|
*/
|
|
private function prepare_directory() {
|
|
global $wpdb;
|
|
$u = umask(0000);
|
|
if (!is_dir(FQDBDIR)) {
|
|
if (!@mkdir(FQDBDIR, 0707, true)) {
|
|
umask($u);
|
|
$message = 'Unable to create the required directory! Please check your server settings.';
|
|
echo $message;
|
|
return false;
|
|
}
|
|
}
|
|
if (!is_writable(FQDBDIR)) {
|
|
umask($u);
|
|
$message = 'Unable to create a file in the directory! Please check your server settings.';
|
|
echo $message;
|
|
return false;
|
|
}
|
|
if (!is_file(FQDBDIR . '.htaccess')) {
|
|
$fh = fopen(FQDBDIR . '.htaccess', "w");
|
|
if (!$fh) {
|
|
umask($u);
|
|
$message = 'Unable to create a file in the directory! Please check your server settings.';
|
|
echo $message;
|
|
return false;
|
|
}
|
|
fwrite($fh, 'DENY FROM ALL');
|
|
fclose($fh);
|
|
}
|
|
umask($u);
|
|
return true;
|
|
}
|
|
/**
|
|
* function to call install() function which overrides WordPress install().
|
|
*
|
|
* This function is executed only once during the installation process.
|
|
*/
|
|
private function make_sqlite_tables() {
|
|
require_once PDODIR . 'install.php';
|
|
}
|
|
/**
|
|
* Function to execute query().
|
|
*
|
|
* Divide the query types into seven different ones. That is to say:
|
|
*
|
|
* 1. SELECT SQL_CALC_FOUND_ROWS
|
|
* 2. INSERT
|
|
* 3. CREATE TABLE(INDEX)
|
|
* 4. ALTER TABLE
|
|
* 5. SHOW VARIABLES
|
|
* 6. DROP INDEX
|
|
* 7. THE OTHERS
|
|
*
|
|
* #1 is just a tricky play. See the private function handle_sql_count() in query.class.php.
|
|
* From #2 through #5 call different functions respectively.
|
|
* #6 call the ALTER TABLE query.
|
|
* #7 is a normal process: sequentially call prepare_query() and execute_query().
|
|
*
|
|
* #1 process has been changed since version 1.5.1.
|
|
*
|
|
* @param $query full SQL statement string
|
|
* @return mixed according to the query type
|
|
* @see PDO::query()
|
|
*/
|
|
public function query($query) {
|
|
$this->flush();
|
|
|
|
$this->queries[] = "Raw query:\n$query";
|
|
$res = $this->determine_query_type($query);
|
|
if (!$res && defined(PDO_DEBUG) && PDO_DEBUG) {
|
|
$bailoutString = sprintf(__("<h1>Unknown query type</h1><p>Sorry, we cannot determine the type of query that is requested.</p><p>The query is %s</p>", 'sqlite-integration'), $query);
|
|
$this->set_error(__LINE__, __FUNCTION__, $bailoutString);
|
|
}
|
|
switch (strtolower($this->query_type)) {
|
|
case 'foundrows':
|
|
$_column = array('FOUND_ROWS()' => '');
|
|
$column = array();
|
|
if (!is_null($this->found_rows_result)) {
|
|
$this->num_rows = $this->found_rows_result;
|
|
$_column['FOUND_ROWS()'] = $this->num_rows;
|
|
// foreach ($this->found_rows_result[0] as $key => $value) {
|
|
// $_column['FOUND_ROWS()'] = $value;
|
|
// }
|
|
$column[] = new ObjectArray($_column);
|
|
$this->results = $column;
|
|
$this->found_rows_result = null;
|
|
}
|
|
break;
|
|
case 'insert':
|
|
if ($this->can_insert_multiple_rows) {
|
|
$this->execute_insert_query_new($query);
|
|
} else {
|
|
$this->execute_insert_query($query);
|
|
}
|
|
break;
|
|
case 'create':
|
|
$result = $this->execute_create_query($query);
|
|
$this->return_value = $result;
|
|
break;
|
|
case 'alter':
|
|
$result = $this->execute_alter_query($query);
|
|
$this->return_value = $result;
|
|
break;
|
|
case 'show_variables':
|
|
$result = $this->show_variables_workaround($query);
|
|
break;
|
|
case 'drop_index':
|
|
$pattern = '/^\\s*(DROP\\s*INDEX\\s*.*?)\\s*ON\\s*(.*)/im';
|
|
if (preg_match($pattern, $query, $match)) {
|
|
$drop_query = 'ALTER TABLE ' . trim($match[2]) . ' ' . trim($match[1]);
|
|
$this->query_type = 'alter';
|
|
$result = $this->execute_alter_query($drop_query);
|
|
$this->return_value = $result;
|
|
} else {
|
|
$this->return_value = false;
|
|
}
|
|
break;
|
|
default:
|
|
$engine = $this->prepare_engine($this->query_type);
|
|
$this->rewritten_query = $engine->rewrite_query($query, $this->query_type);
|
|
$this->queries[] = "Rewritten:\n$this->rewritten_query";
|
|
$this->extract_variables();
|
|
$statement = $this->prepare_query();
|
|
$this->execute_query($statement);
|
|
if (!$this->is_error) {
|
|
$this->process_results($engine);
|
|
} else {// Error
|
|
;
|
|
}
|
|
break;
|
|
}
|
|
if (defined('PDO_DEBUG') && PDO_DEBUG === true) {
|
|
file_put_contents(FQDBDIR . 'debug.txt', $this->get_debug_info(), FILE_APPEND);
|
|
}
|
|
return $this->return_value;
|
|
}
|
|
/**
|
|
* Function to return inserted row id.
|
|
*
|
|
* @return unsigned integer
|
|
*/
|
|
public function get_insert_id() {
|
|
return $this->last_insert_id;
|
|
}
|
|
/**
|
|
* Function to return the number of rows affected.
|
|
*
|
|
* @return unsigned integer
|
|
*/
|
|
public function get_affected_rows() {
|
|
return $this->affected_rows;
|
|
}
|
|
/**
|
|
* Function to return the queried column names.
|
|
*
|
|
* These data are meaningless for SQLite. So they are dummy emulating
|
|
* MySQL columns data.
|
|
*
|
|
* @return array of the object
|
|
*/
|
|
public function get_columns() {
|
|
if (!empty($this->results)) {
|
|
$primary_key = array(
|
|
'meta_id', 'comment_ID', 'link_ID', 'option_id',
|
|
'blog_id', 'option_name', 'ID', 'term_id', 'object_id',
|
|
'term_taxonomy_id', 'umeta_id', 'id');
|
|
$unique_key = array('term_id', 'taxonomy', 'slug');
|
|
$data = array(
|
|
'name' => '', // column name
|
|
'table' => '', // table name
|
|
'max_length' => 0, // max length of the column
|
|
'not_null' => 1, // 1 if not null
|
|
'primary_key' => 0, // 1 if column has primary key
|
|
'unique_key' => 0, // 1 if column has unique key
|
|
'multiple_key' => 0, // 1 if column doesn't have unique key
|
|
'numeric' => 0, // 1 if column has numeric value
|
|
'blob' => 0, // 1 if column is blob
|
|
'type' => '', // type of the column
|
|
'unsigned' => 0, // 1 if column is unsigned integer
|
|
'zerofill' => 0 // 1 if column is zero-filled
|
|
);
|
|
if (preg_match("/\s*FROM\s*(.*)?\s*/i", $this->rewritten_query, $match)) {
|
|
$table_name = trim($match[1]);
|
|
} else {
|
|
$table_name = '';
|
|
}
|
|
foreach ($this->results[0] as $key => $value) {
|
|
$data['name'] = $key;
|
|
$data['table'] = $table_name;
|
|
if (in_array($key, $primary_key)) {
|
|
$data['primary_key'] = 1;
|
|
} elseif (in_array($key, $unique_key)) {
|
|
$data['unique_key'] = 1;
|
|
} else {
|
|
$data['multiple_key'] = 1;
|
|
}
|
|
$this->column_data[] = new ObjectArray($data);
|
|
$data['name'] = '';
|
|
$data['table'] = '';
|
|
$data['primary_key'] = 0;
|
|
$data['unique_key'] = 0;
|
|
$data['multiple_key'] = 0;
|
|
}
|
|
return $this->column_data;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Function to return the queried result data.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function get_query_results() {
|
|
return $this->results;
|
|
}
|
|
/**
|
|
* Function to return the number of rows from the queried result.
|
|
*
|
|
* @return unsigned integer
|
|
*/
|
|
public function get_num_rows() {
|
|
return $this->num_rows;
|
|
}
|
|
/**
|
|
* Function to return the queried results according to the query types.
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function get_return_value() {
|
|
return $this->return_value;
|
|
}
|
|
/**
|
|
* Function to return error messages.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_error_message(){
|
|
if (count($this->error_messages) === 0){
|
|
$this->is_error = false;
|
|
$this->error_messages = array();
|
|
return '';
|
|
}
|
|
$output = '<div style="clear:both"> </div>';
|
|
if ($this->is_error === false){
|
|
// return $output;
|
|
return '';
|
|
}
|
|
$output .= "<div class=\"queries\" style=\"clear:both; margin_bottom:2px; border: red dotted thin;\">Queries made or created this session were<br/>\r\n\t<ol>\r\n";
|
|
foreach ($this->queries as $q){
|
|
$output .= "\t\t<li>".$q."</li>\r\n";
|
|
}
|
|
$output .= "\t</ol>\r\n</div>";
|
|
foreach ($this->error_messages as $num=>$m){
|
|
$output .= "<div style=\"clear:both; margin_bottom:2px; border: red dotted thin;\" class=\"error_message\" style=\"border-bottom:dotted blue thin;\">Error occurred at line {$this->errors[$num]['line']} in Function {$this->errors[$num]['function']}. <br/> Error message was: $m </div>";
|
|
}
|
|
|
|
ob_start();
|
|
debug_print_backtrace();
|
|
$output .= '<pre>' . ob_get_contents() . '</pre>';
|
|
ob_end_clean();
|
|
return $output;
|
|
|
|
}
|
|
/**
|
|
* Function to return information about query string for debugging.
|
|
*
|
|
* @return string
|
|
*/
|
|
private function get_debug_info(){
|
|
$output = '';
|
|
foreach ($this->queries as $q){
|
|
$output .= $q ."\n";
|
|
}
|
|
return $output;
|
|
}
|
|
/**
|
|
* Function to clear previous data.
|
|
*/
|
|
private function flush(){
|
|
$this->rewritten_query = '';
|
|
$this->query_type = '';
|
|
$this->results = null;
|
|
$this->_results = null;
|
|
$this->last_insert_id = null;
|
|
$this->affected_rows = null;
|
|
$this->column_data = array();
|
|
$this->num_rows = null;
|
|
$this->return_value = null;
|
|
$this->extracted_variables = array();
|
|
$this->error_messages = array();
|
|
$this->is_error = false;
|
|
$this->queries = array();
|
|
$this->param_num = 0;
|
|
}
|
|
/**
|
|
* Function to include the apropreate class files.
|
|
*
|
|
* It is not a good habit to change the include files programatically.
|
|
* Needs to be fixed some other way.
|
|
*
|
|
* @param string $query_type
|
|
* @return object reference to apropreate driver
|
|
*/
|
|
private function prepare_engine($query_type = null) {
|
|
if (stripos($query_type, 'create') !== false) {
|
|
require_once PDODIR . 'query_create.class.php';
|
|
$engine = new CreateQuery();
|
|
} elseif (stripos($query_type, 'alter') !== false) {
|
|
require_once PDODIR . 'query_alter.class.php';
|
|
$engine = new AlterQuery();
|
|
} else {
|
|
require_once PDODIR . 'query.class.php';
|
|
$engine = new PDOSQLiteDriver();
|
|
}
|
|
return $engine;
|
|
}
|
|
/**
|
|
* Function to create a PDO statement object from the query string.
|
|
*
|
|
* @return PDOStatement
|
|
*/
|
|
private function prepare_query(){
|
|
$this->queries[] = "Prepare:\n" . $this->prepared_query;
|
|
$reason = 0;
|
|
$message = '';
|
|
$statement = null;
|
|
do {
|
|
try {
|
|
$statement = $this->pdo->prepare($this->prepared_query);
|
|
} catch (PDOException $err) {
|
|
$reason = $err->getCode();
|
|
$message = $err->getMessage();
|
|
}
|
|
} while (5 == $reason || 6 == $reason);
|
|
|
|
if ($reason > 0){
|
|
$err_message = sprintf("Problem preparing the PDO SQL Statement. Error was: %s", $message);
|
|
$this->set_error(__LINE__, __FUNCTION__, $err_message);
|
|
}
|
|
return $statement;
|
|
}
|
|
/**
|
|
* Function to execute PDO statement object.
|
|
*
|
|
* This function executes query and sets the variables to give back to WordPress.
|
|
* The variables are class fields. So if success, no return value. If failure, it
|
|
* returns void and stops.
|
|
*
|
|
* @param object $statement of PDO statement
|
|
* @return boolean
|
|
*/
|
|
private function execute_query($statement) {
|
|
$reason = 0;
|
|
$message = '';
|
|
if (!is_object($statement))
|
|
return false;
|
|
if (count($this->extracted_variables) > 0) {
|
|
$this->queries[] = "Executing:\n" . var_export($this->extracted_variables, true);
|
|
do {
|
|
if ($this->query_type == 'update' || $this->query_type == 'replace') {
|
|
try {
|
|
$this->beginTransaction();
|
|
$statement->execute($this->extracted_variables);
|
|
$this->commit();
|
|
} catch (PDOException $err) {
|
|
$reason = $err->getCode();
|
|
$message = $err->getMessage();
|
|
$this->rollBack();
|
|
}
|
|
} else {
|
|
try {
|
|
$statement->execute($this->extracted_variables);
|
|
} catch (PDOException $err) {
|
|
$reason = $err->getCode();
|
|
$message = $err->getMessage();
|
|
}
|
|
}
|
|
} while (5 == $reason || 6 == $reason);
|
|
} else {
|
|
$this->queries[] = 'Executing: (no parameters)';
|
|
do{
|
|
if ($this->query_type == 'update' || $this->query_type == 'replace') {
|
|
try {
|
|
$this->beginTransaction();
|
|
$statement->execute();
|
|
$this->commit();
|
|
} catch (PDOException $err) {
|
|
$reason = $err->getCode();
|
|
$message = $err->getMessage();
|
|
$this->rollBack();
|
|
}
|
|
} else {
|
|
try {
|
|
$statement->execute();
|
|
} catch (PDOException $err) {
|
|
$reason = $err->getCode();
|
|
$message = $err->getMessage();
|
|
}
|
|
}
|
|
} while (5 == $reason || 6 == $reason);
|
|
}
|
|
if ($reason > 0) {
|
|
$err_message = sprintf("Error while executing query! Error message was: %s", $message);
|
|
$this->set_error(__LINE__, __FUNCTION__, $err_message);
|
|
return false;
|
|
} else {
|
|
$this->_results = $statement->fetchAll(PDO::FETCH_OBJ);
|
|
}
|
|
//generate the results that $wpdb will want to see
|
|
switch ($this->query_type) {
|
|
case 'insert':
|
|
case 'update':
|
|
case 'replace':
|
|
$this->last_insert_id = $this->pdo->lastInsertId();
|
|
$this->affected_rows = $statement->rowCount();
|
|
$this->return_value = $this->affected_rows;
|
|
break;
|
|
case 'select':
|
|
case 'show':
|
|
case 'showcolumns':
|
|
case 'showindex':
|
|
case 'describe':
|
|
case 'desc':
|
|
case 'check':
|
|
case 'analyze':
|
|
// case "foundrows":
|
|
$this->num_rows = count($this->_results);
|
|
$this->return_value = $this->num_rows;
|
|
break;
|
|
case 'delete':
|
|
$this->affected_rows = $statement->rowCount();
|
|
$this->return_value = $this->affected_rows;
|
|
break;
|
|
case 'alter':
|
|
case 'drop':
|
|
case 'create':
|
|
case 'optimize':
|
|
case 'truncate':
|
|
if ($this->is_error) {
|
|
$this->return_value = false;
|
|
} else {
|
|
$this->return_value = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
/**
|
|
* Function to extract field data to an array and prepare the query statement.
|
|
*
|
|
* If original SQL statement is CREATE query, this function do nothing and return
|
|
* true. This returned value is not used.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
private function extract_variables() {
|
|
if ($this->query_type == 'create') {
|
|
$this->prepared_query = $this->rewritten_query;
|
|
return true;
|
|
}
|
|
|
|
//long queries can really kill this
|
|
$pattern = '/(?<!\\\\)([\'"])(.*?)(?<!\\\\)\\1/imsx';
|
|
$_limit = $limit = ini_get('pcre.backtrack_limit');
|
|
do {
|
|
if ($limit > 10000000) {
|
|
$message = 'The query is too big to parse properly';
|
|
$this->set_error(__LINE__, __FUNCTION__, $message);
|
|
break; //no point in continuing execution, would get into a loop
|
|
} else {
|
|
ini_set('pcre.backtrack_limit', $limit);
|
|
$query = preg_replace_callback($pattern, array($this,'replace_variables_with_placeholders'), $this->rewritten_query);
|
|
}
|
|
$limit = $limit * 10;
|
|
} while (empty($query));
|
|
|
|
//reset the pcre.backtrack_limist
|
|
ini_set('pcre.backtrack_limit', $_limit);
|
|
$this->queries[] = "With Placeholders:\n" . $query;
|
|
$this->prepared_query = $query;
|
|
}
|
|
/**
|
|
* Call back function to replace field data with PDO parameter.
|
|
*
|
|
* @param string $matches
|
|
* @return string
|
|
*/
|
|
private function replace_variables_with_placeholders($matches) {
|
|
//remove the wordpress escaping mechanism
|
|
$param = stripslashes($matches[0]);
|
|
|
|
//remove trailing spaces
|
|
$param = trim($param);
|
|
|
|
//remove the quotes at the end and the beginning
|
|
if (in_array($param{strlen($param)-1}, array("'",'"'))) {
|
|
$param = substr($param,0,-1) ;//end
|
|
}
|
|
if (in_array($param{0}, array("'",'"'))) {
|
|
$param = substr($param, 1); //start
|
|
}
|
|
//$this->extracted_variables[] = $param;
|
|
$key = ':param_'.$this->param_num++;
|
|
$this->extracted_variables[] = $param;
|
|
//return the placeholder
|
|
//return ' ? ';
|
|
return ' '.$key.' ';
|
|
}
|
|
|
|
/**
|
|
* Function to determine which query type the argument is.
|
|
*
|
|
* It takes the query string ,determines the type and returns the type string.
|
|
* If the query is the type that SQLite Integration can't executes, returns false.
|
|
*
|
|
* @param string $query
|
|
* @return boolean|string
|
|
*/
|
|
private function determine_query_type($query) {
|
|
$result = preg_match('/^\\s*(EXPLAIN|PRAGMA|SELECT\\s*FOUND_ROWS|SELECT|INSERT|UPDATE|REPLACE|DELETE|ALTER|CREATE|DROP\\s*INDEX|DROP|SHOW\\s*\\w+\\s*\\w+\\s*|DESCRIBE|DESC|TRUNCATE|OPTIMIZE|CHECK|ANALYZE)/i', $query, $match);
|
|
|
|
if (!$result) {
|
|
return false;
|
|
}
|
|
$this->query_type = strtolower($match[1]);
|
|
if (stripos($this->query_type, 'found') !== false) {
|
|
$this->query_type = 'foundrows';
|
|
}
|
|
if (stripos($this->query_type, 'show') !== false) {
|
|
if (stripos($this->query_type, 'show tables') !== false) {
|
|
$this->query_type = 'show';
|
|
} elseif (stripos($this->query_type, 'show columns') !== false || stripos($this->query_type, 'show fields') !== false) {
|
|
$this->query_type = 'showcolumns';
|
|
} elseif (stripos($this->query_type, 'show index') !== false || stripos($this->query_type, 'show indexes') !== false || stripos($this->query_type, 'show keys') !== false) {
|
|
$this->query_type = 'showindex';
|
|
} elseif (stripos($this->query_type, 'show variables') !== false || stripos($this->query_type, 'show global variables') !== false || stripos($this->query_type, 'show session variables') !== false) {
|
|
$this->query_type = 'show_variables';
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
if (stripos($this->query_type, 'drop index') !== false) {
|
|
$this->query_type = 'drop_index';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Function to execute INSERT query for SQLite version 3.7.11 or later.
|
|
*
|
|
* SQLite version 3.7.11 began to support multiple rows insert with values
|
|
* clause. This is for that version or later.
|
|
*
|
|
* @param string $query
|
|
*/
|
|
private function execute_insert_query_new($query) {
|
|
$engine = $this->prepare_engine($this->query_type);
|
|
$this->rewritten_query = $engine->rewrite_query($query, $this->query_type);
|
|
$this->queries[] = "Rewritten:\n" . $this->rewritten_query;
|
|
$this->extract_variables();
|
|
$statement = $this->prepare_query();
|
|
$this->execute_query($statement);
|
|
}
|
|
/**
|
|
* Function to execute INSERT query for SQLite version 3.7.10 or lesser.
|
|
*
|
|
* It executes the INSERT query for SQLite version 3.7.10 or lesser. It is
|
|
* necessary to rewrite multiple row values.
|
|
*
|
|
* @param string $query
|
|
*/
|
|
private function execute_insert_query($query) {
|
|
global $wpdb;
|
|
$multi_insert = false;
|
|
$statement = null;
|
|
$engine = $this->prepare_engine($this->query_type);
|
|
if (preg_match('/(INSERT.*?VALUES\\s*)(\(.*\))/imsx', $query, $matched)) {
|
|
$query_prefix = $matched[1];
|
|
$values_data = $matched[2];
|
|
if (stripos($values_data, 'ON DUPLICATE KEY') !== false) {
|
|
$exploded_parts = $values_data;
|
|
} elseif (stripos($query_prefix, "INSERT INTO $wpdb->comments") !== false) {
|
|
$exploded_parts = $values_data;
|
|
} else {
|
|
$exploded_parts = $this->parse_multiple_inserts($values_data);
|
|
}
|
|
$count = count($exploded_parts);
|
|
if ($count > 1) {
|
|
$multi_insert = true;
|
|
}
|
|
}
|
|
if ($multi_insert) {
|
|
$first = true;
|
|
foreach ($exploded_parts as $value) {
|
|
if (substr($value, -1, 1) === ')') {
|
|
$suffix = '';
|
|
} else {
|
|
$suffix = ')';
|
|
}
|
|
$query_string = $query_prefix . ' ' . $value . $suffix;
|
|
$this->rewritten_query = $engine->rewrite_query($query_string, $this->query_type);
|
|
$this->queries[] = "Rewritten:\n" . $this->rewritten_query;
|
|
$this->extracted_variables = array();
|
|
$this->extract_variables();
|
|
if ($first) {
|
|
$statement = $this->prepare_query();
|
|
$this->execute_query($statement);
|
|
$first = false;
|
|
} else {
|
|
$this->execute_query($statement);
|
|
}
|
|
}
|
|
} else {
|
|
$this->rewritten_query = $engine->rewrite_query($query, $this->query_type);
|
|
$this->queries[] = "Rewritten:\n" . $this->rewritten_query;
|
|
$this->extract_variables();
|
|
$statement = $this->prepare_query();
|
|
$this->execute_query($statement);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function to help rewriting multiple row values insert query.
|
|
*
|
|
* It splits the values clause into an array to execute separately.
|
|
*
|
|
* @param string $values
|
|
* @return array
|
|
*/
|
|
private function parse_multiple_inserts($values) {
|
|
$tokens = preg_split("/(''|(?<!\\\\)'|(?<!\()\),(?=\s*\())/s", $values, -1, PREG_SPLIT_DELIM_CAPTURE);
|
|
$exploded_parts = array();
|
|
$part = '';
|
|
$literal = false;
|
|
foreach ($tokens as $token) {
|
|
switch ($token) {
|
|
case "),":
|
|
if (!$literal) {
|
|
$exploded_parts[] = $part;
|
|
$part = '';
|
|
} else {
|
|
$part .= $token;
|
|
}
|
|
break;
|
|
case "'":
|
|
if ($literal) {
|
|
$literal = false;
|
|
} else {
|
|
$literal = true;
|
|
}
|
|
$part .= $token;
|
|
break;
|
|
default:
|
|
$part .= $token;
|
|
break;
|
|
}
|
|
}
|
|
if (!empty($part)) {
|
|
$exploded_parts[] = $part;
|
|
}
|
|
return $exploded_parts;
|
|
}
|
|
|
|
/**
|
|
* Function to execute CREATE query.
|
|
*
|
|
* @param string
|
|
* @return boolean
|
|
*/
|
|
private function execute_create_query($query) {
|
|
$engine = $this->prepare_engine($this->query_type);
|
|
$rewritten_query = $engine->rewrite_query($query);
|
|
$reason = 0;
|
|
$message = '';
|
|
// $queries = explode(";", $this->rewritten_query);
|
|
try {
|
|
$this->beginTransaction();
|
|
foreach ($rewritten_query as $single_query) {
|
|
$this->queries[] = "Executing:\n" . $single_query;
|
|
$single_query = trim($single_query);
|
|
if (empty($single_query)) continue;
|
|
$this->pdo->exec($single_query);
|
|
}
|
|
$this->commit();
|
|
} catch (PDOException $err) {
|
|
$reason = $err->getCode();
|
|
$message = $err->getMessage();
|
|
if (5 == $reason || 6 == $reason) {
|
|
$this->commit();
|
|
} else {
|
|
$this->rollBack();
|
|
}
|
|
}
|
|
if ($reason > 0) {
|
|
$err_message = sprintf("Problem in creating table or index. Error was: %s", $message);
|
|
$this->set_error(__LINE__, __FUNCTION__, $err_message);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
/**
|
|
* Function to execute ALTER TABLE query.
|
|
*
|
|
* @param string
|
|
* @return boolean
|
|
*/
|
|
private function execute_alter_query($query) {
|
|
$engine = $this->prepare_engine($this->query_type);
|
|
$reason = 0;
|
|
$message = '';
|
|
$re_query = '';
|
|
$rewritten_query = $engine->rewrite_query($query, $this->query_type);
|
|
if (is_array($rewritten_query) && array_key_exists('recursion', $rewritten_query)) {
|
|
$re_query = $rewritten_query['recursion'];
|
|
unset($rewritten_query['recursion']);
|
|
}
|
|
try {
|
|
$this->beginTransaction();
|
|
if (is_array($rewritten_query)) {
|
|
foreach ($rewritten_query as $single_query) {
|
|
$this->queries[] = "Executing:\n" . $single_query;
|
|
$single_query = trim($single_query);
|
|
if (empty($single_query)) continue;
|
|
$this->pdo->exec($single_query);
|
|
}
|
|
} else {
|
|
$this->queries[] = "Executing:\n" . $rewritten_query;
|
|
$rewritten_query = trim($rewritten_query);
|
|
$this->pdo->exec($rewritten_query);
|
|
}
|
|
$this->commit();
|
|
} catch (PDOException $err) {
|
|
$reason = $err->getCode();
|
|
$message = $err->getMessage();
|
|
if (5 == $reason || 6 == $reason) {
|
|
$this->commit();
|
|
usleep(10000);
|
|
} else {
|
|
$this->rollBack();
|
|
}
|
|
}
|
|
if ($re_query != '') {
|
|
$this->query($re_query);
|
|
}
|
|
if ($reason > 0) {
|
|
$err_message = sprintf("Problem in executing alter query. Error was: %s", $message);
|
|
$this->set_error(__LINE__, __FUNCTION__, $err_message);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Function to execute SHOW VARIABLES query
|
|
*
|
|
* This query is meaningless for SQLite. This function returns null data with some
|
|
* exceptions and only avoids the error message.
|
|
*
|
|
* @param string
|
|
* @return ObjectArray
|
|
*/
|
|
private function show_variables_workaround($query) {
|
|
$dummy_data = array('Variable_name' => '', 'Value' => null);
|
|
$pattern = '/SHOW\\s*VARIABLES\\s*LIKE\\s*(.*)?$/im';
|
|
if (preg_match($pattern, $query, $match)) {
|
|
$value = str_replace("'", '', $match[1]);
|
|
$dummy_data['Variable_name'] = trim($value);
|
|
// this is set for Wordfence Security Plugin
|
|
if ($value == 'max_allowed_packet') {
|
|
$dummy_data['Value'] = 1047552;
|
|
} else {
|
|
$dummy_data['Value'] = '';
|
|
}
|
|
}
|
|
$_results[] = new ObjectArray($dummy_data);
|
|
$this->results = $_results;
|
|
$this->num_rows = count($this->results);
|
|
$this->return_value = $this->num_rows;
|
|
return true;
|
|
}
|
|
/**
|
|
* Function to format the queried data to that of MySQL.
|
|
*
|
|
* @param string $engine
|
|
*/
|
|
private function process_results($engine) {
|
|
if (in_array($this->query_type, array('describe', 'desc', 'showcolumns'))) {
|
|
$this->convert_to_columns_object();
|
|
} elseif ('showindex' === $this->query_type){
|
|
$this->convert_to_index_object();
|
|
} elseif (in_array($this->query_type, array('check', 'analyze'))) {
|
|
$this->convert_result_check_or_analyze();
|
|
} else {
|
|
$this->results = $this->_results;
|
|
}
|
|
}
|
|
/**
|
|
* Function to format the error messages and put out to the file.
|
|
*
|
|
* When $wpdb::suppress_errors is set to true or $wpdb::show_errors is set to false,
|
|
* the error messages are ignored.
|
|
*
|
|
* @param string $line where the error occurred.
|
|
* @param string $function to indicate the function name where the error occurred.
|
|
* @param string $message
|
|
* @return boolean
|
|
*/
|
|
private function set_error ($line, $function, $message){
|
|
global $wpdb;
|
|
$this->errors[] = array("line"=>$line, "function"=>$function);
|
|
$this->error_messages[] = $message;
|
|
$this->is_error = true;
|
|
if ($wpdb->suppress_errors) return false;
|
|
if (!$wpdb->show_errors) return false;
|
|
file_put_contents (FQDBDIR .'debug.txt', "Line $line, Function: $function, Message: $message \n", FILE_APPEND);
|
|
}
|
|
/**
|
|
* Function to change the queried data to PHP object format.
|
|
*
|
|
* It takes the associative array of query results and creates a numeric
|
|
* array of anonymous objects
|
|
*
|
|
* @access private
|
|
*/
|
|
private function convert_to_object(){
|
|
$_results = array();
|
|
if (count ($this->results) === 0){
|
|
echo $this->get_error_message();
|
|
} else {
|
|
foreach($this->results as $row){
|
|
$_results[] = new ObjectArray($row);
|
|
}
|
|
}
|
|
$this->results = $_results;
|
|
}
|
|
/**
|
|
* Function to convert the SHOW COLUMNS query data to an object.
|
|
*
|
|
* It rewrites pragma results to mysql compatible array
|
|
* when query_type is describe, we use sqlite pragma function.
|
|
*
|
|
* @access private
|
|
*/
|
|
private function convert_to_columns_object() {
|
|
$_results = array();
|
|
$_columns = array( //Field names MySQL SHOW COLUMNS returns
|
|
'Field' => "",
|
|
'Type' => "",
|
|
'Null' => "",
|
|
'Key' => "",
|
|
'Default' => "",
|
|
'Extra' => ""
|
|
);
|
|
if (count($this->_results) === 0) {
|
|
echo $this->get_error_message();
|
|
} else {
|
|
foreach ($this->_results as $row) {
|
|
$_columns['Field'] = $row->name;
|
|
$_columns['Type'] = $row->type;
|
|
$_columns['Null'] = $row->notnull ? "NO" : "YES";
|
|
$_columns['Key'] = $row->pk ? "PRI" : "";
|
|
$_columns['Default'] = $row->dflt_value;
|
|
$_results[] = new ObjectArray($_columns);
|
|
}
|
|
}
|
|
$this->results = $_results;
|
|
}
|
|
/**
|
|
* Function to convert SHOW INDEX query data to PHP object.
|
|
*
|
|
* It rewrites the result of SHOW INDEX to the Object compatible with MySQL
|
|
* added the WHERE clause manipulation (ver 1.3.1)
|
|
*
|
|
* @access private
|
|
*/
|
|
private function convert_to_index_object() {
|
|
$_results = array();
|
|
$_columns = array(
|
|
'Table' => "",
|
|
'Non_unique' => "",// unique -> 0, not unique -> 1
|
|
'Key_name' => "",// the name of the index
|
|
'Seq_in_index' => "",// column sequence number in the index. begins at 1
|
|
'Column_name' => "",
|
|
'Collation' => "",//A(scend) or NULL
|
|
'Cardinality' => "",
|
|
'Sub_part' => "",// set to NULL
|
|
'Packed' => "",// How to pack key or else NULL
|
|
'Null' => "",// If column contains null, YES. If not, NO.
|
|
'Index_type' => "",// BTREE, FULLTEXT, HASH, RTREE
|
|
'Comment' => ""
|
|
);
|
|
if (count($this->_results) == 0) {
|
|
echo $this->get_error_message();
|
|
} else {
|
|
foreach ($this->_results as $row) {
|
|
if ($row->type == 'table' && !stripos($row->sql, 'primary'))
|
|
continue;
|
|
if ($row->type == 'index' && stripos($row->name, 'sqlite_autoindex') !== false)
|
|
continue;
|
|
switch ($row->type) {
|
|
case 'table':
|
|
$pattern1 = '/^\\s*PRIMARY.*\((.*)\)/im';
|
|
$pattern2 = '/^\\s*(\\w+)?\\s*.*PRIMARY.*(?!\()/im';
|
|
if (preg_match($pattern1, $row->sql, $match)) {
|
|
$col_name = trim($match[1]);
|
|
$_columns['Key_name'] = 'PRIMARY';
|
|
$_columns['Non_unique'] = 0;
|
|
$_columns['Column_name'] = $col_name;
|
|
} elseif (preg_match($pattern2, $row->sql, $match)) {
|
|
$col_name = trim($match[1]);
|
|
$_columns['Key_name'] = 'PRIMARY';
|
|
$_columns['Non_unique'] = 0;
|
|
$_columns['Column_name'] = $col_name;
|
|
}
|
|
break;
|
|
case 'index':
|
|
if (stripos($row->sql, 'unique') !== false) {
|
|
$_columns['Non_unique'] = 0;
|
|
} else {
|
|
$_columns['Non_unique'] = 1;
|
|
}
|
|
if (preg_match('/^.*\((.*)\)/i', $row->sql, $match)) {
|
|
$col_name = str_replace("'", '', $match[1]);
|
|
$_columns['Column_name'] = trim($col_name);
|
|
}
|
|
$_columns['Key_name'] = $row->name;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
$_columns['Table'] = $row->tbl_name;
|
|
$_columns['Collation'] = NULL;
|
|
$_columns['Cardinality'] = 0;
|
|
$_columns['Sub_part'] = NULL;
|
|
$_columns['Packed'] = NULL;
|
|
$_columns['Null'] = 'NO';
|
|
$_columns['Index_type'] = 'BTREE';
|
|
$_columns['Comment'] = '';
|
|
$_results[] = new ObjectArray($_columns);
|
|
}
|
|
if (stripos($this->queries[0], 'WHERE') !== false) {
|
|
preg_match('/WHERE\\s*(.*)$/im', $this->queries[0], $match);
|
|
list($key, $value) = explode('=', $match[1]);
|
|
$key = trim($key);
|
|
$value = preg_replace("/[\';]/", '', $value);
|
|
$value = trim($value);
|
|
foreach ($_results as $result) {
|
|
if (stripos($value, $result->$key) !== false) {
|
|
unset($_results);
|
|
$_results[] = $result;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$this->results = $_results;
|
|
}
|
|
/**
|
|
* Function to the CHECK query data to an object.
|
|
*
|
|
* @access private
|
|
*/
|
|
private function convert_result_check_or_analyze() {
|
|
$results = array();
|
|
if ($this->query_type == 'check') {
|
|
$_columns = array(
|
|
'Table' => '',
|
|
'Op' => 'check',
|
|
'Msg_type' => 'status',
|
|
'Msg_text' => 'OK'
|
|
);
|
|
} else {
|
|
$_columns = array(
|
|
'Table' => '',
|
|
'Op' => 'analyze',
|
|
'Msg_type' => 'status',
|
|
'Msg_text' => 'Table is already up to date'
|
|
);
|
|
}
|
|
$_results[] = new ObjectArray($_columns);
|
|
$this->results = $_results;
|
|
}
|
|
/**
|
|
* Function to check SQLite library version.
|
|
*
|
|
* This is used for checking if SQLite can execute multiple rows insert.
|
|
*
|
|
* @return version number string or 0
|
|
* @access private
|
|
*/
|
|
private function get_sqlite_version() {
|
|
try {
|
|
$statement = $this->pdo->prepare('SELECT sqlite_version()');
|
|
$statement->execute();
|
|
$result = $statement->fetch(PDO::FETCH_NUM);
|
|
return $result[0];
|
|
} catch (PDOException $err) {
|
|
return '0';
|
|
}
|
|
}
|
|
/**
|
|
* Function to call PDO::beginTransaction().
|
|
*
|
|
* @see PDO::beginTransaction()
|
|
* @return boolean
|
|
*/
|
|
public function beginTransaction() {
|
|
if ($this->has_active_transaction) {
|
|
return false;
|
|
} else {
|
|
$this->has_active_transaction = $this->pdo->beginTransaction();
|
|
return $this->has_active_transaction;
|
|
}
|
|
}
|
|
/**
|
|
* Function to call PDO::commit().
|
|
*
|
|
* @see PDO::commit()
|
|
*/
|
|
public function commit() {
|
|
$this->pdo->commit();
|
|
$this->has_active_transaction = false;
|
|
}
|
|
/**
|
|
* Function to call PDO::rollBack().
|
|
*
|
|
* @see PDO::rollBack()
|
|
*/
|
|
public function rollBack() {
|
|
$this->pdo->rollBack();
|
|
$this->has_active_transaction = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class to change queried data to PHP object.
|
|
*
|
|
* @author kjm
|
|
*/
|
|
class ObjectArray {
|
|
function __construct($data = null,&$node= null) {
|
|
foreach ($data as $key => $value) {
|
|
if ( is_array($value) ) {
|
|
if (!$node) {
|
|
$node =& $this;
|
|
}
|
|
$node->$key = new stdClass();
|
|
self::__construct($value,$node->$key);
|
|
} else {
|
|
if (!$node) {
|
|
$node =& $this;
|
|
}
|
|
$node->$key = $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
?>
|