Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F800200
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sat, Feb 22, 12:03 AM
Size
119 KB
Mime Type
text/x-diff
Expires
Mon, Feb 24, 12:03 AM (10 h, 34 m)
Engine
blob
Format
Raw Data
Handle
573461
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.3.x/core/kernel/db/db_load_balancer.php
===================================================================
--- branches/5.3.x/core/kernel/db/db_load_balancer.php (revision 15930)
+++ branches/5.3.x/core/kernel/db/db_load_balancer.php (revision 15931)
@@ -1,870 +1,870 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2011 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
class kDBLoadBalancer extends kBase implements IDBConnection {
/**
* Current database type
*
* @var string
* @access protected
*/
- protected $dbType = 'mysql';
+ protected $dbType = 'mysqli';
/**
* Function to handle sql errors
*
* @var string
* @access public
*/
public $errorHandler = '';
/**
* Database connection settings
*
* @var Array
* @access protected
*/
protected $servers = Array ();
/**
* Load of each slave server, given
*
* @var Array
* @access protected
*/
protected $serverLoads = Array ();
/**
* Caches replication lag of servers
*
* @var Array
* @access protected
*/
protected $serverLagTimes = Array ();
/**
* Connection references to opened connections
*
* @var Array
* @access protected
*/
protected $connections = Array ();
/**
* Index of last user slave connection
*
* @var int
* @access protected
*/
protected $slaveIndex = false;
/**
* Index of the server, that was used last
*
* @var int
* @access protected
*/
protected $lastUsedIndex = 0;
/**
* Consider slave down if it isn't responding for N milliseconds
*
* @var int
* @access protected
*/
protected $DBClusterTimeout = 10;
/**
* Scale load balancer polling time so that under overload conditions, the database server
* receives a SHOW STATUS query at an average interval of this many microseconds
*
* @var int
* @access protected
*/
protected $DBAvgStatusPoll = 2000;
/**
* Indicates, that next database query could be cached, when memory caching is enabled
*
* @var bool
* @access public
*/
public $nextQueryCachable = false;
/**
* Indicates, that next query should be sent to maser database
*
* @var bool
*/
public $nextQueryFromMaster = false;
/**
* Creates new instance of load balancer class
*
- * @param string $dbType
- * @param Array|string $errorHandler
+ * @param string $db_type
+ * @param Array|string $error_handler
*/
- function __construct($dbType, $errorHandler = '')
+ function __construct($db_type, $error_handler = '')
{
parent::__construct();
- $this->dbType = $dbType;
- $this->errorHandler = $errorHandler;
+ $this->dbType = $db_type;
+ $this->errorHandler = $error_handler;
$this->DBClusterTimeout *= 1e6; // convert to milliseconds
}
/**
* Setups load balancer according given configuration.
*
* @param Array $config
* @return void
* @access public
*/
public function setup($config)
{
$this->servers = Array ();
$this->servers[0] = Array (
'DBHost' => $config['Database']['DBHost'],
'DBUser' => $config['Database']['DBUser'],
'DBUserPassword' => $config['Database']['DBUserPassword'],
'DBName' => $config['Database']['DBName'],
'DBLoad' => 0,
);
if ( isset($config['Databases']) ) {
$this->servers = array_merge($this->servers, $config['Databases']);
}
foreach ($this->servers as $server_index => $server_setting) {
$this->serverLoads[$server_index] = $server_setting['DBLoad'];
}
}
/**
* Returns connection index to master database
*
* @return int
* @access protected
*/
protected function getMasterIndex()
{
return 0;
}
/**
* Returns connection index to slave database. This takes into account load ratios and lag times.
* Side effect: opens connections to databases
*
* @return int
* @access protected
*/
protected function getSlaveIndex()
{
if ( count($this->servers) == 1 || $this->Application->isAdmin ) {
// skip the load balancing if there's only one server OR in admin console
return 0;
}
elseif ( $this->slaveIndex !== false ) {
// shortcut if generic reader exists already
return $this->slaveIndex;
}
$total_elapsed = 0;
$non_error_loads = $this->serverLoads;
$i = $found = $lagged_slave_mode = false;
// first try quickly looking through the available servers for a server that meets our criteria
do {
$current_loads = $non_error_loads;
$overloaded_servers = $total_threads_connected = 0;
while ($current_loads) {
if ( $lagged_slave_mode ) {
// when all slave servers are too lagged, then ignore lag and pick random server
$i = $this->pickRandom($current_loads);
}
else {
$i = $this->getRandomNonLagged($current_loads);
if ( $i === false && $current_loads ) {
// all slaves lagged -> pick random lagged slave then
$lagged_slave_mode = true;
$i = $this->pickRandom( $current_loads );
}
}
if ( $i === false ) {
// all slaves are down -> use master as a slave
$this->slaveIndex = $this->getMasterIndex();
return $this->slaveIndex;
}
$conn =& $this->openConnection($i);
if ( !$conn ) {
unset($non_error_loads[$i], $current_loads[$i]);
continue;
}
// Perform post-connection backoff
$threshold = isset($this->servers[$i]['DBMaxThreads']) ? $this->servers[$i]['DBMaxThreads'] : false;
$backoff = $this->postConnectionBackoff($conn, $threshold);
if ( $backoff ) {
// post-connection overload, don't use this server for now
$total_threads_connected += $backoff;
$overloaded_servers++;
unset( $current_loads[$i] );
}
else {
// return this server
break 2;
}
}
// no server found yet
$i = false;
// if all servers were down, quit now
if ( !$non_error_loads ) {
break;
}
// back off for a while
// scale the sleep time by the number of connected threads, to produce a roughly constant global poll rate
$avg_threads = $total_threads_connected / $overloaded_servers;
usleep($this->DBAvgStatusPoll * $avg_threads);
$total_elapsed += $this->DBAvgStatusPoll * $avg_threads;
} while ( $total_elapsed < $this->DBClusterTimeout );
if ( $i !== false ) {
// slave connection successful
if ( $this->slaveIndex <= 0 && $this->serverLoads[$i] > 0 && $i !== false ) {
$this->slaveIndex = $i;
}
}
return $i;
}
/**
* Returns random non-lagged server
*
* @param Array $loads
* @return int
* @access protected
*/
protected function getRandomNonLagged($loads)
{
// unset excessively lagged servers
$lags = $this->getLagTimes();
foreach ($lags as $i => $lag) {
if ( $i != 0 && isset($this->servers[$i]['DBMaxLag']) ) {
if ( $lag === false ) {
unset( $loads[$i] ); // server is not replicating
}
elseif ( $lag > $this->servers[$i]['DBMaxLag'] ) {
unset( $loads[$i] ); // server is excessively lagged
}
}
}
// find out if all the slaves with non-zero load are lagged
if ( !$loads || array_sum($loads) == 0 ) {
return false;
}
// return a random representative of the remainder
return $this->pickRandom($loads);
}
/**
* Select an element from an array of non-normalised probabilities
*
* @param Array $weights
* @return int
* @access protected
*/
protected function pickRandom($weights)
{
if ( !is_array($weights) || !$weights ) {
return false;
}
$sum = array_sum($weights);
if ( $sum == 0 ) {
return false;
}
$max = mt_getrandmax();
$rand = mt_rand(0, $max) / $max * $sum;
$index = $sum = 0;
foreach ($weights as $index => $weight) {
$sum += $weight;
if ( $sum >= $rand ) {
break;
}
}
return $index;
}
/**
* Get lag time for each server
* Results are cached for a short time in memcached, and indefinitely in the process cache
*
* @return Array
* @access protected
*/
protected function getLagTimes()
{
if ( $this->serverLagTimes ) {
return $this->serverLagTimes;
}
$expiry = 5;
$request_rate = 10;
$cache_key = 'lag_times:' . $this->servers[0]['DBHost'];
$times = $this->Application->getCache($cache_key);
if ( $times ) {
// randomly recache with probability rising over $expiry
$elapsed = time() - $times['timestamp'];
$chance = max(0, ($expiry - $elapsed) * $request_rate);
if ( mt_rand(0, $chance) != 0 ) {
unset( $times['timestamp'] );
$this->serverLagTimes = $times;
return $times;
}
}
// cache key missing or expired
$times = Array();
foreach ($this->servers as $index => $server) {
if ($index == 0) {
$times[$index] = 0; // master
}
else {
$conn =& $this->openConnection($index);
if ($conn !== false) {
$times[$index] = $conn->getSlaveLag();
}
}
}
// add a timestamp key so we know when it was cached
$times['timestamp'] = time();
$this->Application->setCache($cache_key, $times, $expiry);
// but don't give the timestamp to the caller
unset($times['timestamp']);
$this->serverLagTimes = $times;
return $this->serverLagTimes;
}
/**
* Determines whatever server should not be used, even, when connection was made
*
* @param kDBConnection $conn
* @param int $threshold
* @return int
* @access protected
*/
protected function postConnectionBackoff(&$conn, $threshold)
{
if ( !$threshold ) {
return 0;
}
$status = $conn->getStatus('Thread%');
return $status['Threads_running'] > $threshold ? $status['Threads_connected'] : 0;
}
/**
* Open a connection to the server given by the specified index
* Index must be an actual index into the array.
* If the server is already open, returns it.
*
* On error, returns false.
*
* @param integer $i Server index
* @return kDBConnection|false
* @access protected
*/
protected function &openConnection($i)
{
if ( isset($this->connections[$i]) ) {
$conn =& $this->connections[$i];
}
else {
$server = $this->servers[$i];
$server['serverIndex'] = $i;
$conn =& $this->reallyOpenConnection($server, $i == $this->getMasterIndex());
if ( $conn->connectionOpened ) {
$this->connections[$i] =& $conn;
$this->lastUsedIndex = $i;
}
else {
$conn = false;
}
}
if ( $this->nextQueryCachable && is_object($conn) ) {
$conn->nextQueryCachable = true;
$this->nextQueryCachable = false;
}
return $conn;
}
/**
* Checks if previous query execution raised an error.
*
* @return bool
* @access public
*/
public function hasError()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->hasError();
}
/**
* Checks if connection to the server given by the specified index is opened.
*
* @param integer $i Server index
* @return bool
* @access public
*/
public function connectionOpened($i = null)
{
$conn =& $this->openConnection(isset($i) ? $i : $this->getMasterIndex());
return $conn ? $conn->connectionOpened : false;
}
/**
* Really opens a connection.
* Returns a database object whether or not the connection was successful.
*
* @param Array $server
* @param bool $is_master
* @return kDBConnection
*/
protected function &reallyOpenConnection($server, $is_master)
{
$debug_mode = $this->Application->isDebugMode();
$db_class = $debug_mode ? 'kDBConnectionDebug' : 'kDBConnection';
$db = $this->Application->makeClass($db_class, Array ($this->dbType, $this->errorHandler, $server['serverIndex']));
/* @var $db kDBConnection */
$db->debugMode = $debug_mode;
- $db->Connect($server['DBHost'], $server['DBUser'], $server['DBUserPassword'], $this->servers[0]['DBName'], true, !$is_master);
+ $db->Connect($server['DBHost'], $server['DBUser'], $server['DBUserPassword'], $this->servers[0]['DBName'], !$is_master);
return $db;
}
/**
* Returns first field of first line of recordset if query ok or false otherwise.
*
* @param string $sql
* @param int $offset
* @return string
* @access public
*/
public function GetOne($sql, $offset = 0)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetOne($sql, $offset);
}
/**
* Returns first row of recordset if query ok, false otherwise.
*
* @param string $sql
* @param int $offset
* @return Array
* @access public
*/
public function GetRow($sql, $offset = 0)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetRow($sql, $offset);
}
/**
* Returns 1st column of recordset as one-dimensional array or false otherwise.
*
* Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return Array
* @access public
*/
public function GetCol($sql, $key_field = null)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetCol($sql, $key_field);
}
/**
* Returns iterator for 1st column of a recordset or false in case of error.
*
* Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return bool|kMySQLQueryCol
*/
public function GetColIterator($sql, $key_field = null)
{
$conn =& $this->chooseConnection($sql);
return $conn->GetColIterator($sql, $key_field);
}
/**
* Queries db with $sql query supplied and returns rows selected if any, false otherwise.
*
* Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return Array
* @access public
*/
public function Query($sql, $key_field = null, $no_debug = false)
{
$conn =& $this->chooseConnection($sql);
return $conn->Query($sql, $key_field, $no_debug);
}
/**
* Returns iterator to a recordset, produced from running $sql query.
*
* Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error.
* Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @param string $iterator_class
* @return kMySQLQuery|bool
* @access public
*/
public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery')
{
$conn =& $this->chooseConnection($sql);
return $conn->GetIterator($sql, $key_field, $no_debug, $iterator_class);
}
/**
* Free memory used to hold recordset handle.
*
* @access public
*/
public function Destroy()
{
$conn =& $this->openConnection($this->lastUsedIndex);
$conn->Destroy();
}
/**
* Performs sql query, that will change database content.
*
* @param string $sql
* @return bool
* @access public
*/
public function ChangeQuery($sql)
{
$conn =& $this->chooseConnection($sql);
return $conn->ChangeQuery($sql);
}
/**
* Returns auto increment field value from insert like operation if any, zero otherwise.
*
* @return int
* @access public
*/
public function getInsertID()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getInsertID();
}
/**
* Returns row count affected by last query.
*
* @return int
* @access public
*/
public function getAffectedRows()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getAffectedRows();
}
/**
* Returns LIMIT sql clause part for specific db.
*
* @param int $offset
* @param int $rows
* @return string
* @access public
*/
public function getLimitClause($offset, $rows)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getLimitClause($offset, $rows);
}
/**
* If it's a string, adds quotes and backslashes. Otherwise returns as-is.
*
* @param mixed $string
* @return string
* @access public
*/
public function qstr($string)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->qstr($string);
}
/**
* Calls "qstr" function for each given array element.
*
* @param Array $array
* @param string $function
* @return Array
*/
public function qstrArray($array, $function = 'qstr')
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->qstrArray($array, $function);
}
/**
* Escapes string.
*
* @param mixed $string
* @return string
* @access public
*/
public function escape($string)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->escape($string);
}
/**
* Returns last error code occurred.
*
* @return int
* @access public
*/
public function getErrorCode()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getErrorCode();
}
/**
* Returns last error message.
*
* @return string
* @access public
*/
public function getErrorMsg()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getErrorMsg();
}
/**
* Performs insert of given data (useful with small number of queries)
* or stores it to perform multiple insert later (useful with large number of queries).
*
* @param Array $fields_hash
* @param string $table
* @param string $type
* @param bool $insert_now
* @return bool
* @access public
*/
public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true)
{
$conn =& $this->openConnection( $this->getMasterIndex() );
return $conn->doInsert($fields_hash, $table, $type, $insert_now);
}
/**
* Update given field values to given record using $key_clause.
*
* @param Array $fields_hash
* @param string $table
* @param string $key_clause
* @return bool
* @access public
*/
public function doUpdate($fields_hash, $table, $key_clause)
{
$conn =& $this->openConnection( $this->getMasterIndex() );
return $conn->doUpdate($fields_hash, $table, $key_clause);
}
/**
* Allows to detect table's presence in database.
*
* @param string $table_name
* @param bool $force
* @return bool
* @access public
*/
public function TableFound($table_name, $force = false)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->TableFound($table_name, $force);
}
/**
* Returns query processing statistics.
*
* @return Array
* @access public
*/
public function getQueryStatistics()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getQueryStatistics();
}
/**
* Get status information from SHOW STATUS in an associative array.
*
* @param string $which
* @return Array
* @access public
*/
public function getStatus($which = '%')
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getStatus($which);
}
/**
* When undefined method is called, then send it directly to last used slave server connection
*
* @param string $name
* @param Array $arguments
* @return mixed
* @access public
*/
public function __call($name, $arguments)
{
$conn =& $this->openConnection($this->lastUsedIndex);
return call_user_func_array( Array (&$conn, $name), $arguments );
}
/**
* Returns appropriate connection based on sql
*
* @param string $sql
* @return kDBConnection
* @access protected
*/
protected function &chooseConnection($sql)
{
if ( $this->nextQueryFromMaster ) {
$this->nextQueryFromMaster = false;
$index = $this->getMasterIndex();
}
else {
$sid = isset($this->Application->Session) ? $this->Application->GetSID() : '9999999999999999999999';
if ( preg_match('/(^[ \t\r\n]*(ALTER|CREATE|DROP|RENAME|DELETE|DO|INSERT|LOAD|REPLACE|TRUNCATE|UPDATE))|ses_' . $sid . '/', $sql) ) {
$index = $this->getMasterIndex();
}
else {
$index = $this->getSlaveIndex();
}
}
$this->lastUsedIndex = $index;
$conn =& $this->openConnection($index);
return $conn;
}
/**
* Get slave replication lag. It will only work if the DB user has the PROCESS privilege.
*
* @return int
* @access public
*/
public function getSlaveLag()
{
$conn =& $this->openConnection($this->lastUsedIndex);
return $conn->getSlaveLag();
}
}
Index: branches/5.3.x/core/kernel/db/db_connection.php
===================================================================
--- branches/5.3.x/core/kernel/db/db_connection.php (revision 15930)
+++ branches/5.3.x/core/kernel/db/db_connection.php (revision 15931)
@@ -1,1507 +1,1405 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Multi database connection class
*
*/
class kDBConnection extends kBase implements IDBConnection {
/**
- * Current database type
- *
- * @var string
- * @access protected
- */
- protected $dbType = 'mysql';
-
- /**
* Created connection handle
*
- * @var resource
+ * @var mysqli
* @access protected
*/
- protected $connectionID = null;
+ protected $connectionID;
/**
* Remembers, that database connection was opened successfully
*
* @var bool
* @access public
*/
public $connectionOpened = false;
/**
* Connection parameters, that were used
*
* @var Array
* @access protected
*/
protected $connectionParams = Array ('host' => '', 'user' => '', 'pass' => '', 'db' => '');
/**
* Index of database server
*
* @var int
* @access protected
*/
protected $serverIndex = 0;
/**
* Handle of currently processed recordset
*
- * @var resource
+ * @var mysqli_result
* @access protected
*/
protected $queryID = null;
/**
- * DB type specific function mappings
- *
- * @var Array
- * @access protected
- */
- protected $metaFunctions = Array ();
-
- /**
* Function to handle sql errors
*
* @var Array|string
* @access public
*/
public $errorHandler = '';
/**
* Error code
*
* @var int
* @access protected
*/
protected $errorCode = 0;
/**
* Error message
*
* @var string
* @access protected
*/
protected $errorMessage = '';
/**
* Defines if database connection
* operations should generate debug
* information
*
* @var bool
* @access public
*/
public $debugMode = false;
/**
* Save query execution statistics
*
* @var bool
* @access protected
*/
protected $_captureStatistics = false;
/**
* Last query to database
*
* @var string
* @access public
*/
public $lastQuery = '';
/**
* Total processed queries count
*
* @var int
* @access protected
*/
protected $_queryCount = 0;
/**
* Total time, used for serving queries
*
* @var Array
* @access protected
*/
protected $_queryTime = 0;
/**
* Indicates, that next database query could be cached, when memory caching is enabled
*
* @var bool
* @access public
*/
public $nextQueryCachable = false;
/**
* For backwards compatibility with kDBLoadBalancer class
*
* @var bool
* @access public
*/
public $nextQueryFromMaster = false;
/**
* Initializes connection class with
* db type to used in future
*
- * @param string $dbType
- * @param string $errorHandler
+ * @param string $db_type
+ * @param string $error_handler
* @param int $server_index
* @access public
*/
- public function __construct($dbType, $errorHandler = '', $server_index = 0)
+ public function __construct($db_type, $error_handler = '', $server_index = 0)
{
if ( class_exists('kApplication') ) {
// prevents "Fatal Error" on 2nd installation step (when database is empty)
parent::__construct();
}
- $this->dbType = $dbType;
$this->serverIndex = $server_index;
-// $this->initMetaFunctions();
- if (!$errorHandler) {
+ if ( !$error_handler ) {
$this->errorHandler = Array(&$this, 'handleError');
}
else {
- $this->errorHandler = $errorHandler;
+ $this->errorHandler = $error_handler;
}
$this->_captureStatistics = defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !(defined('ADMIN') && ADMIN);
}
/**
* Set's custom error
*
* @param int $code
* @param string $msg
* @access protected
*/
protected function setError($code, $msg)
{
$this->errorCode = $code;
$this->errorMessage = $msg;
}
/**
* Checks if previous query execution raised an error.
*
* @return bool
* @access public
*/
public function hasError()
{
return $this->errorCode != 0;
}
/**
- * Caches function specific to requested
- * db type
- *
- * @access protected
- */
- protected function initMetaFunctions()
- {
- $ret = Array ();
-
- switch ( $this->dbType ) {
- case 'mysql':
- $ret = Array (); // only define functions, that name differs from "dbType_<meta_name>"
-
- break;
- }
-
- $this->metaFunctions = $ret;
- }
-
- /**
- * Gets function for specific db type
- * based on it's meta name
- *
- * @param string $name
- * @return string
- * @access protected
- */
- protected function getMetaFunction($name)
- {
- /*if ( !isset($this->metaFunctions[$name]) ) {
- $this->metaFunctions[$name] = $name;
- }*/
-
- return $this->dbType . '_' . $name;
- }
-
- /**
* Try to connect to database server using specified parameters and set database to $db if connection made.
*
* @param string $host
* @param string $user
* @param string $pass
* @param string $db
- * @param bool $force_new
* @param bool $retry
*
* @return bool
* @access public
* @throws RuntimeException When connection failed.
*/
- public function Connect($host, $user, $pass, $db, $force_new = false, $retry = false)
+ public function Connect($host, $user, $pass, $db, $retry = false)
{
$this->connectionParams = Array ('host' => $host, 'user' => $user, 'pass' => $pass, 'db' => $db);
- $func = $this->getMetaFunction('connect');
- $this->connectionID = $func($host, $user, $pass, $force_new);
+ $this->setError(0, ''); // reset error
+ $this->connectionID = mysqli_connect($host, $user, $pass, $db);
+ $this->errorCode = mysqli_connect_errno();
- if ($this->connectionID) {
- if (defined('DBG_SQL_MODE')) {
- $this->Query('SET sql_mode = \''.DBG_SQL_MODE.'\'');
+ if ( is_object($this->connectionID) ) {
+ if ( defined('DBG_SQL_MODE') ) {
+ $this->Query('SET SQL_MODE = "' . DBG_SQL_MODE . '"');
}
- if (defined('SQL_COLLATION') && defined('SQL_CHARSET')) {
- $this->Query('SET NAMES \''.SQL_CHARSET.'\' COLLATE \''.SQL_COLLATION.'\'');
+ if ( defined('SQL_COLLATION') && defined('SQL_CHARSET') ) {
+ $this->Query('SET NAMES \'' . SQL_CHARSET . '\' COLLATE \'' . SQL_COLLATION . '\'');
}
- $this->setError(0, ''); // reset error
- $this->setDB($db);
- }
-
- // process error (fatal in most cases)
- $func = $this->getMetaFunction('errno');
- $this->errorCode = $this->connectionID ? $func($this->connectionID) : $func();
+ if ( !$this->hasError() ) {
+ $this->connectionOpened = true;
- if ( is_resource($this->connectionID) && !$this->hasError() ) {
- $this->connectionOpened = true;
-
- return true;
+ return true;
+ }
}
- $func = $this->getMetaFunction('error');
- $this->errorMessage = $this->connectionID ? $func($this->connectionID) : $func();
+ $this->errorMessage = mysqli_connect_error();
$error_msg = 'Database connection failed, please check your connection settings.<br/>Error (' . $this->errorCode . '): ' . $this->errorMessage;
if ( (defined('IS_INSTALL') && IS_INSTALL) || $retry ) {
trigger_error($error_msg, E_USER_WARNING);
}
else {
$this->Application->redirectToMaintenance();
throw new RuntimeException($error_msg);
}
$this->connectionOpened = false;
return false;
}
/**
* Checks if connection to database is opened.
*
* @return bool
* @access public
*/
public function connectionOpened()
{
return $this->connectionOpened;
}
/**
* Setups the connection according given configuration.
*
* @param Array $config
* @return bool
* @access public
*/
public function setup($config)
{
if ( is_object($this->Application) ) {
$this->debugMode = $this->Application->isDebugMode();
}
return $this->Connect(
$config['Database']['DBHost'],
$config['Database']['DBUser'],
$config['Database']['DBUserPassword'],
$config['Database']['DBName']
);
}
/**
* Performs 3 reconnect attempts in case if connection to a DB was lost in the middle of script run (e.g. server restart)
*
- * @param bool $force_new
* @return bool
* @access protected
*/
- protected function ReConnect($force_new = false)
+ protected function ReConnect()
{
$retry_count = 0;
$connected = false;
- $func = $this->getMetaFunction('close');
- $func($this->connectionID);
+ $this->connectionID->close();
while ( $retry_count < 3 ) {
sleep(5); // wait 5 seconds before each reconnect attempt
$connected = $this->Connect(
$this->connectionParams['host'],
$this->connectionParams['user'],
$this->connectionParams['pass'],
$this->connectionParams['db'],
- $force_new, true
+ true
);
if ( $connected ) {
break;
}
$retry_count++;
}
return $connected;
}
/**
* Shows error message from previous operation
* if it failed
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return bool
* @access protected
*/
protected function showError($sql = '', $key_field = null, $no_debug = false)
{
static $retry_count = 0;
- $func = $this->getMetaFunction('errno');
-
- if (!$this->connectionID) {
+ if ( !is_object($this->connectionID) ) {
// no connection while doing mysql_query
- $this->errorCode = $func();
+ $this->errorCode = mysqli_connect_errno();
if ( $this->hasError() ) {
- $func = $this->getMetaFunction('error');
- $this->errorMessage = $func();
+ $this->errorMessage = mysqli_connect_error();
$ret = $this->callErrorHandler($sql);
if (!$ret) {
exit;
}
}
return false;
}
// checking if there was an error during last mysql_query
- $this->errorCode = $func($this->connectionID);
+ $this->errorCode = $this->connectionID->errno;
if ( $this->hasError() ) {
- $func = $this->getMetaFunction('error');
- $this->errorMessage = $func($this->connectionID);
+ $this->errorMessage = $this->connectionID->error;
$ret = $this->callErrorHandler($sql);
if ( ($this->errorCode == 2006 || $this->errorCode == 2013) && ($retry_count < 3) ) {
// #2006 - MySQL server has gone away
// #2013 - Lost connection to MySQL server during query
$retry_count++;
if ( $this->ReConnect() ) {
return $this->Query($sql, $key_field, $no_debug);
}
}
if (!$ret) {
exit;
}
}
else {
$retry_count = 0;
}
return false;
}
/**
* Sends db error to a predefined error handler
*
* @param $sql
* @return bool
* @access protected
*/
protected function callErrorHandler($sql)
{
if (is_array($this->errorHandler)) {
$func = $this->errorHandler[1];
$ret = $this->errorHandler[0]->$func($this->errorCode, $this->errorMessage, $sql);
}
else {
$func = $this->errorHandler;
$ret = $func($this->errorCode, $this->errorMessage, $sql);
}
return $ret;
}
/**
* Default error handler for sql errors
*
* @param int $code
* @param string $msg
* @param string $sql
* @return bool
* @access public
*/
public function handleError($code, $msg, $sql)
{
echo '<strong>Processing SQL</strong>: ' . $sql . '<br/>';
echo '<strong>Error (' . $code . '):</strong> ' . $msg . '<br/>';
return false;
}
/**
- * Set's database name for connection
- * to $new_name
- *
- * @param string $new_name
- * @return bool
- * @access protected
- */
- protected function setDB($new_name)
- {
- if (!$this->connectionID) return false;
- $func = $this->getMetaFunction('select_db');
- return $func($new_name, $this->connectionID);
- }
-
- /**
* Returns first field of first line of recordset if query ok or false otherwise.
*
* @param string $sql
* @param int $offset
* @return string
* @access public
*/
public function GetOne($sql, $offset = 0)
{
$row = $this->GetRow($sql, $offset);
if ( !$row ) {
return false;
}
return array_shift($row);
}
/**
* Returns first row of recordset if query ok, false otherwise.
*
* @param string $sql
* @param int $offset
* @return Array
* @access public
*/
public function GetRow($sql, $offset = 0)
{
$sql .= ' ' . $this->getLimitClause($offset, 1);
$ret = $this->Query($sql);
if ( !$ret ) {
return false;
}
return array_shift($ret);
}
/**
* Returns 1st column of recordset as one-dimensional array or false otherwise.
*
* Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return Array
* @access public
*/
public function GetCol($sql, $key_field = null)
{
$rows = $this->Query($sql);
if ( !$rows ) {
return $rows;
}
$i = 0;
$row_count = count($rows);
$ret = Array ();
if ( isset($key_field) ) {
while ( $i < $row_count ) {
$ret[$rows[$i][$key_field]] = array_shift($rows[$i]);
$i++;
}
}
else {
while ( $i < $row_count ) {
$ret[] = array_shift($rows[$i]);
$i++;
}
}
return $ret;
}
/**
* Returns iterator for 1st column of a recordset or false in case of error.
*
* Optional parameter $key_field can be used to set field name to be used as resulting array key.
*
* @param string $sql
* @param string $key_field
* @return bool|kMySQLQueryCol
*/
public function GetColIterator($sql, $key_field = null)
{
return $this->GetIterator($sql, $key_field, false, 'kMySQLQueryCol');
}
/**
* Queries db with $sql query supplied and returns rows selected if any, false otherwise.
*
* Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return Array
* @access public
*/
public function Query($sql, $key_field = null, $no_debug = false)
{
$this->_queryCount++;
$this->lastQuery = $sql;
- $query_func = $this->getMetaFunction('query');
-
// set 1st checkpoint: begin
$start_time = $this->_captureStatistics ? microtime(true) : 0;
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
- $this->queryID = $query_func($sql, $this->connectionID);
+ $this->queryID = $this->connectionID->query($sql);
- if ( is_resource($this->queryID) ) {
+ if ( is_object($this->queryID) ) {
$ret = Array ();
- $fetch_func = $this->getMetaFunction('fetch_assoc');
if ( isset($key_field) ) {
- while (($row = $fetch_func($this->queryID))) {
+ while ( $row = $this->queryID->fetch_assoc() ) {
$ret[$row[$key_field]] = $row;
}
}
else {
- while (($row = $fetch_func($this->queryID))) {
+ while ( $row = $this->queryID->fetch_assoc() ) {
$ret[] = $row;
}
}
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$query_time = microtime(true) - $start_time;
if ( $query_time > DBG_MAX_SQL_TIME ) {
$this->Application->logSlowQuery($sql, $query_time);
}
$this->_queryTime += $query_time;
}
// set 2nd checkpoint: end
$this->Destroy();
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$this->_queryTime += microtime(true) - $start_time;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field, $no_debug);
}
/**
* Returns iterator to a recordset, produced from running $sql query.
*
* Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error.
* Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @param string $iterator_class
* @return kMySQLQuery|bool
* @access public
*/
public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery')
{
$this->_queryCount++;
$this->lastQuery = $sql;
- $query_func = $this->getMetaFunction('query');
-
// set 1st checkpoint: begin
$start_time = $this->_captureStatistics ? microtime(true) : 0;
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
- $this->queryID = $query_func($sql, $this->connectionID);
+ $this->queryID = $this->connectionID->query($sql);
- if ( is_resource($this->queryID) ) {
+ if ( is_object($this->queryID) ) {
$ret = new $iterator_class($this->queryID, $key_field);
/* @var $ret kMySQLQuery */
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$query_time = microtime(true) - $start_time;
if ( $query_time > DBG_MAX_SQL_TIME ) {
$this->Application->logSlowQuery($sql, $query_time);
}
$this->_queryTime += $query_time;
}
// set 2nd checkpoint: end
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$this->_queryTime += microtime(true) - $start_time;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field, $no_debug);
}
/**
* Free memory used to hold recordset handle.
*
* @access public
*/
public function Destroy()
{
- $free_func = $this->getMetaFunction('free_result');
- $free_func($this->queryID);
-
+ $this->queryID->free();
unset($this->queryID);
}
/**
* Performs sql query, that will change database content.
*
* @param string $sql
* @return bool
* @access public
*/
public function ChangeQuery($sql)
{
$this->Query($sql);
return !$this->hasError();
}
/**
* Returns auto increment field value from insert like operation if any, zero otherwise.
*
* @return int
* @access public
*/
public function getInsertID()
{
- $func = $this->getMetaFunction('insert_id');
- return $func($this->connectionID);
+ return $this->connectionID->insert_id;
}
/**
* Returns row count affected by last query.
*
* @return int
* @access public
*/
public function getAffectedRows()
{
- $func = $this->getMetaFunction('affected_rows');
- return $func($this->connectionID);
+ return $this->connectionID->affected_rows;
}
/**
* Returns LIMIT sql clause part for specific db.
*
* @param int $offset
* @param int $rows
* @return string
* @access public
*/
public function getLimitClause($offset, $rows)
{
if ( !($rows > 0) ) {
return '';
}
- switch ( $this->dbType ) {
- default:
- return 'LIMIT ' . $offset . ',' . $rows;
- break;
- }
+ return 'LIMIT ' . $offset . ',' . $rows;
}
/**
* If it's a string, adds quotes and backslashes. Otherwise returns as-is.
*
* @param mixed $string
* @return string
* @access public
*/
public function qstr($string)
{
if ( is_null($string) ) {
return 'NULL';
}
# This will also quote numeric values. This should be harmless,
# and protects against weird problems that occur when they really
# _are_ strings such as article titles and string->number->string
# conversion is not 1:1.
- return "'" . mysql_real_escape_string($string, $this->connectionID) . "'";
+ return "'" . $this->connectionID->real_escape_string($string) . "'";
}
/**
* Calls "qstr" function for each given array element.
*
* @param Array $array
* @param string $function
* @return Array
*/
public function qstrArray($array, $function = 'qstr')
{
return array_map(Array (&$this, $function), $array);
}
/**
* Escapes string.
*
* @param mixed $string
* @return string
* @access public
*/
public function escape($string)
{
if ( is_null($string) ) {
return 'NULL';
}
- $string = mysql_real_escape_string($string, $this->connectionID);
+ $string = $this->connectionID->real_escape_string($string);
// prevent double-escaping of MySQL wildcard symbols ("%" and "_") in case if they were already escaped
return str_replace(Array ('\\\\%', '\\\\_'), Array ('\\%', '\\_'), $string);
}
/**
* Returns last error code occurred.
*
* @return int
* @access public
*/
public function getErrorCode()
{
return $this->errorCode;
}
/**
* Returns last error message.
*
* @return string
* @access public
*/
public function getErrorMsg()
{
return $this->errorMessage;
}
/**
* Performs insert of given data (useful with small number of queries)
* or stores it to perform multiple insert later (useful with large number of queries).
*
* @param Array $fields_hash
* @param string $table
* @param string $type
* @param bool $insert_now
* @return bool
* @access public
*/
public function doInsert($fields_hash, $table, $type = 'INSERT', $insert_now = true)
{
static $value_sqls = Array ();
if ($insert_now) {
$fields_sql = '`' . implode('`,`', array_keys($fields_hash)) . '`';
}
$values_sql = '';
foreach ($fields_hash as $field_name => $field_value) {
$values_sql .= $this->qstr($field_value) . ',';
}
// don't use preg here, as it may fail when string is too long
$value_sqls[] = rtrim($values_sql, ',');
$insert_result = true;
if ($insert_now) {
$insert_count = count($value_sqls);
if (($insert_count > 1) && ($value_sqls[$insert_count - 1] == $value_sqls[$insert_count - 2])) {
// last two records are the same
array_pop($value_sqls);
}
$sql = strtoupper($type) . ' INTO `' . $table . '` (' . $fields_sql . ') VALUES (' . implode('),(', $value_sqls) . ')';
$value_sqls = Array (); // reset before query to prevent repeated call from error handler to insert 2 records instead of 1
$insert_result = $this->ChangeQuery($sql);
}
return $insert_result;
}
/**
* Update given field values to given record using $key_clause.
*
* @param Array $fields_hash
* @param string $table
* @param string $key_clause
* @return bool
* @access public
*/
public function doUpdate($fields_hash, $table, $key_clause)
{
if (!$fields_hash) return true;
$fields_sql = '';
foreach ($fields_hash as $field_name => $field_value) {
$fields_sql .= '`'.$field_name.'` = ' . $this->qstr($field_value) . ',';
}
// don't use preg here, as it may fail when string is too long
$fields_sql = rtrim($fields_sql, ',');
$sql = 'UPDATE `'.$table.'` SET '.$fields_sql.' WHERE '.$key_clause;
return $this->ChangeQuery($sql);
}
/**
* Allows to detect table's presence in database.
*
* @param string $table_name
* @param bool $force
* @return bool
* @access public
*/
public function TableFound($table_name, $force = false)
{
static $table_found = false;
if ( $table_found === false ) {
$table_found = array_flip($this->GetCol('SHOW TABLES'));
}
if ( !preg_match('/^' . preg_quote(TABLE_PREFIX, '/') . '(.*)/', $table_name) ) {
$table_name = TABLE_PREFIX . $table_name;
}
if ( $force ) {
if ( $this->Query('SHOW TABLES LIKE ' . $this->qstr($table_name)) ) {
$table_found[$table_name] = 1;
}
else {
unset($table_found[$table_name]);
}
}
return isset($table_found[$table_name]);
}
/**
* Returns query processing statistics.
*
* @return Array
* @access public
*/
public function getQueryStatistics()
{
return Array ('time' => $this->_queryTime, 'count' => $this->_queryCount);
}
/**
* Get status information from SHOW STATUS in an associative array.
*
* @param string $which
* @return Array
* @access public
*/
public function getStatus($which = '%')
{
$status = Array ();
$records = $this->Query('SHOW STATUS LIKE "' . $which . '"');
foreach ($records as $record) {
$status[ $record['Variable_name'] ] = $record['Value'];
}
return $status;
}
/**
* Get slave replication lag. It will only work if the DB user has the PROCESS privilege.
*
* @return int
* @access public
*/
public function getSlaveLag()
{
// don't use kDBConnection::Query method, since it will create an array of all server processes
$processes = $this->GetIterator('SHOW PROCESSLIST');
$skip_states = Array (
'Waiting for master to send event',
'Connecting to master',
'Queueing master event to the relay log',
'Waiting for master update',
'Requesting binlog dump',
);
// find slave SQL thread
foreach ($processes as $process) {
if ( $process['User'] == 'system user' && !in_array($process['State'], $skip_states) ) {
// this is it, return the time (except -ve)
return $process['Time'] > 0x7fffffff ? false : $process['Time'];
}
}
return false;
}
}
class kDBConnectionDebug extends kDBConnection {
protected $_profileSQLs = false;
/**
* Info about this database connection to show in debugger report
*
* @var string
* @access protected
*/
protected $serverInfoLine = '';
/**
* Initializes connection class with
* db type to used in future
*
- * @param string $dbType
- * @param string $errorHandler
+ * @param string $db_type
+ * @param string $error_handler
* @param int $server_index
* @access public
*/
- public function __construct($dbType, $errorHandler = '', $server_index = 0)
+ public function __construct($db_type, $error_handler = '', $server_index = 0)
{
- parent::__construct($dbType, $errorHandler, $server_index);
+ parent::__construct($db_type, $error_handler, $server_index);
$this->_profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE;
}
/**
* Try to connect to database server
* using specified parameters and set
* database to $db if connection made
*
* @param string $host
* @param string $user
* @param string $pass
* @param string $db
* @param bool $force_new
* @param bool $retry
* @return bool
* @access public
*/
public function Connect($host, $user, $pass, $db, $force_new = false, $retry = false)
{
if ( defined('DBG_SQL_SERVERINFO') && DBG_SQL_SERVERINFO ) {
$this->serverInfoLine = $this->serverIndex . ' (' . $host . ')';
}
return parent::Connect($host, $user, $pass, $db, $force_new, $retry);
}
/**
* Queries db with $sql query supplied and returns rows selected if any, false otherwise.
*
* Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @return Array
* @access public
*/
public function Query($sql, $key_field = null, $no_debug = false)
{
if ( $no_debug ) {
return parent::Query($sql, $key_field, $no_debug);
}
global $debugger;
$this->_queryCount++;
$this->lastQuery = $sql;
- $query_func = $this->getMetaFunction('query');
-
// set 1st checkpoint: begin
if ( $this->_profileSQLs ) {
$queryID = $debugger->generateID();
$debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql));
}
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
- $this->queryID = $query_func($sql, $this->connectionID);
+ $this->queryID = $this->connectionID->query($sql);
- if ( is_resource($this->queryID) ) {
+ if ( is_object($this->queryID) ) {
$ret = Array ();
- $fetch_func = $this->getMetaFunction('fetch_assoc');
if ( isset($key_field) ) {
- while (($row = $fetch_func($this->queryID))) {
+ while ( $row = $this->queryID->fetch_assoc() ) {
$ret[$row[$key_field]] = $row;
}
}
else {
- while (($row = $fetch_func($this->queryID))) {
+ while ( $row = $this->queryID->fetch_assoc() ) {
$ret[] = $row;
}
}
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$current_element = current($ret);
$first_cell = count($ret) == 1 && count($current_element) == 1 ? current($current_element) : null;
if ( strlen($first_cell) > 200 ) {
$first_cell = substr($first_cell, 0, 50) . ' ...';
}
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
$this->Destroy();
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field);
}
/**
* Returns iterator to a recordset, produced from running $sql query.
*
* Queries db with $sql query supplied and returns kMySQLQuery iterator or false in case of error.
* Optional parameter $key_field allows to set one of the query fields value as key in string array.
*
* @param string $sql
* @param string $key_field
* @param bool $no_debug
* @param string $iterator_class
* @return kMySQLQuery|bool
* @access public
*/
public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery')
{
if ( $no_debug ) {
return parent::Query($sql, $key_field, $no_debug, $iterator_class);
}
global $debugger;
$this->_queryCount++;
$this->lastQuery = $sql;
- $query_func = $this->getMetaFunction('query');
-
// set 1st checkpoint: begin
if ( $this->_profileSQLs ) {
$queryID = $debugger->generateID();
$debugger->profileStart('sql_' . $queryID, $debugger->formatSQL($sql));
}
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
- $this->queryID = $query_func($sql, $this->connectionID);
+ $this->queryID = $this->connectionID->query($sql);
- if ( is_resource($this->queryID) ) {
+ if ( is_object($this->queryID) ) {
$ret = new $iterator_class($this->queryID, $key_field);
/* @var $ret kMySQLQuery */
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$first_cell = count($ret) == 1 && $ret->fieldCount() == 1 ? current($ret->current()) : null;
if ( strlen($first_cell) > 200 ) {
$first_cell = substr($first_cell, 0, 50) . ' ...';
}
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), $first_cell, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
return $ret;
}
else {
// set 2nd checkpoint: begin
if ( $this->_profileSQLs ) {
$debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverInfoLine);
$debugger->profilerAddTotal('sql', 'sql_' . $queryID);
$this->nextQueryCachable = false;
}
// set 2nd checkpoint: end
}
return $this->showError($sql, $key_field);
}
}
class kMySQLQuery implements Iterator, Countable, SeekableIterator {
/**
* Current index in recordset
*
* @var int
* @access protected
*/
protected $position = -1;
/**
* Query resource
*
- * @var resource
+ * @var mysqli_result
* @access protected
*/
protected $result;
/**
* Field to act as key in a resulting array
*
* @var string
* @access protected
*/
protected $keyField = null;
/**
* Data in current row of recordset
*
* @var Array
* @access protected
*/
protected $rowData = Array ();
/**
* Row count in a result
*
* @var int
* @access protected
*/
protected $rowCount = 0;
/**
* Creates new instance of a class
*
- * @param resource $result
+ * @param mysqli_result $result
* @param null|string $key_field
*/
- public function __construct($result, $key_field = null)
+ public function __construct(mysqli_result $result, $key_field = null)
{
$this->result = $result;
$this->keyField = $key_field;
- $this->rowCount = mysql_num_rows($this->result);
+ $this->rowCount = $this->result->num_rows;
$this->rewind();
}
/**
* Moves recordset pointer to first element
*
* @return void
* @access public
* @implements Iterator::rewind
*/
public function rewind()
{
$this->seek(0);
}
/**
* Returns value at current position
*
* @return mixed
* @access public
* @implements Iterator::current
*/
function current()
{
return $this->rowData;
}
/**
* Returns key at current position
*
* @return mixed
* @access public
* @implements Iterator::key
*/
function key()
{
return $this->keyField ? $this->rowData[$this->keyField] : $this->position;
}
/**
* Moves recordset pointer to next position
*
* @return void
* @access public
* @implements Iterator::next
*/
function next()
{
$this->seek($this->position + 1);
}
/**
* Detects if current position is within recordset bounds
*
* @return bool
* @access public
* @implements Iterator::valid
*/
public function valid()
{
return $this->position < $this->rowCount;
}
/**
* Counts recordset rows
*
* @return int
* @access public
* @implements Countable::count
*/
public function count()
{
return $this->rowCount;
}
/**
* Counts fields in current row
*
* @return int
* @access public
*/
public function fieldCount()
{
return count($this->rowData);
}
/**
* Moves cursor into given position within recordset
*
* @param int $position
* @throws OutOfBoundsException
* @access public
* @implements SeekableIterator::seek
*/
public function seek($position)
{
if ( $this->position == $position ) {
return;
}
$this->position = $position;
if ( $this->valid() ) {
- mysql_data_seek($this->result, $this->position);
+ $this->result->data_seek($this->position);
- $this->rowData = mysql_fetch_assoc($this->result);
+ $this->rowData = $this->result->fetch_assoc();
}
/*if ( !$this->valid() ) {
throw new OutOfBoundsException('Invalid seek position (' . $position . ')');
}*/
}
/**
* Returns first recordset row
*
* @return Array
* @access public
*/
public function first()
{
$this->seek(0);
return $this->rowData;
}
/**
* Closes recordset and freese memory
*
* @return void
* @access public
*/
public function close()
{
- mysql_free_result($this->result);
+ $this->result->free();
unset($this->result);
}
/**
* Frees memory when object is destroyed
*
* @return void
* @access public
*/
public function __destruct()
{
$this->close();
}
/**
* Returns all keys
*
* @return Array
* @access public
*/
public function keys()
{
$ret = Array ();
foreach ($this as $key => $value) {
$ret[] = $key;
}
return $ret;
}
/**
* Returns all values
*
* @return Array
* @access public
*/
public function values()
{
$ret = Array ();
foreach ($this as $value) {
$ret[] = $value;
}
return $ret;
}
/**
* Returns whole recordset as array
*
* @return Array
* @access public
*/
public function toArray()
{
$ret = Array ();
foreach ($this as $key => $value) {
$ret[$key] = $value;
}
return $ret;
}
}
class kMySQLQueryCol extends kMySQLQuery {
/**
* Returns value at current position
*
* @return mixed
* @access public
* @implements Iterator::current
*/
function current()
{
return reset($this->rowData);
}
/**
* Returns first column of first recordset row
*
* @return string
* @access public
*/
public function first()
{
$this->seek(0);
return reset($this->rowData);
}
}
Index: branches/5.3.x/core/kernel/startup.php
===================================================================
--- branches/5.3.x/core/kernel/startup.php (revision 15930)
+++ branches/5.3.x/core/kernel/startup.php (revision 15931)
@@ -1,201 +1,201 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
define('KERNEL_PATH', FULL_PATH . '/core/kernel');
$globals_start = microtime(true);
include_once(KERNEL_PATH . '/globals.php'); // some non-OOP functions and kUtil static class used through kernel
include_once(KERNEL_PATH . '/utility/multibyte.php'); // emulating multi-byte php extension
$globals_end = microtime(true);
$vars = kUtil::getConfigVars();
$charset = isset($vars['WebsiteCharset']) ? $vars['WebsiteCharset'] : 'utf-8';
define('CHARSET', $charset);
$admin_directory = isset($vars['AdminDirectory']) ? $vars['AdminDirectory'] : '/admin';
define('ADMIN_DIRECTORY', $admin_directory);
$admin_Presets_directory = isset($vars['AdminPresetsDirectory']) ? $vars['AdminPresetsDirectory'] : ADMIN_DIRECTORY;
define('ADMIN_PRESETS_DIRECTORY', $admin_Presets_directory);
$https_mark = getArrayValue($_SERVER, 'HTTPS');
define('PROTOCOL', ($https_mark == 'on') || ($https_mark == '1') ? 'https://' : 'http://');
if ( isset($_SERVER['HTTP_HOST']) ) {
// accessed from browser
$http_host = $_SERVER['HTTP_HOST'];
}
else {
// accessed from command line
$http_host = $vars['Domain'];
$_SERVER['HTTP_HOST'] = $vars['Domain'];
}
$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : false;
if ($port) {
if ( (PROTOCOL == 'http://' && $port != '80') || (PROTOCOL == 'https://' && $port != '443') ) {
// if non-standard port is used, then define it
define('PORT', $port);
}
$http_host = preg_replace('/:' . $port . '$/', '', $http_host);
}
define('SERVER_NAME', $http_host);
if (!$vars) {
echo 'In-Portal is probably not installed, or configuration file is missing.<br/>';
echo 'Please use the installation script to fix the problem.<br/><br/>';
$base_path = rtrim(preg_replace('/'.preg_quote(rtrim($admin_directory, '/'), '/').'$/', '', str_replace('\\', '/', dirname($_SERVER['PHP_SELF']))), '/');
echo '<a href="' . PROTOCOL . SERVER_NAME . $base_path . '/core/install.php">Go to installation script</a><br><br>';
flush();
exit;
}
// variable WebsitePath is auto-detected once during installation/upgrade
define('BASE_PATH', $vars['WebsitePath']);
define('APPLICATION_CLASS', isset($vars['ApplicationClass']) ? $vars['ApplicationClass'] : 'kApplication');
define('APPLICATION_PATH', isset($vars['ApplicationPath']) ? $vars['ApplicationPath'] : '/core/kernel/application.php');
if (isset($vars['WriteablePath'])) {
define('WRITEABLE', FULL_PATH . $vars['WriteablePath']);
define('WRITEBALE_BASE', $vars['WriteablePath']);
}
if ( isset($vars['RestrictedPath']) ) {
define('RESTRICTED', FULL_PATH . $vars['RestrictedPath']);
}
define('SQL_TYPE', $vars['DBType']);
define('SQL_SERVER', $vars['DBHost']);
define('SQL_USER', $vars['DBUser']);
define('SQL_PASS', $vars['DBUserPassword']);
define('SQL_DB', $vars['DBName']);
if (isset($vars['DBCollation']) && isset($vars['DBCharset'])) {
- define('SQL_COLLATION', $vars['DBCollation']);
- define('SQL_CHARSET', $vars['DBCharset']);
+ define('SQL_COLLATION', $vars['DBCollation']); // utf8_general_ci
+ define('SQL_CHARSET', $vars['DBCharset']); // utf8
}
define('TABLE_PREFIX', $vars['TablePrefix']);
define('DOMAIN', getArrayValue($vars, 'Domain'));
ini_set('memory_limit', '50M');
define('MODULES_PATH', FULL_PATH . DIRECTORY_SEPARATOR . 'modules');
define('EXPORT_BASE_PATH', WRITEBALE_BASE . '/export');
define('EXPORT_PATH', FULL_PATH . EXPORT_BASE_PATH);
define('GW_CLASS_PATH', MODULES_PATH . '/in-commerce/units/gateways/gw_classes'); // Payment Gateway Classes Path
define('SYNC_CLASS_PATH', FULL_PATH . '/sync'); // path for 3rd party user syncronization scripts
define('ENV_VAR_NAME','env');
define('IMAGES_PATH', WRITEBALE_BASE . '/images/');
define('IMAGES_PENDING_PATH', IMAGES_PATH . 'pending/');
define('MAX_UPLOAD_SIZE', min(ini_get('upload_max_filesize'), ini_get('post_max_size'))*1024*1024);
define('EDITOR_PATH', isset($vars['EditorPath']) ? $vars['EditorPath'] : '/core/ckeditor/');
// caching types
define('CACHING_TYPE_NONE', 0);
define('CACHING_TYPE_MEMORY', 1);
define('CACHING_TYPE_TEMPORARY', 2);
class CacheSettings {
static public $unitCacheRebuildTime;
static public $structureTreeRebuildTime;
static public $cmsMenuRebuildTime;
static public $templateMappingRebuildTime;
static public $sectionsParsedRebuildTime;
static public $domainsParsedRebuildTime;
}
CacheSettings::$unitCacheRebuildTime = isset($vars['UnitCacheRebuildTime']) ? $vars['UnitCacheRebuildTime'] : 10;
CacheSettings::$structureTreeRebuildTime = isset($vars['StructureTreeRebuildTime']) ? $vars['StructureTreeRebuildTime'] : 10;
CacheSettings::$cmsMenuRebuildTime = isset($vars['CmsMenuRebuildTime']) ? $vars['CmsMenuRebuildTime'] : 10;
CacheSettings::$templateMappingRebuildTime = isset($vars['TemplateMappingRebuildTime']) ? $vars['TemplateMappingRebuildTime'] : 5;
CacheSettings::$sectionsParsedRebuildTime = isset($vars['SectionsParsedRebuildTime']) ? $vars['SectionsParsedRebuildTime'] : 10;
CacheSettings::$domainsParsedRebuildTime = isset($vars['DomainsParsedRebuildTime']) ? $vars['DomainsParsedRebuildTime'] : 2;
class MaintenanceMode {
const NONE = 0;
const SOFT = 1;
const HARD = 2;
}
unset($vars); // just in case someone will be still using it
if (ini_get('safe_mode')) {
// safe mode will be removed at all in PHP6
define('SAFE_MODE', 1);
}
if (file_exists(WRITEABLE . '/debug.php')) {
include_once(WRITEABLE . '/debug.php');
if (array_key_exists('DEBUG_MODE', $dbg_options) && $dbg_options['DEBUG_MODE']) {
$debugger_start = microtime(true);
include_once(KERNEL_PATH . '/utility/debugger.php');
$debugger_end = microtime(true);
if (isset($debugger) && kUtil::constOn('DBG_PROFILE_INCLUDES')) {
$debugger->profileStart('inc_globals', KERNEL_PATH . '/globals.php', $globals_start);
$debugger->profileFinish('inc_globals', KERNEL_PATH . '/globals.php', $globals_end);
$debugger->profilerAddTotal('includes', 'inc_globals');
$debugger->profileStart('inc_debugger', KERNEL_PATH . '/utility/debugger.php', $debugger_start);
$debugger->profileFinish('inc_debugger', KERNEL_PATH . '/utility/debugger.php', $debugger_end);
$debugger->profilerAddTotal('includes', 'inc_debugger');
}
}
}
kUtil::safeDefine('SILENT_LOG', 0); // can be set in "debug.php" too
$includes = Array(
KERNEL_PATH . "/interfaces/cacheable.php",
KERNEL_PATH . '/application.php',
FULL_PATH . APPLICATION_PATH,
KERNEL_PATH . "/kbase.php",
KERNEL_PATH . '/db/i_db_connection.php',
KERNEL_PATH . '/db/db_connection.php',
KERNEL_PATH . '/db/db_load_balancer.php',
KERNEL_PATH . '/utility/event.php',
KERNEL_PATH . '/utility/logger.php',
KERNEL_PATH . "/utility/factory.php",
KERNEL_PATH . "/languages/phrases_cache.php",
KERNEL_PATH . "/db/dblist.php",
KERNEL_PATH . "/db/dbitem.php",
KERNEL_PATH . "/event_handler.php",
KERNEL_PATH . '/db/db_event_handler.php',
);
foreach ($includes as $a_file) {
kUtil::includeOnce($a_file);
}
if (defined('DEBUG_MODE') && DEBUG_MODE && isset($debugger)) {
$debugger->AttachToApplication();
}
// system users
define('USER_ROOT', -1);
define('USER_GUEST', -2);
\ No newline at end of file
Index: branches/5.3.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.3.x/core/kernel/utility/temp_handler.php (revision 15930)
+++ branches/5.3.x/core/kernel/utility/temp_handler.php (revision 15931)
@@ -1,1589 +1,1589 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
/**
* Create a public interface to temp table structure of one unit and it's sub-items
*
* Pattern: Facade
*/
class kTempTablesHandler extends kBase {
/**
* Tables
*
* @var kTempHandlerTopTable
*/
protected $_tables;
/**
* Event, that was used to create this object
*
* @var kEvent
* @access protected
*/
protected $parentEvent = null;
/**
* Sets new parent event to the object
*
* @param kEvent $event
* @return void
* @access public
*/
public function setParentEvent($event)
{
$this->parentEvent = $event;
if ( is_object($this->_tables) ) {
$this->_tables->setParentEvent($event);
}
}
/**
* Scans table structure of given unit
*
* @param string $prefix
* @param Array $ids
* @return void
* @access public
*/
public function BuildTables($prefix, $ids)
{
$this->_tables = new kTempHandlerTopTable($prefix, $ids);
if ( is_object($this->parentEvent) ) {
$this->_tables->setParentEvent($this->parentEvent);
}
}
/**
* Create temp table for editing db record from live table. If none ids are given, then just empty tables are created.
*
* @return void
* @access public
*/
public function PrepareEdit()
{
$this->_tables->doCopyLiveToTemp();
$this->_tables->checkSimultaneousEdit();
}
/**
* Deletes temp tables without copying their data back to live tables
*
* @return void
* @access public
*/
public function CancelEdit()
{
$this->_tables->deleteAll();
}
/**
* Saves changes made in temp tables to live tables
*
* @param Array $master_ids
* @return bool
* @access public
*/
public function SaveEdit($master_ids = Array())
{
// SessionKey field is required for deleting records from expired sessions
$sleep_count = 0;
$conn = $this->_getSeparateConnection();
do {
// acquire lock
$conn->ChangeQuery('LOCK TABLES ' . TABLE_PREFIX . 'Semaphores WRITE');
$sql = 'SELECT SessionKey
FROM ' . TABLE_PREFIX . 'Semaphores
WHERE (MainPrefix = ' . $conn->qstr($this->_tables->getPrefix()) . ')';
$another_coping_active = $conn->GetOne($sql);
if ( $another_coping_active ) {
// another user is coping data from temp table to live -> release lock and try again after 1 second
$conn->ChangeQuery('UNLOCK TABLES');
$sleep_count++;
sleep(1);
}
} while ($another_coping_active && ($sleep_count <= 30));
if ( $sleep_count > 30 ) {
// another coping process failed to finished in 30 seconds
$error_message = $this->Application->Phrase('la_error_TemporaryTableCopyingFailed');
$this->Application->SetVar('_temp_table_message', $error_message);
return false;
}
// mark, that we are coping from temp to live right now, so other similar attempt (from another script) will fail
$fields_hash = Array (
'SessionKey' => $this->Application->GetSID(),
'Timestamp' => time(),
'MainPrefix' => $this->_tables->getPrefix(),
);
$conn->doInsert($fields_hash, TABLE_PREFIX . 'Semaphores');
$semaphore_id = $conn->getInsertID();
// unlock table now to prevent permanent lock in case, when coping will end with SQL error in the middle
$conn->ChangeQuery('UNLOCK TABLES');
$ids = $this->_tables->doCopyTempToLive($master_ids);
// remove mark, that we are coping from temp to live
$conn->Query('LOCK TABLES ' . TABLE_PREFIX . 'Semaphores WRITE');
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Semaphores
WHERE SemaphoreId = ' . $semaphore_id;
$conn->ChangeQuery($sql);
$conn->ChangeQuery('UNLOCK TABLES');
return $ids;
}
/**
* Deletes unit data for given items along with related sub-items
*
* @param string $prefix
* @param string $special
* @param Array $ids
* @throws InvalidArgumentException
*/
function DeleteItems($prefix, $special, $ids)
{
if ( strpos($prefix, '.') !== false ) {
throw new InvalidArgumentException("Pass prefix and special as separate arguments");
}
if ( !is_array($ids) ) {
throw new InvalidArgumentException('Incorrect ids format');
}
$this->_tables->doDeleteItems(rtrim($prefix . '.' . $special, '.'), $ids);
}
/**
* Clones given ids
*
* @param string $prefix
* @param string $special
* @param Array $ids
* @param Array $master
* @param int $foreign_key
* @param string $parent_prefix
* @param bool $skip_filenames
* @return Array
*/
function CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false)
{
return $this->_tables->doCloneItems($prefix . '.' . $special, $ids, $foreign_key, $skip_filenames);
}
/**
* Create separate connection for locking purposes
*
* @return kDBConnection
* @access protected
*/
protected function _getSeparateConnection()
{
static $connection = null;
if (!isset($connection)) {
$connection = $this->Application->makeClass( 'kDBConnection', Array (SQL_TYPE, Array ($this->Application, 'handleSQLError')) );
/* @var $connection kDBConnection */
$connection->debugMode = $this->Application->isDebugMode();
- $connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB, true);
+ $connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB);
}
return $connection;
}
}
/**
* Base class, that represents one table
*
* Pattern: Composite
*/
abstract class kTempHandlerTable extends kBase {
/**
* Temp table was created from live table OR it was copied back to live table
*/
const STATE_COPIED = 1;
/**
* Temp table was deleted
*/
const STATE_DELETED = 2;
/**
* Reference to parent table
*
* @var kTempHandlerTable
* @access protected
*/
protected $_parent;
/**
* Field in this db table, that holds ID from it's parent table
*
* @var string
* @access protected
*/
protected $_foreignKey = '';
/**
* This table is connected to multiple parent tables
*
* @var bool
* @access protected
*/
protected $_multipleParents = false;
/**
* Foreign key cache
*
* @var Array
* @access protected
*/
protected $_foreignKeyCache = Array ();
/**
* Field in parent db table from where foreign key field value originates
*
* @var string
* @access protected
*/
protected $_parentTableKey = '';
/**
* Additional WHERE filter, that determines what records needs to be processed
*
* @var string
* @access protected
*/
protected $_constrain = '';
/**
* Automatically clone records from this table when parent table record is cloned
*
* @var bool
* @access protected
*/
protected $_autoClone = true;
/**
* Automatically delete records from this table when parent table record is deleted
*
* @var bool
* @access protected
*/
protected $_autoDelete = true;
/**
* List of sub-tables
*
* @var Array
* @access protected
*/
protected $_subTables = Array ();
/**
* Window ID of current window
*
* @var int
* @access protected
*/
protected $_windowID = '';
/**
* Unit prefix
*
* @var string
* @access protected
*/
protected $_prefix = '';
/**
* IDs, that needs to be processed
*
* @var Array
* @access protected
*/
protected $_ids = Array ();
/**
* Table name-based 2-level array of cloned ids
*
* @static
* @var array
* @access protected
*/
static protected $_clonedIds = Array ();
/**
* IDs of newly cloned items (key - special, value - array of ids)
*
* @var Array
* @access protected
*/
protected $_savedIds = Array ();
/**
* ID field of associated db table
*
* @var string
* @access protected
*/
protected $_idField = '';
/**
* Name of associated db table
*
* @var string
* @access protected
*/
protected $_tableName = '';
/**
* State of the table
*
* @var int
* @access protected
*/
protected $_state = 0;
/**
* Tells that this is last usage of this table
*
* @var bool
* @access protected
*/
protected $_lastUsage = false;
/**
* Event, that was used to create this object
*
* @var kEvent
* @access protected
*/
protected $_parentEvent = null;
/**
* Creates table object
*
* @param string $prefix
* @param Array $ids
*/
public function __construct($prefix, $ids = Array ())
{
parent::__construct();
$this->_windowID = $this->Application->GetVar('m_wid');
$this->_prefix = $prefix;
$this->_ids = $ids;
if ( !$this->unitRegistered() ) {
return;
}
$this->_collectTableInfo();
}
/**
* Creates temp tables (recursively) and optionally fills them with data from live table
*
* @param Array $foreign_keys
* @return void
* @access public
*/
public function doCopyLiveToTemp($foreign_keys = Array ())
{
$parsed_prefix = $this->Application->processPrefix($this->_prefix);
$foreign_key_field = $this->_foreignKey ? $this->_foreignKey : $this->_idField;
if ( !is_numeric($parsed_prefix['special']) ) {
// TODO: find out what numeric specials are used for
if ( $this->_delete() ) {
$this->_create();
}
}
$foreign_keys = $this->_parseLiveIds($foreign_keys);
if ( $foreign_keys != '' && !$this->_inState(self::STATE_COPIED) ) {
// 1. copy data from live table into temp table
$sql = 'INSERT INTO ' . $this->_getTempTableName() . '
SELECT *
FROM ' . $this->_tableName . '
WHERE ' . $foreign_key_field . ' IN (' . $foreign_keys . ')';
$this->Conn->Query($this->_addConstrain($sql));
$this->_setAsCopied();
// 2. get ids, that were actually copied into temp table
$sql = 'SELECT ' . $this->_idField . '
FROM ' . $this->_tableName . '
WHERE ' . $foreign_key_field . ' IN (' . $foreign_keys . ')';
$copied_ids = $this->Conn->GetCol($this->_addConstrain($sql));
$this->_raiseEvent('OnAfterCopyToTemp', '', $copied_ids);
}
/* @var $sub_table kTempHandlerSubTable */
foreach ($this->_subTables as $sub_table) {
if ( !$sub_table->_parentTableKey ) {
continue;
}
if ( $foreign_keys != '' && $sub_table->_parentTableKey != $foreign_key_field ) {
// if sub-table isn't connected this this table by id field, then get foreign keys
$sql = 'SELECT ' . $sub_table->_parentTableKey . '
FROM ' . $this->_tableName . '
WHERE ' . $foreign_key_field . ' IN (' . $foreign_keys . ')';
$sub_foreign_keys = implode(',', $this->Conn->GetCol($sql));
}
else {
$sub_foreign_keys = $foreign_keys;
}
$sub_table->doCopyLiveToTemp($sub_foreign_keys);
}
}
/**
* Ensures, that ids are always a comma-separated string, that is ready to be used in SQLs
*
* @param Array|string $ids
* @return string
* @access protected
*/
protected function _parseLiveIds($ids)
{
if ( !$ids ) {
$ids = $this->_ids;
}
if ( is_array($ids) ) {
$ids = implode(',', $ids);
}
return $ids;
}
/**
* Copies data from temp to live table and returns IDs of copied records
*
* @param Array $current_ids
* @return Array
* @access public
*/
public function doCopyTempToLive($current_ids = Array())
{
$current_ids = $this->_parseTempIds($current_ids);
if ( $current_ids ) {
$this->_deleteFromLive($current_ids);
if ( $this->_subTables ) {
if ( $this->_inState(self::STATE_COPIED) || !$this->_lastUsage ) {
return Array ();
}
$this->_copyTempToLiveWithSubTables($current_ids);
}
elseif ( !$this->_inState(self::STATE_COPIED) && $this->_lastUsage ) {
// If current master doesn't have sub-tables - we could use mass operations
// We don't need to delete items from live here, as it get deleted in the beginning of the
// method for MasterTable or in parent table processing for sub-tables
$this->_copyTempToLiveWithoutSubTables($current_ids);
// no need to clear temp table - it will be dropped by next statement
}
}
if ( !$this->_lastUsage ) {
return Array ();
}
/*if ( is_array(getArrayValue($master, 'ForeignKey')) ) { //if multiple ForeignKeys
if ( $master['ForeignKey'][$parent_prefix] != end($master['ForeignKey']) ) {
return; // Do not delete temp table if not all ForeignKeys have been processed (current is not the last)
}
}*/
$this->_delete();
$this->Application->resetCounters($this->_tableName);
return isset($this->_savedIds['']) ? $this->_savedIds[''] : Array ();
}
/**
* Deletes unit db records along with related sub-items by id field
*
* @param string $prefix_special
* @param Array $ids
* @return void
* @access public
*/
public function doDeleteItems($prefix_special, $ids)
{
if ( !$ids ) {
return;
}
$object = $this->_getItem($prefix_special);
$parsed_prefix = $this->Application->processPrefix($prefix_special);
foreach ($ids as $id) {
$object->Load($id);
$original_values = $object->GetFieldValues();
if ( !$object->Delete($id) ) {
continue;
}
/* @var $sub_table kTempHandlerSubTable */
foreach ($this->_subTables as $sub_table) {
$sub_table->subDeleteItems($object, $parsed_prefix['special'], $original_values);
}
}
}
/**
* Clones item by id and it's sub-items by foreign key
*
* @param string $prefix_special
* @param Array $ids
* @param string $foreign_key
* @param bool $skip_filenames
* @return Array
* @access public
*/
public function doCloneItems($prefix_special, $ids, $foreign_key = null, $skip_filenames = false)
{
$object = $this->_getItem($prefix_special);
$object->PopulateMultiLangFields();
foreach ($ids as $id) {
$mode = 'create';
$cloned_ids = getArrayValue(self::$_clonedIds, $this->_tableName);
if ( $cloned_ids ) {
// if we have already cloned the id, replace it with cloned id and set mode to update
// update mode is needed to update second ForeignKey for items cloned by first ForeignKey
if ( getArrayValue($cloned_ids, $id) ) {
$id = $cloned_ids[$id];
$mode = 'update';
}
}
$object->Load($id);
$original_values = $object->GetFieldValues();
if ( !$skip_filenames ) {
$master = Array ('ForeignKey' => $this->_foreignKey, 'TableName' => $this->_tableName);
$object->NameCopy($master, $foreign_key);
}
elseif ( $object instanceof kCatDBItem ) {
$object->useFilenames = false;
}
if ( isset($foreign_key) ) {
$object->SetDBField($this->_foreignKey, $foreign_key);
}
if ( $mode == 'create' ) {
$this->_raiseEvent('OnBeforeClone', $object->Special, Array ($object->GetID()), $foreign_key);
}
$object->inCloning = true;
$res = $mode == 'update' ? $object->Update() : $object->Create();
$object->inCloning = false;
if ( $res ) {
if ( $mode == 'create' && $this->_multipleParents ) {
// remember original => clone mapping for dual ForeignKey updating
self::$_clonedIds[$this->_tableName][$id] = $object->GetID();
}
if ( $mode == 'create' ) {
$this->_raiseEvent('OnAfterClone', $object->Special, Array ($object->GetID()), $foreign_key, Array ('original_id' => $id));
$this->_saveId($object->Special, $object->GetID());
}
/* @var $sub_table kTempHandlerSubTable */
foreach ($this->_subTables as $sub_table) {
$sub_table->subCloneItems($object, $original_values);
}
}
}
return isset($this->_savedIds[$object->Special]) ? $this->_savedIds[$object->Special] : Array ();
}
/**
* Returns item, associated with this table
*
* @param string $prefix_special
* @return kDBItem
* @access protected
*/
protected function _getItem($prefix_special)
{
// recalling by different name, because we may get kDBList, if we recall just by prefix
$parsed_prefix = $this->Application->processPrefix($prefix_special);
$recall_prefix = $parsed_prefix['prefix'] . '.' . preg_replace('/-item$/', '', $parsed_prefix['special']) . '-item';
$object = $this->Application->recallObject($recall_prefix, null, Array ('skip_autoload' => true, 'parent_event' => $this->_parentEvent));
/* @var $object kDBItem */
return $object;
}
/**
* Copies data from temp table that has sub-tables one-by-one record
*
* @param $temp_ids
* @return void
* @access protected
*/
protected function _copyTempToLiveWithSubTables($temp_ids)
{
/* @var $sub_table kTempHandlerSubTable */
$live_ids = Array ();
foreach ($temp_ids as $index => $temp_id) {
$this->_raiseEvent('OnBeforeCopyToLive', '', Array ($temp_id));
list ($new_temp_id, $live_id) = $this->_copyOneTempID($temp_id);
$live_ids[$index] = $live_id;
$this->_saveId('', Array ($temp_id => $live_id));
$this->_raiseEvent('OnAfterCopyToLive', '', Array ($temp_id => $live_id));
$this->_updateChangeLogForeignKeys($live_id, $temp_id);
foreach ($this->_subTables as $sub_table) {
$sub_table->subUpdateForeignKeys($live_id, $temp_id);
}
// delete only after sub-table foreign key update !
$this->_deleteOneTempID($new_temp_id);
}
$this->_setAsCopied();
// when all of ids in current master has been processed, copy all sub-tables data
foreach ($this->_subTables as $sub_table) {
$sub_table->subCopyToLive($live_ids, $temp_ids);
}
}
/**
* Copies data from temp table that has no sub-tables all records together
*
* @param $temp_ids
* @return void
* @access protected
*/
protected function _copyTempToLiveWithoutSubTables($temp_ids)
{
$live_ids = Array ();
$this->_raiseEvent('OnBeforeCopyToLive', '', $temp_ids);
foreach ($temp_ids as $temp_id) {
if ( $temp_id > 0 ) {
$live_ids[$temp_id] = $temp_id;
// positive ids (already live) will be copied together below
continue;
}
// copy negative IDs (exists only in temp) one-by-one
list ($new_temp_id, $live_id) = $this->_copyOneTempID($temp_id);
$live_ids[$temp_id] = $live_id;
$this->_updateChangeLogForeignKeys($live_ids[$temp_id], $temp_id);
$this->_deleteOneTempID($new_temp_id);
}
// copy ALL records with positive ids (since negative ids were processed above) to live table
$sql = 'INSERT INTO ' . $this->_tableName . '
SELECT *
FROM ' . $this->_getTempTableName();
$this->Conn->Query($this->_addConstrain($sql));
$this->_saveId('', $live_ids);
$this->_raiseEvent('OnAfterCopyToLive', '', $live_ids);
$this->_setAsCopied();
}
/**
* Copies one record with 0/negative ID from temp to live table to obtain it's live auto-increment id
*
* @param int $temp_id
* @return Array Pair of temp id and live id
* @access protected
*/
protected function _copyOneTempID($temp_id)
{
$copy_id = $temp_id;
if ( $temp_id < 0 ) {
$sql = 'UPDATE ' . $this->_getTempTableName() . '
SET ' . $this->_idField . ' = 0
WHERE ' . $this->_idField . ' = ' . $temp_id;
$this->Conn->Query($this->_addConstrain($sql));
$copy_id = 0;
}
$sql = 'INSERT INTO ' . $this->_tableName . '
SELECT *
FROM ' . $this->_getTempTableName() . '
WHERE ' . $this->_idField . ' = ' . $copy_id;
$this->Conn->Query($sql);
return Array ($copy_id, $copy_id == 0 ? $this->Conn->getInsertID() : $copy_id);
}
/**
* Delete already copied record from master temp table
*
* @param int $temp_id
* @return void
* @access protected
*/
protected function _deleteOneTempID($temp_id)
{
$sql = 'DELETE FROM ' . $this->_getTempTableName() . '
WHERE ' . $this->_idField . ' = ' . $temp_id;
$this->Conn->Query($this->_addConstrain($sql));
}
/**
* Deletes records from live table
*
* @param $ids
* @return void
* @access protected
*/
abstract protected function _deleteFromLive($ids);
/**
* Ensures, that ids are always an array
*
* @param Array $ids
* @return Array
* @access protected
*/
protected function _parseTempIds($ids)
{
if ( !$ids ) {
$sql = 'SELECT ' . $this->_idField . '
FROM ' . $this->_getTempTableName();
$ids = $this->Conn->GetCol($this->_addConstrain($sql));
}
return $ids;
}
/**
* Sets new parent event to the object
*
* @param kEvent $event
* @return void
* @access public
*/
public function setParentEvent(kEvent $event)
{
$this->_parentEvent = $event;
$this->_top()->_drillDown($this, 'setParentEvent');
}
/**
* Collects information about table
*
* @return void
* @access protected
*/
protected function _collectTableInfo()
{
$config = $this->Application->getUnitConfig($this->_prefix);
$this->_idField = $config->getIDField();
$this->_tableName = $config->getTableName();
$this->_foreignKey = $config->getForeignKey();
$this->_parentTableKey = $config->getParentTableKey();
$this->_constrain = $config->getConstrain('');
$this->_autoClone = $config->getAutoClone();
$this->_autoDelete = $config->getAutoDelete();
}
/**
* Discovers and adds sub-tables to this table
*
* @return void
* @access protected
* @throws InvalidArgumentException
*/
protected function _addSubTables()
{
$sub_items = $this->Application->getUnitConfig($this->_prefix)->getSubItems(Array ());
if ( !is_array($sub_items) ) {
throw new InvalidArgumentException('TempHandler: SubItems property in unit config must be an array');
}
foreach ($sub_items as $sub_item_prefix) {
$this->add(new kTempHandlerSubTable($sub_item_prefix));
}
}
/**
* Adds new sub-table
*
* @param kTempHandlerSubTable $table
* @return void
* @access public
*/
public function add(kTempHandlerSubTable $table)
{
if ( !$table->unitRegistered() ) {
trigger_error('TempHandler: unit "' . $table->_prefix . '" not registered', E_USER_WARNING);
return ;
}
$this->_subTables[] = $table;
$table->setParent($this);
}
/**
* Sets parent table
*
* @param kTempHandlerTable $parent
* @return void
* @access public
*/
public function setParent(kTempHandlerTable $parent)
{
$this->_parent = $parent;
if ( is_array($this->_foreignKey) ) {
$this->_multipleParents = true;
$this->_foreignKey = $this->_foreignKey[$parent->_prefix];
}
if ( is_array($this->_parentTableKey) ) {
$this->_parentTableKey = $this->_parentTableKey[$parent->_prefix];
}
$this->_setAsLastUsed();
$this->_addSubTables();
}
/**
* Returns unit prefix
*
* @return string
* @access public
*/
public function getPrefix()
{
return $this->_prefix;
}
/**
* Determines if unit used to create table exists
*
* @return bool
* @access public
*/
public function unitRegistered()
{
return $this->Application->prefixRegistred($this->_prefix);
}
/**
* Returns topmost table
*
* @return kTempHandlerTopTable
* @access protected
*/
protected function _top()
{
$top = $this;
while ( is_object($top->_parent) ) {
$top = $top->_parent;
}
return $top;
}
/**
* Performs given operation on current table and all it's sub-tables
*
* @param kTempHandlerTable $table
* @param string $operation
* @param bool $same_table
* @param bool $same_constrain
* @return void
* @access protected
*/
protected function _drillDown(kTempHandlerTable $table, $operation, $same_table = false, $same_constrain = false)
{
$table_match = $same_table ? $this->_tableName == $table->_tableName : true;
$constrain_match = $same_constrain ? $this->_constrain == $table->_constrain : true;
if ( $table_match && $constrain_match ) {
switch ( $operation ) {
case 'state:copied':
$this->_addState(self::STATE_COPIED);
break;
case 'state:deleted':
$this->_addState(self::STATE_DELETED);
break;
case 'setParentEvent':
$this->_parentEvent = $table->_parentEvent;
break;
case 'resetLastUsed':
$this->_lastUsage = false;
break;
}
}
/* @var $sub_table kTempHandlerSubTable */
foreach ($this->_subTables as $sub_table) {
$sub_table->_drillDown($table, $operation, $same_table, $same_constrain);
}
}
/**
* Marks this instance of a table as it's last usage
*
* @return void
* @access protected
*/
protected function _setAsLastUsed()
{
$this->_top()->_drillDown($this, 'resetLastUsed', true, true);
$this->_lastUsage = true;
}
/**
* Marks table and all it's clones as copied
*
* @return void
* @access protected
*/
protected function _setAsCopied()
{
$this->_top()->_drillDown($this, 'state:copied', true, true);
}
/**
* Update foreign key columns after new ids were assigned instead of temporary ids in change log
*
* @param int $live_id
* @param int $temp_id
*/
function _updateChangeLogForeignKeys($live_id, $temp_id)
{
if ( $live_id == $temp_id ) {
return;
}
$main_prefix = $this->Application->GetTopmostPrefix($this->_prefix);
$ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->_prefix);
$changes = $this->Application->RecallVar($ses_var_name);
$changes = $changes ? unserialize($changes) : Array ();
foreach ($changes as $key => $rec) {
if ( $rec['Prefix'] == $this->_prefix && $rec['ItemId'] == $temp_id ) {
// main item change log record
$changes[$key]['ItemId'] = $live_id;
}
if ( $rec['MasterPrefix'] == $this->_prefix && $rec['MasterId'] == $temp_id ) {
// sub item change log record
$changes[$key]['MasterId'] = $live_id;
}
if ( in_array($this->_prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->_prefix] == $temp_id ) {
// parent item change log record
$changes[$key]['ParentId'][$this->_prefix] = $live_id;
if ( array_key_exists('DependentFields', $rec) ) {
// these are fields from table of $rec['Prefix'] table!
// when one of dependent fields goes into idfield of it's parent item, that was changed
$config = $this->Application->getUnitConfig($rec['Prefix']);
$parent_table_key = $config->getParentTableKey($this->_prefix);
if ( $parent_table_key == $this->_idField ) {
$foreign_key = $config->getForeignKey($this->_prefix);
$changes[$key]['DependentFields'][$foreign_key] = $live_id;
}
}
}
}
$this->Application->StoreVar($ses_var_name, serialize($changes));
}
/**
* Returns foreign key pairs for given ids and $sub_table
*
* USE: MainTable
*
* @param kTempHandlerSubTable $sub_table
* @param int|Array $live_id
* @param int|Array $temp_id
* @return Array
* @access protected
*/
protected function _getForeignKeys(kTempHandlerSubTable $sub_table, $live_id, $temp_id = null)
{
$single_mode = false;
if ( !is_array($live_id) ) {
$single_mode = true;
$live_id = Array ($live_id);
}
if ( isset($temp_id) && !is_array($temp_id) ) {
$temp_id = Array ($temp_id);
}
$cache_key = serialize($live_id);
$parent_key_field = $sub_table->_parentTableKey ? $sub_table->_parentTableKey : $this->_idField;
$cached = getArrayValue($this->_foreignKeyCache, $parent_key_field);
if ( $cached ) {
if ( array_key_exists($cache_key, $cached) ) {
list($live_foreign_key, $temp_foreign_key) = $cached[$cache_key];
return $single_mode ? Array ($live_foreign_key[0], $temp_foreign_key[0]) : $live_foreign_key;
}
}
if ( $parent_key_field != $this->_idField ) {
$sql = 'SELECT ' . $parent_key_field . '
FROM ' . $this->_tableName . '
WHERE ' . $this->_idField . ' IN (' . implode(',', $live_id) . ')';
$live_foreign_key = $this->Conn->GetCol($sql);
if ( isset($temp_id) ) {
// because doCopyTempToLive resets negative IDs to 0 in temp table (one by one) before copying to live
$temp_key = $temp_id < 0 ? 0 : $temp_id;
$sql = 'SELECT ' . $parent_key_field . '
FROM ' . $this->_getTempTableName() . '
WHERE ' . $this->_idField . ' IN (' . implode(',', $temp_key) . ')';
$temp_foreign_key = $this->Conn->GetCol($sql);
}
else {
$temp_foreign_key = Array ();
}
}
else {
$live_foreign_key = $live_id;
$temp_foreign_key = $temp_id;
}
$this->_foreignKeyCache[$parent_key_field][$cache_key] = Array ($live_foreign_key, $temp_foreign_key);
if ( $single_mode ) {
return Array ($live_foreign_key[0], $temp_foreign_key[0]);
}
return $live_foreign_key;
}
/**
* Adds constrain to given sql
*
* @param $sql
* @return string
* @access protected
*/
protected function _addConstrain($sql)
{
if ( $this->_constrain ) {
$sql .= ' AND ' . $this->_constrain;
}
return $sql;
}
/**
* Creates temp table
* Don't use CREATE TABLE ... LIKE because it also copies indexes
*
* @return void
* @access protected
*/
protected function _create()
{
$sql = 'CREATE TABLE ' . $this->_getTempTableName() . '
SELECT *
FROM ' . $this->_tableName . '
WHERE 0';
$this->Conn->Query($sql);
}
/**
* Deletes temp table
*
* @return bool
* @access protected
*/
protected function _delete()
{
if ( $this->_inState(self::STATE_DELETED) ) {
return false;
}
$sql = 'DROP TABLE IF EXISTS ' . $this->_getTempTableName();
$this->Conn->Query($sql);
$this->_top()->_drillDown($this, 'state:deleted', true);
return true;
}
/**
* Deletes table and all it's sub-tables
*
* @return void
* @access public
*/
public function deleteAll()
{
$this->_delete();
/* @var $sub_table kTempHandlerSubTable */
foreach ($this->_subTables as $sub_table) {
$sub_table->deleteAll();
}
}
/**
* Returns temp table name for current table
*
* @return string
* @access protected
*/
protected function _getTempTableName()
{
return $this->Application->GetTempName($this->_tableName, $this->_windowID);
}
/**
* Adds table state
*
* @param int $state
* @return kTempHandlerTable
* @access protected
*/
protected function _addState($state)
{
$this->_state |= $state;
return $this;
}
/**
* Removes table state
*
* @param int $state
* @return kTempHandlerTable
* @access protected
*/
protected function _removeState($state)
{
$this->_state = $this->_state &~ $state;
return $this;
}
/**
* Checks that table has given state
*
* @param int $state
* @return bool
* @access protected
*/
protected function _inState($state)
{
return ($this->_state & $state) == $state;
}
/**
* Saves id for later usage
*
* @param string $special
* @param int|Array $id
* @return void
* @access protected
*/
protected function _saveId($special = '', $id = null)
{
if ( !isset($this->_savedIds[$special]) ) {
$this->_savedIds[$special] = Array ();
}
if ( is_array($id) ) {
foreach ($id as $tmp_id => $live_id) {
$this->_savedIds[$special][$tmp_id] = $live_id;
}
}
else {
$this->_savedIds[$special][] = $id;
}
}
/**
* Raises event using IDs, that are currently being processed in temp handler
*
* @param string $name
* @param string $special
* @param Array $ids
* @param string $foreign_key
* @param Array $add_params
* @return bool
* @access protected
*/
protected function _raiseEvent($name, $special, $ids, $foreign_key = null, $add_params = null)
{
if ( !is_array($ids) ) {
return true;
}
$event = new kEvent($this->_prefix . ($special ? '.' : '') . $special . ':' . $name);
$event->MasterEvent = $this->_parentEvent;
if ( isset($foreign_key) ) {
$event->setEventParam('foreign_key', $foreign_key);
}
$set_temp_id = ($name == 'OnAfterCopyToLive') && (!is_array($add_params) || !array_key_exists('temp_id', $add_params));
foreach ($ids as $index => $id) {
$event->setEventParam('id', $id);
if ( $set_temp_id ) {
$event->setEventParam('temp_id', $index);
}
if ( is_array($add_params) ) {
foreach ($add_params as $name => $val) {
$event->setEventParam($name, $val);
}
}
$this->Application->HandleEvent($event);
}
return $event->status == kEvent::erSUCCESS;
}
}
/**
* Represents topmost table, that has related tables inside it
*
* Pattern: Composite
*/
class kTempHandlerTopTable extends kTempHandlerTable {
/**
* Creates table object
*
* @param string $prefix
* @param Array $ids
*/
public function __construct($prefix, $ids = Array ())
{
parent::__construct($prefix, $ids);
$this->_setAsLastUsed();
$this->_addSubTables();
}
/**
* Checks, that someone is editing selected records and returns true, when no one.
*
* @param Array $ids
* @return bool
* @access public
*/
public function checkSimultaneousEdit($ids = null)
{
if ( !$this->Application->getUnitConfig($this->_prefix)->getCheckSimulatniousEdit() ) {
return true;
}
$tables = $this->Conn->GetCol('SHOW TABLES');
$mask_edit_table = '/' . TABLE_PREFIX . 'ses_(.*)_edit_' . $this->_tableName . '$/';
$my_sid = $this->Application->GetSID();
$my_wid = $this->Application->GetVar('m_wid');
$ids = implode(',', isset($ids) ? $ids : $this->_ids);
$sids = Array ();
if ( !$ids ) {
return true;
}
foreach ($tables as $table) {
if ( !preg_match($mask_edit_table, $table, $regs) ) {
continue;
}
// remove popup's wid from sid
$sid = preg_replace('/(.*)_(.*)/', '\\1', $regs[1]);
if ( $sid == $my_sid ) {
if ( $my_wid ) {
// using popups for editing
if ( preg_replace('/(.*)_(.*)/', '\\2', $regs[1]) == $my_wid ) {
// don't count window, that is being opened right now
continue;
}
}
else {
// not using popups for editing -> don't count my session tables
continue;
}
}
$sql = 'SELECT COUNT(' . $this->_idField . ')
FROM ' . $table . '
WHERE ' . $this->_idField . ' IN (' . $ids . ')';
$found = $this->Conn->GetOne($sql);
if ( !$found || in_array($sid, $sids) ) {
continue;
}
$sids[] = $sid;
}
if ( !$sids ) {
return true;
}
// detect who is it
$sql = 'SELECT CONCAT(
(CASE s.PortalUserId WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE CONCAT(u.FirstName, " ", u.LastName, " (", u.Username, ")") END),
" IP: ", s.IpAddress
)
FROM ' . TABLE_PREFIX . 'UserSessions AS s
LEFT JOIN ' . TABLE_PREFIX . 'Users AS u ON u.PortalUserId = s.PortalUserId
WHERE s.SessionKey IN (' . implode(',', $sids) . ')';
$users = $this->Conn->GetCol($sql);
if ( $users ) {
$this->Application->SetVar('_simultaneous_edit_message', sprintf($this->Application->Phrase('la_record_being_edited_by'), implode(",\n", $users)));
return false;
}
return true;
}
/**
* Deletes records from live table
*
* @param $ids
* @return void
* @access protected
*/
protected function _deleteFromLive($ids)
{
if ( !$this->_raiseEvent('OnBeforeDeleteFromLive', '', $ids) ) {
return;
}
$sql = 'DELETE FROM ' . $this->_tableName . '
WHERE ' . $this->_idField . ' IN (' . implode(',', $ids) . ')';
$this->Conn->Query($sql);
}
}
/**
* Represents sub table, that has related tables inside it
*
* Pattern: Composite
*/
class kTempHandlerSubTable extends kTempHandlerTable {
/**
* Deletes records from live table
*
* @param $ids
* @return void
* @access protected
*/
protected function _deleteFromLive($ids)
{
// for sub-tables records get deleted in "subCopyToLive" method !BY Foreign Key!
}
/**
* Copies sub-table contents to live
*
* @param Array $live_ids
* @param Array $temp_ids
* @return void
* @access public
*/
public function subCopyToLive($live_ids, $temp_ids)
{
// delete records from live table by foreign key, so that records deleted from temp table
// get deleted from live
if ( $temp_ids && !$this->_inState(self::STATE_COPIED) ) {
if ( !$this->_foreignKey ) {
return;
}
$foreign_keys = $this->_parent->_getForeignKeys($this, $live_ids, $temp_ids);
if ( count($foreign_keys) > 0 ) {
$sql = 'SELECT ' . $this->_idField . '
FROM ' . $this->_tableName . '
WHERE ' . $this->_foreignKey . ' IN (' . implode(',', $foreign_keys) . ')';
$ids = $this->Conn->GetCol($this->_addConstrain($sql));
if ( $this->_raiseEvent('OnBeforeDeleteFromLive', '', $ids, $foreign_keys) ) {
$sql = 'DELETE FROM ' . $this->_tableName . '
WHERE ' . $this->_foreignKey . ' IN (' . implode(',', $foreign_keys) . ')';
$this->Conn->Query($this->_addConstrain($sql));
}
}
}
// sub_table passed here becomes master in the method, and recursively updated and copy its sub tables
$this->doCopyTempToLive();
}
/**
* Deletes unit db records and it's sub-items by foreign key
*
* @param kDBItem $object
* @param string $special
* @param Array $original_values
* @return void
* @access public
*/
public function subDeleteItems(kDBItem $object, $special, $original_values)
{
if ( !$this->_autoDelete || !$this->_foreignKey || !$this->_parentTableKey ) {
return;
}
$table_name = $object->IsTempTable() ? $this->_getTempTableName() : $this->_tableName;
$sql = 'SELECT ' . $this->_idField . '
FROM ' . $table_name . '
WHERE ' . $this->_foreignKey . ' = ' . $original_values[$this->_parentTableKey];
$sub_ids = $this->Conn->GetCol($sql);
$this->doDeleteItems($this->_prefix .'.' . $special, $sub_ids);
}
/**
* Clones unit db records and it's sub-items by foreign key
*
* @param kDBItem $object
* @param Array $original_values
* @return void
* @access public
*/
public function subCloneItems(kDBItem $object, $original_values)
{
if ( !$this->_autoClone || !$this->_foreignKey || !$this->_parentTableKey ) {
return;
}
$table_name = $object->IsTempTable() ? $this->_getTempTableName() : $this->_tableName;
$sql = 'SELECT ' . $this->_idField . '
FROM ' . $table_name . '
WHERE ' . $this->_foreignKey . ' = ' . $original_values[$this->_parentTableKey];
$sub_ids = $this->Conn->GetCol($this->_addConstrain($sql));
if ( $this->_multipleParents ) {
// $sub_ids could contain newly cloned items, we need to remove it here to escape double cloning
$cloned_ids = getArrayValue(self::$_clonedIds, $this->_tableName);
if ( !$cloned_ids ) {
$cloned_ids = Array ();
}
$sub_ids = array_diff($sub_ids, array_values($cloned_ids));
}
$parent_key = $object->GetDBField($this->_parentTableKey);
$this->doCloneItems($this->_prefix . '.' . $object->Special, $sub_ids, $parent_key);
}
/**
* Update foreign key columns after new ids were assigned instead of temporary ids in db
*
* @param int $live_id
* @param int $temp_id
* @return void
* @access public
*/
public function subUpdateForeignKeys($live_id, $temp_id)
{
if ( !$this->_foreignKey ) {
return;
}
list ($live_foreign_key, $temp_foreign_key) = $this->_parent->_getForeignKeys($this, $live_id, $temp_id);
// update ForeignKey in temporary sub-table
if ( $live_foreign_key == $temp_foreign_key ) {
return;
}
$sql = 'UPDATE ' . $this->_getTempTableName() . '
SET ' . $this->_foreignKey . ' = ' . $live_foreign_key . '
WHERE ' . $this->_foreignKey . ' = ' . $temp_foreign_key;
$this->Conn->Query($this->_addConstrain($sql));
}
}
\ No newline at end of file
Index: branches/5.3.x/core/install/step_templates/db_reconfig.tpl
===================================================================
--- branches/5.3.x/core/install/step_templates/db_reconfig.tpl (revision 15930)
+++ branches/5.3.x/core/install/step_templates/db_reconfig.tpl (revision 15931)
@@ -1,67 +1,67 @@
<tr class="table-color2">
<td class="text"><b>Database Server Type<span class="error">*</span>:</b></td>
<td align="left">
<select name="DBType">
<?php
- $options = Array ('mysql' => 'MySQL', /*'mssql' => 'MS-SQL Server', 'pgsql' => 'pgSQL'*/);
+ $options = Array ('mysqli' => 'MySQL', /*'mssql' => 'MS-SQL Server', 'pgsql' => 'pgSQL'*/);
$option_tpl = '<option value="%1$s"%2$s>%3$s</option>'."\n";
foreach ($options as $option_key => $option_title) {
$selected = $option_key == $this->toolkit->getSystemConfig('Database', 'DBType') ? ' selected' : '';
echo sprintf($option_tpl, $option_key, $selected, $option_title);
}
?>
</select>
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database Hostname <span class="error">*</span>:</b></td>
<td align="left">
<input type="text" name="DBHost" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBHost'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database Name <span class="error">*</span>:</b></td>
<td align="left">
<input type="text" name="DBName" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBName'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database User Name <span class="error">*</span>:</b></td>
<td align="left">
<input type="text" name="DBUser" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBUser'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database User Password:</b></td>
<td align="left">
<input type="password" name="DBUserPassword" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBUserPassword'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database Collation <span class="error">*</span>:</b></td>
<td align="left">
<select name="DBCollation" class="text">
<?php
$option_tpl = '<option value="%1$s"%2$s>%1$s</option>'."\n";
$collations = Array ('utf8_general_ci', 'latin1_swedish_ci');
foreach ($collations as $collation) {
$selected = ($collation == $this->toolkit->getSystemConfig('Database', 'DBCollation')) ? ' selected="selected"' : '';
echo sprintf($option_tpl, $collation, $selected);
}
?>
</select>
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Prefix for Table Names:</b></td>
<td align="left">
<input type="text" name="TablePrefix" class="text" maxlength="7" value="<?php echo $this->toolkit->getSystemConfig('Database', 'TablePrefix'); ?>" />
</td>
</tr>
\ No newline at end of file
Index: branches/5.3.x/core/install/step_templates/db_config.tpl
===================================================================
--- branches/5.3.x/core/install/step_templates/db_config.tpl (revision 15930)
+++ branches/5.3.x/core/install/step_templates/db_config.tpl (revision 15931)
@@ -1,77 +1,77 @@
<tr class="table-color2">
<td class="text"><b>Database Server Type<span class="error">*</span>:</b></td>
<td align="left">
<select name="DBType">
<?php
- $options = Array ('mysql' => 'MySQL', /*'mssql' => 'MS-SQL Server', 'pgsql' => 'pgSQL'*/);
+ $options = Array ('mysqli' => 'MySQL', /*'mssql' => 'MS-SQL Server', 'pgsql' => 'pgSQL'*/);
$option_tpl = '<option value="%1$s"%2$s>%3$s</option>'."\n";
foreach ($options as $option_key => $option_title) {
$selected = $option_key == $this->toolkit->getSystemConfig('Database', 'DBType') ? ' selected' : '';
echo sprintf($option_tpl, $option_key, $selected, $option_title);
}
?>
</select>
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database Hostname <span class="error">*</span>:</b></td>
<td align="left">
<input type="text" name="DBHost" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBHost'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database Name <span class="error">*</span>:</b></td>
<td align="left">
<input type="text" name="DBName" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBName'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database User Name <span class="error">*</span>:</b></td>
<td align="left">
<input type="text" name="DBUser" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBUser'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database User Password:</b></td>
<td align="left">
<input type="password" name="DBUserPassword" class="text" value="<?php echo $this->toolkit->getSystemConfig('Database', 'DBUserPassword'); ?>" />
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Database Collation <span class="error">*</span>:</b></td>
<td align="left">
<select name="DBCollation" class="text">
<?php
$option_tpl = '<option value="%1$s"%2$s>%1$s</option>'."\n";
$collations = Array ('utf8_general_ci', 'latin1_swedish_ci');
foreach ($collations as $collation) {
$selected = ($collation == $this->toolkit->getSystemConfig('Database', 'DBCollation')) ? ' selected="selected"' : '';
echo sprintf($option_tpl, $collation, $selected);
}
?>
</select>
</td>
</tr>
<tr class="table-color2">
<td class="text"><b>Prefix for Table Names:</b></td>
<td align="left">
<input type="text" name="TablePrefix" class="text" maxlength="7" value="<?php echo $this->toolkit->getSystemConfig('Database', 'TablePrefix'); ?>" />
</td>
</tr>
<?php if ($this->GetVar('preset') != 'already_installed') { ?>
-<!--show this option ONLY when config.php is empty or cant connect to In-Portal installation using current DB settings -->
+<!--show this option ONLY when config.php is empty or cant connect to In-Portal installation using current DB settings -->
<tr class="table-color2">
<td class="text" width="55%"><b>Use existing In-Portal installation in this Database:</b></td>
<td align="left">
<input type="radio" name="UseExistingSetup" id="UseExistingSetup_1" value="1"/> <label for="UseExistingSetup_1">Yes</label> <input type="radio" name="UseExistingSetup" id="UseExistingSetup_0" value="0" checked/> <label for="UseExistingSetup_0">No</label>
</td>
</tr>
<?php } ?>
\ No newline at end of file
Index: branches/5.3.x/core/install/prerequisites.php
===================================================================
--- branches/5.3.x/core/install/prerequisites.php (revision 15930)
+++ branches/5.3.x/core/install/prerequisites.php (revision 15931)
@@ -1,241 +1,241 @@
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
defined('FULL_PATH') or die('restricted access!');
$prerequisite_class = 'InPortalPrerequisites';
/**
* Class, that holds all prerequisite scripts for "In-Portal" module
*
*/
class InPortalPrerequisites {
/**
* Install toolkit instance
*
* @var kInstallToolkit
*/
var $_toolkit = null;
/**
* Connection to database
*
* @var IDBConnection
* @access protected
*/
protected $Conn = null;
/**
* Version upgrade rules
*
* @var array
* @access private
*/
private $upgradeRules = Array (
'5.0.0' => Array ('from' => '5.0.0-B1', 'to' => '5.1.0-B1'),
'5.1.0' => Array ('from' => '5.1.0-B1', 'to' => '5.2.0-B1'),
'5.2.0' => Array ('from' => '5.2.0-B1', 'to' => '5.3.0-B1'),
);
/**
* Sets common instance of installator toolkit
*
* @param kInstallToolkit $instance
*/
function setToolkit(&$instance)
{
$this->_toolkit =& $instance;
}
/**
* Checks minimal version, that could be upgradeable
*
* @return IDBConnection
*/
function getConnection()
{
return $this->_toolkit->Conn;
}
/**
* Checks minimal version, that could be upgradeable
*
* @param Array $versions
* @param string $mode when called mode {install, upgrade, standalone)
* @return Array
*/
function CheckPrerequisites($versions, $mode)
{
$errors = Array ();
if ( $mode == 'upgrade' ) {
$sql = 'SELECT Version
FROM ' . TABLE_PREFIX . 'Modules
WHERE Name = "In-Portal"';
$inportal_version = $this->getConnection()->GetOne($sql);
if ( $inportal_version === false ) {
// only, when In-Portal was installed (below 4.3.x)
return $errors;
}
$min_version = '4.3.1'; // K4-based installator was created, that no longer maintained old upgrade scripts
if ( version_compare($inportal_version, $min_version, '<') ) {
$errors[] = 'Please upgrade "In-Portal" to version ' . $min_version . ' first';
}
// example: to upgrade to 5.1.0-B1 or more you at least need to have 5.0.0 installed
foreach ($this->upgradeRules as $min_version => $version_rules) {
if ( version_compare($versions[0], $version_rules['from'], '<') && version_compare($versions[1], $version_rules['to'], '>=') ) {
$errors[] = 'Please upgrade "In-Portal" to version ' . $min_version . ' first';
break;
}
}
}
return $errors;
}
/**
* Returns information about system requirements
*
* @return array
*/
function CheckSystemRequirements()
{
$ret = Array ();
$ret['php_version'] = version_compare(PHP_VERSION, '5.2.0', '>=');
if ( function_exists('apache_get_modules') ) {
$mod_rewrite = in_array('mod_rewrite', apache_get_modules());
}
else {
$mod_rewrite = getenv('HTTP_MOD_REWRITE') == 'On';
}
$ret['url_rewriting'] = $mod_rewrite;
$ret['memcache'] = class_exists('Memcache');
$ret['curl'] = function_exists('curl_init');
$ret['simplexml'] = function_exists('simplexml_load_string');
$ret['spl'] = function_exists('spl_autoload_register');
$ret['freetype'] = function_exists('imagettfbbox');
$ret['gd_version'] = false;
if ( function_exists('gd_info') ) {
$gd_info = gd_info();
$gd_version = preg_replace('/[^\d.]/', '', $gd_info['GD Version']);
$ret['gd_version'] = version_compare($gd_version, '1.8', '>=');
}
$ret['jpeg'] = function_exists('imagecreatefromjpeg');
- $ret['mysql'] = function_exists('mysql_connect');
+ $ret['mysql'] = function_exists('mysqli_connect');
$ret['json'] = function_exists('json_encode');
$output = shell_exec('java -version 2>&1');
$ret['java'] = stripos($output, 'java version') !== false;
$ret['memory_limit'] = $this->isPhpSettingChangeable('memory_limit', '33M');
$ret['display_errors'] = $this->isPhpSettingChangeable('display_errors', '1');
$ret['error_reporting'] = $this->canChangeErrorReporting();
$ret['date.timezone'] = ini_get('date.timezone') != '';
$ret['variables_order'] = $this->_hasLetters(ini_get('variables_order'), Array ('G', 'P', 'C', 'S'));
$output_buffering = strtolower(ini_get('output_buffering'));
$ret['output_buffering'] = $output_buffering == 'on' || $output_buffering > 0;
return $ret;
}
/**
* Determines of a setting string has all given letters (ignoring order) in it
*
* @param string $setting
* @param Array $search_letters
* @return bool
* @access protected
*/
protected function _hasLetters($setting, $search_letters)
{
$setting = preg_replace('/(' . implode('|', $search_letters) . ')/', '*', $setting);
return substr_count($setting, '*') == count($search_letters);
}
/**
* Detects if error reporting can be changed at runtime
*
* @return bool
* @access protected
*/
protected function canChangeErrorReporting()
{
$old_value = error_reporting(E_PARSE);
$new_value = error_reporting();
if ( $new_value == E_PARSE ) {
error_reporting($old_value);
return true;
}
return false;
}
/**
* Detects if setting of php.ini can be changed
*
* @param string $setting_name
* @param string $new_value
* @return bool
*/
protected function isPhpSettingChangeable($setting_name, $new_value)
{
$old_value = ini_get($setting_name);
if ( ini_set($setting_name, $new_value) === false ) {
return false;
}
ini_set($setting_name, $old_value);
return true;
}
/**
* Returns information about DB requirements
*
* @return array
*/
function CheckDBRequirements()
{
// check PHP version 5.2+
$ret = Array();
$sql = 'SELECT VERSION()';
$conn = $this->getConnection();
$db_version = preg_replace('/[^\d.]/', '', $conn->GetOne($sql));
$ret['version'] = version_compare($db_version, '5.0', '>=');
$sql = 'SHOW VARIABLES LIKE "max_allowed_packet"';
$db_variables = $conn->Query($sql, 'Variable_name');
$ret['packet_size'] = $db_variables['max_allowed_packet']['Value'] >= 1048576;
return $ret;
}
}
\ No newline at end of file
Event Timeline
Log In to Comment