init();
}
function __destruct() {
$this->pdo = null;
return true;
}
/**
* Function to initialize database
* 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!
", 'sqlite-integration');
$message .= sprintf(__("Error message is: %s", 'sqlite-integration'), $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 connection error!
", 'sqlite-integration');
$message .= sprintf(__("Error message is: %s", 'sqlite-integration'), $err->getMessage());
$this->set_error(__LINE__, __FUNCTION__, $message);
return false;
}
$this->make_sqlite_tables();
}
}
/**
* Make database direcotry and .htaccess file
* executed once while installation process
*/
private function prepare_directory() {
global $wpdb;
$u = umask(0000);
if (!is_dir(FQDBDIR)) {
if (!@mkdir(FQDBDIR, 0777, true)) {
umask($u);
$message = __('Unable to create the required directory! Please check your server settings.', 'sqlite-integration');
echo $message;
return false;
}
}
if (!is_writable(FQDBDIR)) {
umask($u);
$message = __('Unable to create a file in the directory! Please check your server settings.', 'sqlite-integration');
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.', 'sqlite-integration');
echo $message;
return false;
}
fwrite($fh, "DENY FROM ALL");
fclose($fh);
}
umask($u);
return true;
}
/**
* Make database file itself and WordPress tables
* executed once while installation process
*/
private function make_sqlite_tables() {
require_once PDODIR . "install.php";
}
public function query($query) {
$this->flush();
$this->queries[] = "Raw query:\t$query";
$res = $this->determine_query_type($query);
if (!$res) {
$bailoutString = sprintf(__("
Sorry, we cannot determine the type of query that is requested.
The query is %s
", 'sqlite-integration'), $query); $this->set_error(__LINE__, __FUNCTION__, $bailoutString); } switch (strtolower($this->query_type)) { case 'foundrows': $this->results = $this->found_rows_result; $this->num_rows = count($this->results); $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: $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(), FLIE_APPEND); } return $this->return_value; } public function get_insert_id() { return $this->last_insert_id; } public function get_affected_rows() { return $this->affected_rows; } public function get_columns() { return $this->column_names; } public function get_query_results() { return $this->results; } public function get_num_rows() { return $this->num_rows; } public function get_return_value() { return $this->return_value; } public function get_error_message(){ if (count($this->error_messages) === 0){ $this->is_error = false; $this->error_messages = array(); return ''; } $output = '" . ob_get_contents() . ""; ob_end_clean(); return $output; } private function get_debug_info(){ $output = ''; foreach ($this->queries as $q){ $output .= $q ."\r\n"; } return $output; } 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_names = 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; } 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; } private function prepare_query(){ $this->queries[] = "Prepare:\t". $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", 'sqlite-integration'), $message); $this->set_error(__LINE__, __FUNCTION__, $err_message); } return $statement; } private function execute_query($statement) { $reason = 0; $message = ''; if (!is_object($statement)) return; if (count($this->extracted_variables) > 0) { $this->queries[] = "Executing: ". 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)\t "; 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", 'sqlite-integration'), $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 "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; } } private function extract_variables() { if ($this->query_type == 'create') { $this->prepared_query = $this->rewritten_query; return; } //long queries can really kill this $pattern = '/(? 10000000) { $message = __("The query is too big to parse properly", 'sqlite-integration'); $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: $query "; $this->prepared_query = $query; } 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.' '; } /** * takes the query string ,determines the type and returns the type string * if the query is the type PDO for Wordpress 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)/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; } /** * SQLite version 3.7.11 began 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: $this->rewritten_query"; $this->extract_variables(); $statement = $this->prepare_query(); $this->execute_query($statement); } /** * executes the INSERT query for SQLite version 3.7.10 or lesser * @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: $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: $this->rewritten_query"; $this->extract_variables(); $statement = $this->prepare_query(); $this->execute_query($statement); } } /** * helper function for execute_insert_query() * @param string $values * @return array */ private function parse_multiple_inserts($values) { $tokens = preg_split("/(''|(?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:\t" . $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", 'sqlite-integration'), $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 = ''; $rewritten_query = $engine->rewrite_query($query, $this->query_type); try { $this->beginTransaction(); if (is_array($rewritten_query)) { foreach ($rewritten_query as $single_query) { $this->queries[] = "Executing:\t" . $single_query; $single_query = trim($single_query); if (empty($single_query)) continue; $this->pdo->exec($single_query); } } else { $this->queries[] = "Executing:\t" . $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 ($reason > 0) { $err_message = sprintf(__("Problem in executing alter query. Error was: %s", 'sqlite-integration'), $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 and * avoid 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; } $_results[] = new ObjectArray($dummy_data); $this->results = $_results; $this->num_rows = count($this->results); $this->return_value = $this->num_rows; return true; } 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(); } else { $this->results = $this->_results; } } 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); } /** * method that takes the associative array of query results and creates a numeric array of anonymous objects */ 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; } /** * method to rewrite pragma results to mysql compatible array * when query_type is describe, we use sqlite pragma function. * see pdo_sqlite_driver.php */ 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; } /** * rewrites the result of SHOW INDEX to the Object compatible with MySQL */ 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); } } $this->results = $_results; } /** * function to get SQLite library version * this is used for checking if SQLite can execute multiple rows insert * @return version number string or 0 */ 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 call to PDO::beginTransaction() * @see PDO::beginTransaction() */ public function beginTransaction() { if ($this->has_active_transaction) { return false; } else { $this->has_active_transaction = $this->pdo->beginTransaction(); return $this->has_active_transaction; } } /** * function call to PDO::commit() * @see PDO::commit() */ public function commit() { $this->pdo->commit(); $this->has_active_transaction = false; } /** * function call to PDO::rollBack() * @see PDO::rollBack() */ public function rollBack() { $this->pdo->rollBack(); $this->has_active_transaction = false; } } 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; } } } } ?>