mirror of
https://github.com/kennethreitz-archive/shopp.git
synced 2026-06-05 23:40:16 +00:00
423 lines
12 KiB
PHP
423 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* DB classes
|
|
* Database management classes
|
|
*
|
|
* @author Jonathan Davis
|
|
* @version 1.0
|
|
* @copyright Ingenesis Limited, 28 March, 2008
|
|
* @package Shopp
|
|
**/
|
|
|
|
define("AS_ARRAY",false);
|
|
define("SHOPP_DBPREFIX","shopp_");
|
|
if (!defined('SHOPP_QUERY_DEBUG')) define('SHOPP_QUERY_DEBUG',false);
|
|
|
|
// Make sure that compatibility mode is not enabled
|
|
if (ini_get('zend.ze1_compatibility_mode'))
|
|
ini_set('zend.ze1_compatibility_mode','Off');
|
|
|
|
class DB {
|
|
private static $instance;
|
|
// Define datatypes for MySQL
|
|
var $_datatypes = array("int" => array("int", "bit", "bool", "boolean"),
|
|
"float" => array("float", "double", "decimal", "real"),
|
|
"string" => array("char", "binary", "varbinary", "text", "blob"),
|
|
"list" => array("enum","set"),
|
|
"date" => array("date", "time", "year")
|
|
);
|
|
var $results = array();
|
|
var $queries = array();
|
|
var $dbh = false;
|
|
|
|
|
|
protected function DB () {
|
|
global $wpdb;
|
|
$this->dbh = $wpdb->dbh;
|
|
$this->version = mysql_get_server_info();
|
|
}
|
|
|
|
function __clone() { trigger_error('Clone is not allowed.', E_USER_ERROR); }
|
|
static function &get() {
|
|
if (!self::$instance instanceof self)
|
|
self::$instance = new self;
|
|
return self::$instance;
|
|
}
|
|
|
|
|
|
/*
|
|
* Connects to the database server */
|
|
function connect($user, $password, $database, $host) {
|
|
$this->dbh = @mysql_connect($host, $user, $password);
|
|
if (!$this->dbh) trigger_error("Could not connect to the database server '$host'.");
|
|
else $this->select($database);
|
|
}
|
|
|
|
/**
|
|
* Select the database to use for our connection */
|
|
function select($database) {
|
|
if(!@mysql_select_db($database,$this->dbh))
|
|
trigger_error("Could not select the '$database' database.");
|
|
}
|
|
|
|
/**
|
|
* Escape contents of data for safe insertion into the db */
|
|
function escape($data) {
|
|
// Prevent double escaping by stripping any existing escapes out
|
|
return addslashes(stripslashes($data));
|
|
}
|
|
|
|
function clean($data) {
|
|
if (is_array($data)) array_map(array(&$this,'clean'), $data);
|
|
if (is_string($data)) rtrim($data);
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Send a query to the database */
|
|
function query($query, $output=true, $errors=true) {
|
|
if (SHOPP_QUERY_DEBUG) $this->queries[] = $query;
|
|
$result = @mysql_query($query, $this->dbh);
|
|
if (SHOPP_QUERY_DEBUG && class_exists('ShoppError')) new ShoppError($query,'shopp_query_debug',SHOPP_DEBUG_ERR);
|
|
|
|
// Error handling
|
|
if ($this->dbh && $error = mysql_error($this->dbh))
|
|
new ShoppError(sprintf(__('Query failed: %s - DB Query: %s','Shopp'),$error, str_replace("\n","",$query)),'shopp_query_error',SHOPP_DB_ERR);
|
|
|
|
// Results handling
|
|
if ( preg_match("/^\\s*(create|drop|insert|delete|update|replace) /i",$query) ) {
|
|
$this->affected = mysql_affected_rows();
|
|
if ( preg_match("/^\\s*(insert|replace) /i",$query) ) {
|
|
$insert = @mysql_fetch_object(@mysql_query("SELECT LAST_INSERT_ID() AS id", $this->dbh));
|
|
return $insert->id;
|
|
}
|
|
|
|
if ($this->affected > 0) return $this->affected;
|
|
else return true;
|
|
} else {
|
|
if ($result === true) return true;
|
|
$this->results = array();
|
|
while ($row = @mysql_fetch_object($result)) {
|
|
$this->results[] = $row;
|
|
}
|
|
@mysql_free_result($result);
|
|
if ($output && sizeof($this->results) == 1) $this->results = $this->results[0];
|
|
return $this->results;
|
|
}
|
|
|
|
}
|
|
|
|
function datatype($type) {
|
|
foreach((array)$this->_datatypes as $datatype => $patterns) {
|
|
foreach((array)$patterns as $pattern) {
|
|
if (strpos($type,$pattern) !== false) return $datatype;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Prepare the data properties for entry into
|
|
* the database */
|
|
function prepare($object) {
|
|
$data = array();
|
|
|
|
// Go through each data property of the object
|
|
foreach(get_object_vars($object) as $property => $value) {
|
|
if (!isset($object->_datatypes[$property])) continue;
|
|
|
|
// If the property is has a _datatype
|
|
// it belongs in the database and needs
|
|
// to be prepared
|
|
|
|
// Process the data
|
|
switch ($object->_datatypes[$property]) {
|
|
case "string":
|
|
// Escape characters in strings as needed
|
|
if (is_array($value)) $value = serialize($value);
|
|
$data[$property] = "'".$this->escape($value)."'";
|
|
break;
|
|
case "list":
|
|
// If value is empty, skip setting the field
|
|
// so it inherits the default value in the db
|
|
if (!empty($value))
|
|
$data[$property] = "'$value'";
|
|
break;
|
|
case "date":
|
|
// If it's an empty date, set it to now()'s timestamp
|
|
if (is_null($value)) {
|
|
$data[$property] = "now()";
|
|
// If the date is an integer, convert it to an
|
|
// sql YYYY-MM-DD HH:MM:SS format
|
|
} elseif (!empty($value) && is_int(intval($value))) {
|
|
$data[$property] = "'".mkdatetime(intval($value))."'";
|
|
// Otherwise it's already ready, so pass it through
|
|
} else {
|
|
$data[$property] = "'$value'";
|
|
}
|
|
break;
|
|
case "int":
|
|
case "float":
|
|
$value = floatnum($value);
|
|
|
|
$data[$property] = "'$value'";
|
|
|
|
if (empty($value)) $data[$property] = "'0'";
|
|
|
|
// Special exception for id fields
|
|
if ($property == "id" && empty($value)) $data[$property] = "NULL";
|
|
|
|
break;
|
|
default:
|
|
// Anything not needing processing
|
|
// passes through into the object
|
|
$data[$property] = "'$value'";
|
|
}
|
|
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Get the list of possible values for an enum() or set() column */
|
|
function column_options($table = null, $column = null) {
|
|
if ( ! ($table && $column)) return array();
|
|
$r = $this->query("SHOW COLUMNS FROM $table LIKE '$column'");
|
|
if ( strpos($r[0]->Type,"enum('") )
|
|
$list = substr($r[0]->Type, 6, strlen($r[0]->Type) - 8);
|
|
|
|
if ( strpos($r[0]->Type,"set('") )
|
|
$list = substr($r[0]->Type, 5, strlen($r[0]->Type) - 7);
|
|
|
|
return explode("','",$list);
|
|
}
|
|
|
|
/**
|
|
* Send a large set of queries to the database. */
|
|
function loaddata ($queries) {
|
|
$queries = explode(";\n", $queries);
|
|
array_pop($queries);
|
|
foreach ($queries as $query) if (!empty($query)) $this->query($query);
|
|
return true;
|
|
}
|
|
|
|
|
|
} // END class DB
|
|
|
|
|
|
/* class DatabaseObject
|
|
* Generic database glueware between the database and the active data model */
|
|
|
|
class DatabaseObject {
|
|
|
|
function DatabaseObject () {
|
|
// Constructor
|
|
}
|
|
|
|
/**
|
|
* Initializes the db object by grabbing table schema
|
|
* so we know how to work with this table */
|
|
function init ($table,$key="id") {
|
|
global $Shopp;
|
|
$db = DB::get();
|
|
|
|
$this->_table = $this->tablename($table); // So we know what the table name is
|
|
$this->_key = $key; // So we know what the primary key is
|
|
$this->_datatypes = array(); // So we know the format of the table
|
|
$this->_lists = array(); // So we know the options for each list
|
|
$this->_defaults = array(); // So we know the default values for each field
|
|
|
|
if (isset($Shopp->Settings)) {
|
|
$Tables = $Shopp->Settings->get('data_model');
|
|
if (isset($Tables[$this->_table])) {
|
|
$this->_datatypes = $Tables[$this->_table]->_datatypes;
|
|
$this->_lists = $Tables[$this->_table]->_lists;
|
|
foreach($this->_datatypes as $property => $type) {
|
|
$this->{$property} = (isset($this->_defaults[$property]))?
|
|
$this->_defaults[$property]:'';
|
|
if (empty($this->{$property}) && $type == "date")
|
|
$this->{$property} = null;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!$r = $db->query("SHOW COLUMNS FROM $this->_table",true,false)) return false;
|
|
|
|
// Map out the table definition into our data structure
|
|
foreach($r as $object) {
|
|
$property = $object->Field;
|
|
$this->{$property} = $object->Default;
|
|
$this->_datatypes[$property] = $db->datatype($object->Type);
|
|
$this->_defaults[$property] = $object->Default;
|
|
|
|
// Grab out options from list fields
|
|
if ($db->datatype($object->Type) == "list") {
|
|
$values = str_replace("','", ",", substr($object->Type,strpos($object->Type,"'")+1,-2));
|
|
$this->_lists[$property] = explode(",",$values);
|
|
}
|
|
}
|
|
|
|
if (isset($Shopp->Settings)) {
|
|
$Tables[$this->_table] = new StdClass();
|
|
$Tables[$this->_table]->_datatypes = $this->_datatypes;
|
|
$Tables[$this->_table]->_lists = $this->_lists;
|
|
$Tables[$this->_table]->_defaults = $this->_defaults;
|
|
$Shopp->Settings->save('data_model',$Tables);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a single record by the primary key or a custom query
|
|
* @param $where - An array of key/values to be built into an SQL where clause
|
|
* or
|
|
* @param $id - A string containing the id for db object's predefined primary key
|
|
* or
|
|
* @param $id - A string containing the object's id value
|
|
* @param $key - A string of the name of the db object's primary key
|
|
**/
|
|
function load () {
|
|
$db = DB::get();
|
|
|
|
$args = func_get_args();
|
|
if (empty($args[0])) return false;
|
|
|
|
$where = "";
|
|
if (is_array($args[0]))
|
|
foreach ($args[0] as $key => $id)
|
|
$where .= ($where == ""?"":" AND ")."$key='".$db->escape($id)."'";
|
|
else {
|
|
$id = $args[0];
|
|
$key = $this->_key;
|
|
if (!empty($args[1])) $key = $args[1];
|
|
$where = $key."='".$db->escape($id)."'";
|
|
}
|
|
|
|
$r = $db->query("SELECT * FROM $this->_table WHERE $where LIMIT 1");
|
|
$this->populate($r);
|
|
|
|
if (!empty($this->id)) return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Processes a bulk string of semi-colon separated SQL queries */
|
|
function loaddata ($queries) {
|
|
$queries = explode(";\n", $queries);
|
|
array_pop($queries);
|
|
foreach ($queries as $query) if (!empty($query)) $this->query($query);
|
|
return true;
|
|
}
|
|
|
|
function tablename ($table) {
|
|
global $table_prefix;
|
|
return $table_prefix.SHOPP_DBPREFIX.$table;
|
|
}
|
|
|
|
/**
|
|
* Save a record, updating when we have a value for the primary key,
|
|
* inserting a new record when we don't */
|
|
function save () {
|
|
$db = DB::get();
|
|
|
|
$data = $db->prepare($this);
|
|
$id = $this->{$this->_key};
|
|
// Update record
|
|
if (!empty($id)) {
|
|
if (isset($data['modified'])) $data['modified'] = "now()";
|
|
$dataset = $this->dataset($data);
|
|
$db->query("UPDATE $this->_table SET $dataset WHERE $this->_key=$id");
|
|
return true;
|
|
// Insert new record
|
|
} else {
|
|
if (isset($data['created'])) $data['created'] = "now()";
|
|
if (isset($data['modified'])) $data['modified'] = "now()";
|
|
$dataset = $this->dataset($data);
|
|
//print "INSERT $this->_table SET $dataset";
|
|
$this->id = $db->query("INSERT $this->_table SET $dataset");
|
|
return $this->id;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Deletes the record associated with this object */
|
|
function delete () {
|
|
$db = DB::get();
|
|
// Delete record
|
|
$id = $this->{$this->_key};
|
|
if (!empty($id)) $db->query("DELETE FROM $this->_table WHERE $this->_key='$id'");
|
|
else return false;
|
|
}
|
|
|
|
/**
|
|
* Populate the object properties from a set of
|
|
* loaded results */
|
|
function populate($data) {
|
|
if(empty($data)) return false;
|
|
foreach(get_object_vars($data) as $property => $value) {
|
|
if (empty($this->_datatypes[$property])) continue;
|
|
// Process the data
|
|
switch ($this->_datatypes[$property]) {
|
|
case "date":
|
|
$this->{$property} = mktimestamp($value);
|
|
break;
|
|
case "int":
|
|
case "float":
|
|
case "string":
|
|
// If string has been serialized, unserialize it
|
|
if (preg_match("/^[sibNaO](?:\:.+?\{.*\}$|\:.+;$|;$)/s",$value)) $value = unserialize($value);
|
|
default:
|
|
// Anything not needing processing
|
|
// passes through into the object
|
|
$this->{$property} = $value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build an SQL-ready string of the prepared data
|
|
* for entry into the database */
|
|
function dataset($data) {
|
|
$query = "";
|
|
foreach($data as $property => $value) {
|
|
if (!empty($query)) $query .= ", ";
|
|
$query .= "$property=$value";
|
|
}
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Populate the object properties from a set of
|
|
* form post inputs */
|
|
function updates($data,$ignores = array()) {
|
|
$db = DB::get();
|
|
|
|
foreach ($data as $key => $value) {
|
|
if (!is_null($value) &&
|
|
!in_array($key,$ignores) &&
|
|
property_exists($this, $key) ) {
|
|
$this->{$key} = $db->clean($value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Copy property values from a given (like) object to this object
|
|
* where the property names match */
|
|
function copydata ($Object,$prefix="") {
|
|
$db = DB::get();
|
|
|
|
$ignores = array("_datatypes","_table","_key","_lists","id","created","modified");
|
|
foreach(get_object_vars($Object) as $property => $value) {
|
|
$property = $prefix.$property;
|
|
if (property_exists($this,$property) &&
|
|
!in_array($property,$ignores))
|
|
$this->{$property} = $db->clean($value);
|
|
}
|
|
}
|
|
|
|
} // END class DatabaseObject
|
|
|
|
?>
|