658 lines
13 KiB
PHP
658 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* Class SQLSRV_DataBase
|
|
*
|
|
* @version 0.2.0
|
|
* @license GPLv2
|
|
*/
|
|
|
|
class SQLServerDb {
|
|
|
|
/**
|
|
* The last ran query
|
|
*
|
|
* The last query is retained in case you want to do extended error handling in some way
|
|
*
|
|
* @since 0.1.0
|
|
* @access private
|
|
* @var string
|
|
*/
|
|
private $last_query = '';
|
|
|
|
/**
|
|
* The last Id from an sql->insert call
|
|
*
|
|
* @since 0.1.0
|
|
* @access private
|
|
* @var int
|
|
*/
|
|
private $last_insert_id = null;
|
|
|
|
/**
|
|
* Hold all errors encountered while processing a query/class construct
|
|
*
|
|
* @since 0.1.0
|
|
* @access private
|
|
* @var array
|
|
*/
|
|
private $error = array();
|
|
|
|
/**
|
|
* The database connection is held here
|
|
*
|
|
* @since 0.1.0
|
|
* @access private
|
|
* @var false|null|resource
|
|
*/
|
|
private $db = null;
|
|
|
|
/**
|
|
* The Database Schema is read into memory and kept here
|
|
*
|
|
* This is done because MSSQL is very picky about data types and containers, so if enabled
|
|
* the class will download the schema and keep it on hand to properly handle various data types
|
|
*
|
|
* @since 0.2.0
|
|
* @access private
|
|
* @var array|bool|mixed
|
|
*/
|
|
private $schema = false;
|
|
|
|
/**
|
|
* The storage location for the DB schema
|
|
*
|
|
* @since 0.2.0
|
|
* @access private
|
|
* @var null|string
|
|
*/
|
|
private $schema_location = null;
|
|
|
|
/**
|
|
* The numbrs of rows affected by a query
|
|
*
|
|
* @since 0.1.0
|
|
* @var int
|
|
*/
|
|
public $num_rows = 0;
|
|
|
|
/**
|
|
* If a query has returned any rows or not
|
|
*
|
|
* @since 0.1.0
|
|
* @var bool
|
|
*/
|
|
public $has_rows = false;
|
|
|
|
/**
|
|
* If a connection ot the database exists
|
|
*
|
|
* @since 0.1.0
|
|
* @var bool
|
|
*/
|
|
public $is_connected = false;
|
|
|
|
/**
|
|
* Database Username
|
|
*
|
|
* @since 0.1.0
|
|
* @access protected
|
|
* @var string
|
|
*/
|
|
protected $dbuser;
|
|
|
|
/**
|
|
* Database Password
|
|
*
|
|
* @since 0.1.0
|
|
* @access protected
|
|
* @var string
|
|
*/
|
|
protected $dbpassword;
|
|
|
|
/**
|
|
* Database Host
|
|
* @since 0.1.0
|
|
* @var string
|
|
*/
|
|
protected $dbhost;
|
|
|
|
/**
|
|
* Database Name
|
|
* @since 0.1.0
|
|
* @var string
|
|
*/
|
|
protected $dbname;
|
|
|
|
/**
|
|
* Database Port
|
|
* @since 0.1.0
|
|
* @var int
|
|
*/
|
|
protected $dbport;
|
|
|
|
/**
|
|
* SQLSRV_DataBase constructor.
|
|
*
|
|
* @since 0.1.0
|
|
* @since 0.2.0 Added the optional `$build_schema` parameter
|
|
*
|
|
* @param string $dbuser MSSQL database user
|
|
* @param string $dbpassword MSSQL database password
|
|
* @param string $dbname MSSQL database name
|
|
* @param string $dbhost MSSQL database host
|
|
* @param int $dbport MSSQL database port
|
|
* @param mixed $build_schema Where (if at all) to store the DB schema
|
|
*/
|
|
public function __construct( $dbuser, $dbpassword, $dbname, $dbhost, $dbport = 1433, $build_schema = false ) {
|
|
$this->dbuser = $dbuser;
|
|
$this->dbpassword = $dbpassword;
|
|
$this->dbname = $dbname;
|
|
$this->dbhost = $dbhost;
|
|
$this->dbport = $dbport;
|
|
|
|
$this->is_connected = $this->db_connect();
|
|
|
|
// If we've chosen to build a database schema, this is done on construct
|
|
if ( $this->is_connected && $build_schema ) {
|
|
if ( is_string( $build_schema ) ) {
|
|
$this->schema_location = $build_schema;
|
|
}
|
|
else {
|
|
// Set the schema store location to be alongside the DB class
|
|
$this->schema_location = dirname( __FILE__ );
|
|
}
|
|
$this->schema = $this->build_schema();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connect to and select database
|
|
*
|
|
* @since 0.1.0
|
|
* @return bool
|
|
*/
|
|
public function db_connect() {
|
|
$serverName = "tcp:" . $this->dbhost . ", " . $this->dbport;
|
|
$connectionOptions = array(
|
|
"Database" => $this->dbname,
|
|
"UID" => $this->dbuser,
|
|
"PWD" => $this->dbpassword
|
|
);
|
|
|
|
// Create the connection resource
|
|
$this->db = sqlsrv_connect( $serverName, $connectionOptions );
|
|
|
|
|
|
// If the connection fails we get a false value and build our error log
|
|
if ( false === $this->db )
|
|
{
|
|
/*
|
|
* We don't use log_error() here as the values passed from a failed connection
|
|
* are not compatible with the errors passed from a failed query
|
|
*/
|
|
$error = sqlsrv_errors();
|
|
$this->error[] = $error;
|
|
error_log( 'Database failure: ' . print_r($error, true) );
|
|
return false;
|
|
}
|
|
sqlsrv_configure( 'WarningsReturnAsErrors', true );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Build the database schema based on table structures
|
|
*
|
|
* @param bool $force Force rewrite the schemas file
|
|
*
|
|
* @since 0.2.0
|
|
*
|
|
* @return array|bool|mixed
|
|
*/
|
|
private function build_schema( $force = false ) {
|
|
$schema_file = $this->schema_location . '/db-schema.php';
|
|
|
|
/*
|
|
* We return the data of the existing schema file if it exists and we aren't force re-writing it
|
|
*/
|
|
if ( file_exists( $schema_file ) && ! $force ) {
|
|
return json_decode( file_get_contents( $schema_file ) );
|
|
}
|
|
|
|
// Check if we can open the file location for writing
|
|
if ( ! $file = fopen( $schema_file, "w+" ) ) {
|
|
return false;
|
|
}
|
|
|
|
$schema = array();
|
|
|
|
$tables = $this->get_results( "
|
|
SELECT
|
|
TABLE_NAME
|
|
FROM
|
|
INFORMATION_SCHEMA.TABLES
|
|
WHERE
|
|
TABLE_TYPE = 'BASE TABLE'
|
|
AND
|
|
TABLE_CATALOG = '" . addslashes( DB_NAME ) . "'
|
|
" );
|
|
foreach( $tables AS $table ) {
|
|
$schema[ $table->TABLE_NAME ] = array();
|
|
|
|
$columns = $this->get_results( "
|
|
EXEC
|
|
sp_columns
|
|
" . $table->TABLE_NAME . "
|
|
" );
|
|
foreach( $columns AS $column ) {
|
|
$schema[ $table->TABLE_NAME ][ $column->COLUMN_NAME ] = $column;
|
|
}
|
|
}
|
|
|
|
fwrite( $file, json_encode( $schema ) );
|
|
fclose( $file );
|
|
|
|
return $schema;
|
|
}
|
|
|
|
/**
|
|
* Prepare values based on either the expected schema data (if it exists) or by what type of data it is
|
|
*
|
|
* @param string $table
|
|
* @param string $column
|
|
* @param mixed $value
|
|
*
|
|
* @since 0.2.0
|
|
*
|
|
* @return string
|
|
*/
|
|
private function schema_prepare_value( $table, $column, $value ) {
|
|
if ( false === $this->schema || ! isset( $this->schema->$table ) || ! isset( $this->schema->$table->$column ) ) {
|
|
if ( null === $value ) {
|
|
return 'NULL';
|
|
}
|
|
elseif ( ctype_digit( str_replace( array( '.' ), '', $value ) ) && substr_count( $value, '.' ) < 2 ) {
|
|
if(preg_match("/^0/",$value)) {
|
|
return "'".addslashes( utf8_decode( $value ) )."'";
|
|
}
|
|
else
|
|
return $value;
|
|
}
|
|
else {
|
|
return "'" . addslashes( utf8_decode( $value ) ) . "'";
|
|
}
|
|
}
|
|
|
|
$schema = $this->schema->$table->$column;
|
|
$numerics = array(
|
|
'int',
|
|
'decimal',
|
|
'money'
|
|
);
|
|
|
|
if ( in_array( $schema->TYPE_NAME, $numerics ) ) {
|
|
if ( null === $value || '' === $value ) {
|
|
if ( 1 == $schema->NULLABLE ) {
|
|
return 'NULL';
|
|
}
|
|
else {
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
return $value;
|
|
}
|
|
}
|
|
else {
|
|
if ( null === $value || empty( $value ) ) {
|
|
if ( 1 == $schema->NULLABLE ) {
|
|
return 'NULL';
|
|
}
|
|
else {
|
|
return "''";
|
|
}
|
|
}
|
|
}
|
|
|
|
return "'" . addslashes( utf8_decode( $value ) ) . "'";
|
|
}
|
|
|
|
|
|
/**
|
|
* Prepare the DB class for a new query
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return void
|
|
*/
|
|
private function prepare() {
|
|
$this->error = array();
|
|
$this->last_insert_id = null;
|
|
$this->last_query = '';
|
|
$this->num_rows = 0;
|
|
$this->has_rows = false;
|
|
}
|
|
|
|
/**
|
|
* Log errors to the error container of the class and to the systems error log
|
|
*
|
|
* @param array $errors
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return void
|
|
*/
|
|
private function log_error( $errors ) {
|
|
foreach( $errors AS $error ) {
|
|
$new_error = array(
|
|
'SQLSTATE' => $error['SQLSTATE'],
|
|
'code' => $error['code'],
|
|
'message' => $error['message'],
|
|
'query' => $this->last_query
|
|
);
|
|
|
|
error_log( var_export( $new_error, true ) );
|
|
$this->error[] = $new_error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update values in a table that matches the give ncriterias
|
|
*
|
|
* @param string $table
|
|
* @param array $what
|
|
* @param array $where
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function update( $table, $what, $where = array() ) {
|
|
$set = '';
|
|
$check = '';
|
|
|
|
foreach( $what AS $field => $value ) {
|
|
$field = trim( $field );
|
|
$value = trim( $value );
|
|
|
|
if ( ! empty( $set ) ) {
|
|
$set .= ', ';
|
|
}
|
|
$set .= $table . '.' . $field . ' = ';
|
|
|
|
$set .= $this->schema_prepare_value( $table, $field, $value );
|
|
}
|
|
|
|
foreach( $where AS $field => $value ) {
|
|
$check .= ' AND ' . $table . '.' . $field;
|
|
if ( null === $value ) {
|
|
$check .= ' IS NULL';
|
|
}
|
|
elseif ( ctype_digit( str_replace( array( '.', '-' ), '', $value ) ) && substr_count( $value, '.' ) < 2 ) {
|
|
$check .= ' = ' . $value;
|
|
}
|
|
else {
|
|
$check .= " = '" . addslashes( utf8_decode( $value ) ) . "'";
|
|
}
|
|
}
|
|
|
|
$result = $this->query( "
|
|
UPDATE
|
|
" . $table . "
|
|
SET
|
|
" . $set . "
|
|
WHERE
|
|
1 = 1
|
|
" . $check . "
|
|
", false );
|
|
}
|
|
|
|
/**
|
|
* Delete rows in a table based on the given criterias
|
|
*
|
|
* @param string $table
|
|
* @param array $where
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function delete( $table, $where = array() ) {
|
|
$check = '';
|
|
foreach( $where AS $field => $value ) {
|
|
$field = trim( $field );
|
|
$value = trim( $value );
|
|
|
|
$check .= ' AND ' . $table . '.' . $field;
|
|
if ( null === $value ) {
|
|
$check .= ' IS NULL';
|
|
}
|
|
if ( ctype_digit( str_replace( array( '.', '-' ), '', $value ) ) && substr_count( $value, '.' ) < 2 ) {
|
|
$check .= ' = ' . $value;
|
|
}
|
|
else {
|
|
$check .= " = '" . addslashes( utf8_decode( $value ) ) . "'";
|
|
}
|
|
}
|
|
|
|
$result = $this->query( "
|
|
DELETE FROM
|
|
" . $table . "
|
|
WHERE
|
|
1 = 1
|
|
" . $check . "
|
|
", false );
|
|
}
|
|
|
|
/**
|
|
* Insert a new row and populate it with the given values
|
|
*
|
|
* @param string $table
|
|
* @param array $data
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return void
|
|
*/
|
|
public function insert( $table, $data ) {
|
|
$fields = '';
|
|
$values = '';
|
|
|
|
foreach( $data AS $field => $value ) {
|
|
$field = trim( $field );
|
|
$value = trim( $value );
|
|
|
|
if ( ! empty( $fields ) ) {
|
|
$fields .= ', ';
|
|
}
|
|
if ( ! empty( $values ) ) {
|
|
$values .= ', ';
|
|
}
|
|
|
|
$fields .= $table . '.' . $field;
|
|
|
|
$values .= $this->schema_prepare_value( $table, $field, $value );
|
|
}
|
|
|
|
$result = $this->query( "
|
|
INSERT INTO
|
|
" . $table . " ( " . $fields . " )
|
|
VALUES ( " . $values . " )
|
|
", false );
|
|
}
|
|
|
|
/**
|
|
* Get a single row from the database and return it in the given format
|
|
*
|
|
* @param string $query
|
|
* @param string $format
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return array|bool|null|object
|
|
*/
|
|
public function get_row( $query, $format = 'object' ) {
|
|
$request = $this->query( $query );
|
|
|
|
if ( ! $this->has_error() ) {
|
|
if ( 'array' == $format ) {
|
|
$response = sqlsrv_fetch_array( $request, SQLSRV_FETCH_ASSOC );
|
|
}
|
|
else {
|
|
$response = sqlsrv_fetch_object( $request );
|
|
}
|
|
}
|
|
else {
|
|
$response = false;
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Get all the rows returned by a query to the database
|
|
*
|
|
* @param string $query
|
|
* @param string $format
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return array|bool
|
|
*/
|
|
public function get_results( $query, $format = 'object' ) {
|
|
$response = array();
|
|
|
|
$request = $this->query( $query );
|
|
|
|
if ( $this->has_error() ) {
|
|
$response = false;
|
|
}
|
|
else {
|
|
if ( 'array' == $format ) {
|
|
while ( $answer = sqlsrv_fetch_array( $request, SQLSRV_FETCH_ASSOC ) ) {
|
|
$response[] = $answer;
|
|
}
|
|
}
|
|
else {
|
|
while ( $answer = sqlsrv_fetch_object( $request ) ) {
|
|
$response[] = $answer;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Return the primary index value from a table
|
|
*
|
|
* @since 0.2.0
|
|
*
|
|
* @return bool|int
|
|
*/
|
|
public function last_insert_id() {
|
|
if ( $this->has_error() || empty( $this->last_query ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( empty( $this->last_insert_id ) ) {
|
|
$this->last_insert_id = $this->get_row( "SELECT SCOPE_IDENTITY() AS [SCOPE_IDENTITY]" );
|
|
}
|
|
|
|
return $this->last_insert_id->SCOPE_IDENTITY;
|
|
}
|
|
|
|
/**
|
|
* @since 0.1.0
|
|
* @deprecated 0.2.0 Use last_insert_id()
|
|
* @see last_insert_id()
|
|
*
|
|
* @return bool|int
|
|
*/
|
|
public function get_last_id() {
|
|
return $this->last_insert_id();
|
|
}
|
|
|
|
/**
|
|
* Runs the actual query against the database
|
|
*
|
|
* @param string $query
|
|
* @param bool $can_get_rows
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return bool|resource
|
|
*/
|
|
public function query( $query, $can_get_rows = true ) {
|
|
// If no connection is found we try to restore it
|
|
if ( ! $this->is_connected ) {
|
|
$this->is_connected = $this->db_connect();
|
|
|
|
// If we couldn't reconnect we break out early
|
|
if ( ! $this->is_connected ) {
|
|
return false;
|
|
}
|
|
}
|
|
$this->prepare();
|
|
$this->last_query = $query;
|
|
|
|
$doing_query = sqlsrv_query( $this->db, $query );
|
|
|
|
if ( false === $doing_query ) {
|
|
if ( null != ( $errors = sqlsrv_errors() ) ) {
|
|
$this->log_error( $errors );
|
|
}
|
|
}
|
|
else {
|
|
$this->has_rows = true;
|
|
$this->num_rows = sqlsrv_num_rows( $doing_query );
|
|
}
|
|
|
|
if ( $can_get_rows ) {
|
|
if ( sqlsrv_has_rows( $doing_query ) ) {
|
|
$this->has_rows = true;
|
|
} else {
|
|
$this->has_rows = false;
|
|
}
|
|
}
|
|
|
|
return $doing_query;
|
|
}
|
|
|
|
/**
|
|
* Return a list of errors encountered on the last query, or false
|
|
*
|
|
* @since 0.2.0
|
|
*
|
|
* @return array|bool
|
|
*/
|
|
public function has_error() {
|
|
if ( ! empty( $this->error ) ) {
|
|
return $this->error;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @since 0.1.0
|
|
* @deprecated 0.2.0 Use has_error() instead
|
|
* @see has_error()
|
|
*
|
|
* @return array|bool
|
|
*/
|
|
public function hasError() {
|
|
return $this->has_error();
|
|
}
|
|
|
|
/**
|
|
* Return the last ran query in its entirety
|
|
*
|
|
* @since 0.1.0
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_last_query() {
|
|
return $this->last_query;
|
|
}
|
|
} |