Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Fri, Jun 20, 5:59 AM

in-portal

Index: branches/5.2.x/core/kernel/db/db_load_balancer.php
===================================================================
--- branches/5.2.x/core/kernel/db/db_load_balancer.php (revision 15238)
+++ branches/5.2.x/core/kernel/db/db_load_balancer.php (revision 15239)
@@ -1,656 +1,659 @@
<?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 {
/**
* Current database type
*
* @var string
* @access protected
*/
protected $dbType = 'mysql';
/**
* 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
*/
function __construct($dbType, $errorHandler = '')
{
parent::__construct();
$this->dbType = $dbType;
$this->errorHandler = $errorHandler;
$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 = adodb_mktime() - $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'] = adodb_mktime();
$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;
}
/**
* 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)
{
- $db = $this->Application->makeClass( 'kDBConnection', Array ($this->dbType, $this->errorHandler, $server['serverIndex']) );
+ $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 = $this->Application->isDebugMode();
+ $db->debugMode = $debug_mode;
$db->Connect($server['DBHost'], $server['DBUser'], $server['DBUserPassword'], $this->servers[0]['DBName'], true, !$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);
}
/**
* 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);
}
/**
* 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);
}
/**
* If it's a string, adds quotes and backslashes (only work since PHP 4.3.0)
* 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);
}
/**
* 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);
}
/**
* 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;
}
}
Index: branches/5.2.x/core/kernel/db/db_connection.php
===================================================================
--- branches/5.2.x/core/kernel/db/db_connection.php (revision 15238)
+++ branches/5.2.x/core/kernel/db/db_connection.php (revision 15239)
@@ -1,1106 +1,1463 @@
<?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 {
/**
* Current database type
*
* @var string
* @access protected
*/
protected $dbType = 'mysql';
/**
* Created connection handle
*
* @var resource
* @access protected
*/
protected $connectionID = null;
/**
* 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
* @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 int $server_index
* @access public
*/
public function __construct($dbType, $errorHandler = '', $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) {
$this->errorHandler = Array(&$this, 'handleError');
}
else {
$this->errorHandler = $errorHandler;
}
$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
*/
public function Connect($host, $user, $pass, $db, $force_new = false, $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);
if ($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.'\'');
}
$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 ( is_resource($this->connectionID) && !$this->hasError() ) {
$this->connectionOpened = true;
return true;
}
$func = $this->getMetaFunction('error');
$this->errorMessage = $this->connectionID ? $func($this->connectionID) : $func();
$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 Exception($error_msg);
}
$this->connectionOpened = false;
return false;
}
/**
* 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)
{
$retry_count = 0;
$connected = false;
$func = $this->getMetaFunction('close');
$func($this->connectionID);
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
);
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) {
// no connection while doing mysql_query
$this->errorCode = $func();
if ( $this->hasError() ) {
$func = $this->getMetaFunction('error');
$this->errorMessage = $func();
$ret = $this->callErrorHandler($sql);
if (!$ret) {
exit;
}
}
return false;
}
// checking if there was an error during last mysql_query
$this->errorCode = $func($this->connectionID);
if ( $this->hasError() ) {
$func = $this->getMetaFunction('error');
$this->errorMessage = $func($this->connectionID);
$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;
}
/**
* 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;
- if ( !$no_debug ) {
- $this->_queryCount++;
- }
- if ( $this->debugMode && !$no_debug ) {
- return $this->debugQuery($sql, $key_field);
- }
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
- if ( $this->_captureStatistics ) {
- $start_time = microtime(true);
- }
+ $start_time = $this->_captureStatistics ? microtime(true) : 0;
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
$this->queryID = $query_func($sql, $this->connectionID);
+
if ( is_resource($this->queryID) ) {
$ret = Array ();
$fetch_func = $this->getMetaFunction('fetch_assoc');
+
if ( isset($key_field) ) {
- while ( ($row = $fetch_func($this->queryID)) ) {
+ while (($row = $fetch_func($this->queryID))) {
$ret[$row[$key_field]] = $row;
}
}
else {
- while ( ($row = $fetch_func($this->queryID)) ) {
+ while (($row = $fetch_func($this->queryID))) {
$ret[] = $row;
}
}
// set 2nd checkpoint: begin
if ( $this->_captureStatistics ) {
$query_time = microtime(true) - $start_time;
- if ( $query_time > DBG_MAX_SQL_TIME && !$no_debug ) {
+
+ 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);
}
/**
- * Retrieves data from database and return resource id on success or false otherwise
+ * 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 $select_sql
- * @return resource
+ * @param string $sql
+ * @param string $key_field
+ * @param bool $no_debug
+ * @param string $iterator_class
+ * @return kMySQLQuery|bool
* @access public
- * @see kDBConnection::GetNextRow
*/
- public function QueryRaw($select_sql)
+ public function GetIterator($sql, $key_field = null, $no_debug = false, $iterator_class = 'kMySQLQuery')
{
- $this->lastQuery = $select_sql;
$this->_queryCount++;
+ $this->lastQuery = $sql;
$query_func = $this->getMetaFunction('query');
// set 1st checkpoint: begin
- if ($this->_captureStatistics) {
- $start_time = microtime(true);
- }
+ $start_time = $this->_captureStatistics ? microtime(true) : 0;
// set 1st checkpoint: end
$this->setError(0, ''); // reset error
- $resource = $query_func($select_sql, $this->connectionID);
-
- // set 2nd checkpoint: begin
- if ($this->_captureStatistics) {
- $query_time = microtime(true) - $start_time;
- if ($query_time > DBG_MAX_SQL_TIME) {
- $this->Application->logSlowQuery($select_sql, $query_time);
- }
- $this->_queryTime += $query_time;
- }
- // set 2nd checkpoint: end
+ $this->queryID = $query_func($sql, $this->connectionID);
- if ( is_resource($resource) ) {
- return $resource;
- }
+ if ( is_resource($this->queryID) ) {
+ $ret = new $iterator_class($this->queryID, $key_field);
+ /* @var $ret kMySQLQuery */
- return $this->showError($select_sql);
- }
+ // set 2nd checkpoint: begin
+ if ( $this->_captureStatistics ) {
+ $query_time = microtime(true) - $start_time;
- /**
- * Returns row count in recordset
- *
- * @param resource $resource
- * @return int
- * @access public
- */
- public function RowCount($resource)
- {
- $count_func = $this->getMetaFunction('num_rows');
+ if ( $query_time > DBG_MAX_SQL_TIME ) {
+ $this->Application->logSlowQuery($sql, $query_time);
+ }
- return $count_func($resource);
- }
+ $this->_queryTime += $query_time;
+ }
+ // set 2nd checkpoint: end
- /**
- * Returns next available row from recordset
- *
- * @param resource $resource
- * @param string $fetch_type
- * @return Array
- * @access public
- * @see kDBConnection::QueryRaw
- */
- public function GetNextRow($resource, $fetch_type = 'assoc')
- {
- $fetch_func = $this->getMetaFunction('fetch_' . $fetch_type);
+ return $ret;
+ }
+ else {
+ // set 2nd checkpoint: begin
+ if ( $this->_captureStatistics ) {
+ $this->_queryTime += microtime(true) - $start_time;
+ }
+ // set 2nd checkpoint: end
+ }
- return $fetch_func($resource);
+ return $this->showError($sql, $key_field, $no_debug);
}
/**
* Free memory used to hold recordset handle
*
- * @param resource $resource
* @access public
- * @see kDBConnection::QueryRaw
*/
- public function Destroy($resource = null)
+ public function Destroy()
{
- if ( !isset($resource) ) {
- $resource = $this->queryID;
- }
+ $free_func = $this->getMetaFunction('free_result');
+ $free_func($this->queryID);
- if ( $resource ) {
- $free_func = $this->getMetaFunction('free_result');
- $free_func($resource);
-
- $this->queryID = null;
- }
+ 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->errorCode == 0 ? true : false;
- }
- /**
- * 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.
- *
- * Each database query will be profiled into Debugger.
- *
- * @param string $sql
- * @param string $key_field
- * @return Array
- * @access public
- */
- protected function debugQuery($sql, $key_field = null)
- {
- global $debugger;
- $query_func = $this->getMetaFunction('query');
-
- // set 1st checkpoint: begin
- $profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE;
- if ( $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);
-
- if ( is_resource($this->queryID) ) {
- $ret = Array ();
- $fetch_func = $this->getMetaFunction('fetch_assoc');
- if ( isset($key_field) ) {
- while ( ($row = $fetch_func($this->queryID)) ) {
- $ret[$row[$key_field]] = $row;
- }
- }
- else {
- while ( ($row = $fetch_func($this->queryID)) ) {
- $ret[] = $row;
- }
- }
-
- // set 2nd checkpoint: begin
- if ( $profileSQLs ) {
- $first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : 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->serverIndex);
- $debugger->profilerAddTotal('sql', 'sql_' . $queryID);
- $this->nextQueryCachable = false;
- }
- // set 2nd checkpoint: end
+ return !$this->hasError();
+ }
- $this->Destroy();
- return $ret;
- }
- else {
- // set 2nd checkpoint: begin
- if ( $profileSQLs ) {
- $debugger->profileFinish('sql_' . $queryID, null, null, $this->getAffectedRows(), null, $this->_queryCount, $this->nextQueryCachable, $this->serverIndex);
- $debugger->profilerAddTotal('sql', 'sql_' . $queryID);
- $this->nextQueryCachable = false;
- }
- // set 2nd checkpoint: end
- }
- return $this->showError($sql, $key_field);
- }
/**
* 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);
}
/**
* Returns row count affected by last query
*
* @return int
* @access public
*/
public function getAffectedRows()
{
$func = $this->getMetaFunction('affected_rows');
return $func($this->connectionID);
}
/**
* 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;
}
}
/**
* If it's a string, adds quotes and backslashes (only work since PHP 4.3.0)
* 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) . "'";
}
/**
* 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 strings (only work since PHP 4.3.0)
*
* @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);
// 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
- $rs = $this->QueryRaw('SHOW PROCESSLIST');
+ $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
- while ( $row = $this->GetNextRow($rs, 'array') ) {
- if ( $row['User'] == 'system user' && !in_array($row['State'], $skip_states) ) {
+ foreach ($processes as $process) {
+ if ( $process['User'] == 'system user' && !in_array($process['State'], $skip_states) ) {
// this is it, return the time (except -ve)
- $this->Destroy($rs);
- return $row['Time'] > 0x7fffffff ? false : $row['Time'];
+ return $process['Time'] > 0x7fffffff ? false : $process['Time'];
}
}
- $this->Destroy($rs);
-
return false;
}
- }
\ No newline at end of file
+ }
+
+
+class kDBConnectionDebug extends kDBConnection {
+
+ protected $_profileSQLs = false;
+
+ /**
+ * Initializes connection class with
+ * db type to used in future
+ *
+ * @param string $dbType
+ * @param string $errorHandler
+ * @param int $server_index
+ * @access public
+ */
+ public function __construct($dbType, $errorHandler = '', $server_index = 0)
+ {
+ parent::__construct($dbType, $errorHandler, $server_index);
+
+ $this->_profileSQLs = defined('DBG_SQL_PROFILE') && DBG_SQL_PROFILE;
+ }
+
+ /**
+ * 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);
+
+ if ( is_resource($this->queryID) ) {
+ $ret = Array ();
+ $fetch_func = $this->getMetaFunction('fetch_assoc');
+
+ if ( isset($key_field) ) {
+ while (($row = $fetch_func($this->queryID))) {
+ $ret[$row[$key_field]] = $row;
+ }
+ }
+ else {
+ while (($row = $fetch_func($this->queryID))) {
+ $ret[] = $row;
+ }
+ }
+
+ // set 2nd checkpoint: begin
+ if ( $this->_profileSQLs ) {
+ $first_cell = count($ret) == 1 && count(current($ret)) == 1 ? current(current($ret)) : 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->serverIndex);
+ $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->serverIndex);
+ $debugger->profilerAddTotal('sql', 'sql_' . $queryID);
+ $this->nextQueryCachable = false;
+ }
+ // set 2nd checkpoint: end
+ }
+
+ return $this->showError($sql, $key_field);
+ }
+
+ /**
+ * 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);
+
+ if ( is_resource($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->serverIndex);
+ $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->serverIndex);
+ $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
+ * @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 null|string $key_field
+ */
+ public function __construct($result, $key_field = null)
+ {
+ $this->result = $result;
+ $this->keyField = $key_field;
+
+ $this->rowCount = mysql_num_rows($this->result);
+ $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->rowData = mysql_fetch_array($this->result);
+ }
+
+ /*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);
+ 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.2.x/core/kernel/application.php
===================================================================
--- branches/5.2.x/core/kernel/application.php (revision 15238)
+++ branches/5.2.x/core/kernel/application.php (revision 15239)
@@ -1,3158 +1,3159 @@
<?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!');
/**
* Basic class for Kernel4-based Application
*
* This class is a Facade for any other class which needs to deal with Kernel4 framework.<br>
* The class encapsulates the main run-cycle of the script, provide access to all other objects in the framework.<br>
* <br>
* The class is a singleton, which means that there could be only one instance of kApplication in the script.<br>
* This could be guaranteed by NOT calling the class constructor directly, but rather calling kApplication::Instance() method,
* which returns an instance of the application. The method guarantees that it will return exactly the same instance for any call.<br>
* See singleton pattern by GOF.
*/
class kApplication implements kiCacheable {
/**
* Location of module helper class (used in installator too)
*/
const MODULE_HELPER_PATH = '/../units/helpers/modules_helper.php';
/**
* Is true, when Init method was called already, prevents double initialization
*
* @var bool
*/
public $InitDone = false;
/**
* Holds internal NParser object
*
* @var NParser
* @access public
*/
public $Parser;
/**
* Holds parser output buffer
*
* @var string
* @access protected
*/
protected $HTML = '';
/**
* The main Factory used to create
* almost any class of kernel and
* modules
*
* @var kFactory
* @access protected
*/
protected $Factory;
/**
* Template names, that will be used instead of regular templates
*
* @var Array
* @access public
*/
public $ReplacementTemplates = Array ();
/**
* Mod-Rewrite listeners used during url building and parsing
*
* @var Array
* @access public
*/
public $RewriteListeners = Array ();
/**
* Reference to debugger
*
* @var Debugger
* @access public
*/
public $Debugger = null;
/**
* Holds all phrases used
* in code and template
*
* @var PhrasesCache
* @access public
*/
public $Phrases;
/**
* Modules table content, key - module name
*
* @var Array
* @access public
*/
public $ModuleInfo = Array ();
/**
* Holds DBConnection
*
* @var kDBConnection
* @access public
*/
public $Conn = null;
/**
* Maintains list of user-defined error handlers
*
* @var Array
* @access public
*/
public $errorHandlers = Array ();
/**
* Maintains list of user-defined exception handlers
*
* @var Array
* @access public
*/
public $exceptionHandlers = Array ();
// performance needs:
/**
* Holds a reference to httpquery
*
* @var kHttpQuery
* @access public
*/
public $HttpQuery = null;
/**
* Holds a reference to UnitConfigReader
*
* @var kUnitConfigReader
* @access public
*/
public $UnitConfigReader = null;
/**
* Holds a reference to Session
*
* @var Session
* @access public
*/
public $Session = null;
/**
* Holds a ref to kEventManager
*
* @var kEventManager
* @access public
*/
public $EventManager = null;
/**
* Holds a ref to kUrlManager
*
* @var kUrlManager
* @access public
*/
public $UrlManager = null;
/**
* Ref for TemplatesCache
*
* @var TemplatesCache
* @access public
*/
public $TemplatesCache = null;
/**
* Holds current NParser tag while parsing, can be used in error messages to display template file and line
*
* @var _BlockTag
* @access public
*/
public $CurrentNTag = null;
/**
* Object of unit caching class
*
* @var kCacheManager
* @access public
*/
public $cacheManager = null;
/**
* Tells, that administrator has authenticated in administrative console
* Should be used to manipulate data change OR data restrictions!
*
* @var bool
* @access public
*/
public $isAdminUser = false;
/**
* Tells, that admin version of "index.php" was used, nothing more!
* Should be used to manipulate data display!
*
* @var bool
* @access public
*/
public $isAdmin = false;
/**
* Instance of site domain object
*
* @var kDBItem
* @access public
* @todo move away into separate module
*/
public $siteDomain = null;
/**
* Prevent kApplication class to be created directly, only via Instance method
*
* @access private
*/
private function __construct()
{
}
final private function __clone() {}
/**
* Returns kApplication instance anywhere in the script.
*
* This method should be used to get single kApplication object instance anywhere in the
* Kernel-based application. The method is guaranteed to return the SAME instance of kApplication.
* Anywhere in the script you could write:
* <code>
* $application =& kApplication::Instance();
* </code>
* or in an object:
* <code>
* $this->Application =& kApplication::Instance();
* </code>
* to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class.
* To use descendant of standard kApplication class in your project you would need to define APPLICATION_CLASS constant
* BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would
* create and return default KernelApplication instance.
*
* Pattern: Singleton
*
* @static
* @return kApplication
* @access public
*/
public static function &Instance()
{
static $instance = false;
if ( !$instance ) {
$class = defined('APPLICATION_CLASS') ? APPLICATION_CLASS : 'kApplication';
$instance = new $class();
}
return $instance;
}
/**
* Initializes the Application
*
* @param string $factory_class
* @return bool Was Init actually made now or before
* @access public
* @see kHTTPQuery
* @see Session
* @see TemplatesCache
*/
public function Init($factory_class = 'kFactory')
{
if ( $this->InitDone ) {
return false;
}
$this->isAdmin = kUtil::constOn('ADMIN');
if ( !kUtil::constOn('SKIP_OUT_COMPRESSION') ) {
ob_start(); // collect any output from method (other then tags) into buffer
}
if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
$this->Debugger->appendMemoryUsage('Application before Init:');
}
if ( !$this->isDebugMode() && !kUtil::constOn('DBG_ZEND_PRESENT') ) {
error_reporting(0);
ini_set('display_errors', 0);
}
if ( !kUtil::constOn('DBG_ZEND_PRESENT') ) {
$error_handler = set_error_handler(Array (&$this, 'handleError'));
if ( $error_handler ) {
// wrap around previous error handler, if any was set
$this->errorHandlers[] = $error_handler;
}
$exception_handler = set_exception_handler(Array (&$this, 'handleException'));
if ( $exception_handler ) {
// wrap around previous exception handler, if any was set
$this->exceptionHandlers[] = $exception_handler;
}
}
$this->Factory = new $factory_class();
$this->registerDefaultClasses();
$vars = kUtil::parseConfig(true);
- $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : 'kDBConnection';
+ $db_class = isset($vars['Databases']) ? 'kDBLoadBalancer' : ($this->isDebugMode() ? 'kDBConnectionDebug' : 'kDBConnection');
$this->Conn = $this->Factory->makeClass($db_class, Array (SQL_TYPE, Array (&$this, 'handleSQLError')));
$this->Conn->setup($vars);
$this->cacheManager = $this->makeClass('kCacheManager');
$this->cacheManager->InitCache();
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendTimestamp('Before UnitConfigReader');
}
// init config reader and all managers
$this->UnitConfigReader = $this->makeClass('kUnitConfigReader');
$this->UnitConfigReader->scanModules(MODULES_PATH); // will also set RewriteListeners when existing cache is read
$this->registerModuleConstants();
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendTimestamp('After UnitConfigReader');
}
define('MOD_REWRITE', $this->ConfigValue('UseModRewrite') && !$this->isAdmin ? 1 : 0);
// start processing request
$this->HttpQuery = $this->recallObject('HTTPQuery');
$this->HttpQuery->process();
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendTimestamp('Processed HTTPQuery initial');
}
$this->Session = $this->recallObject('Session');
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendTimestamp('Processed Session');
}
$this->Session->ValidateExpired(); // needs mod_rewrite url already parsed to keep user at proper template after session expiration
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendTimestamp('Processed HTTPQuery AfterInit');
}
$this->cacheManager->LoadApplicationCache();
$site_timezone = $this->ConfigValue('Config_Site_Time');
if ( $site_timezone ) {
putenv('TZ=' . $site_timezone);
}
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendTimestamp('Loaded cache and phrases');
}
$this->ValidateLogin(); // must be called before AfterConfigRead, because current user should be available there
$this->UnitConfigReader->AfterConfigRead(); // will set RewriteListeners when missing cache is built first time
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendTimestamp('Processed AfterConfigRead');
}
if ( $this->GetVar('m_cat_id') === false ) {
$this->SetVar('m_cat_id', 0);
}
if ( !$this->RecallVar('curr_iso') ) {
$this->StoreVar('curr_iso', $this->GetPrimaryCurrency(), true); // true for optional
}
$visit_id = $this->RecallVar('visit_id');
if ( $visit_id !== false ) {
$this->SetVar('visits_id', $visit_id);
}
$language = $this->recallObject('lang.current', null, Array ('live_table' => true));
/* @var $language LanguagesItem */
if ( preg_match('/utf-8/', $language->GetDBField('Charset')) ) {
setlocale(LC_ALL, 'en_US.UTF-8');
mb_internal_encoding('UTF-8');
}
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->profileFinish('kernel4_startup');
}
$this->InitDone = true;
$this->HandleEvent(new kEvent('adm:OnStartup'));
return true;
}
/**
* Performs initialization of manager classes, that can be overridden from unit configs
*
* @return void
* @access public
* @throws Exception
*/
public function InitManagers()
{
if ( $this->InitDone ) {
throw new Exception('Duplicate call of ' . __METHOD__, E_USER_ERROR);
return;
}
$this->UrlManager = $this->makeClass('kUrlManager');
$this->EventManager = $this->makeClass('EventManager');
$this->Phrases = $this->makeClass('kPhraseCache');
$this->RegisterDefaultBuildEvents();
}
/**
* Returns module information. Searches module by requested field
*
* @param string $field
* @param mixed $value
* @param string $return_field field value to returns, if not specified, then return all fields
* @return Array
*/
public function findModule($field, $value, $return_field = null)
{
$found = $module_info = false;
foreach ($this->ModuleInfo as $module_info) {
if ( strtolower($module_info[$field]) == strtolower($value) ) {
$found = true;
break;
}
}
if ( $found ) {
return isset($return_field) ? $module_info[$return_field] : $module_info;
}
return false;
}
/**
* Refreshes information about loaded modules
*
* @return void
* @access public
*/
public function refreshModuleInfo()
{
if ( defined('IS_INSTALL') && IS_INSTALL && !$this->TableFound('Modules', true) ) {
$this->registerModuleConstants();
return;
}
// use makeClass over recallObject, since used before kApplication initialization during installation
$modules_helper = $this->makeClass('ModulesHelper');
/* @var $modules_helper kModulesHelper */
$this->Conn->nextQueryCachable = true;
$sql = 'SELECT *
FROM ' . TABLE_PREFIX . 'Modules
WHERE ' . $modules_helper->getWhereClause() . '
ORDER BY LoadOrder';
$this->ModuleInfo = $this->Conn->Query($sql, 'Name');
$this->registerModuleConstants();
}
/**
* Checks if passed language id if valid and sets it to primary otherwise
*
* @return void
* @access public
*/
public function VerifyLanguageId()
{
$language_id = $this->GetVar('m_lang');
if ( !$language_id ) {
$language_id = 'default';
}
$this->SetVar('lang.current_id', $language_id);
$this->SetVar('m_lang', $language_id);
$lang_mode = $this->GetVar('lang_mode');
$this->SetVar('lang_mode', '');
$lang = $this->recallObject('lang.current');
/* @var $lang kDBItem */
if ( !$lang->isLoaded() || (!$this->isAdmin && !$lang->GetDBField('Enabled')) ) {
if ( !defined('IS_INSTALL') ) {
$this->ApplicationDie('Unknown or disabled language');
}
}
$this->SetVar('lang_mode', $lang_mode);
}
/**
* Checks if passed theme id if valid and sets it to primary otherwise
*
* @return void
* @access public
*/
public function VerifyThemeId()
{
if ( $this->isAdmin ) {
kUtil::safeDefine('THEMES_PATH', '/core/admin_templates');
return;
}
$path = $this->GetFrontThemePath();
if ( $path === false ) {
$this->ApplicationDie('No Primary Theme Selected or Current Theme is Unknown or Disabled');
}
kUtil::safeDefine('THEMES_PATH', $path);
}
/**
* Returns relative path to current front-end theme
*
* @param bool $force
* @return string
* @access public
*/
public function GetFrontThemePath($force = false)
{
static $path = null;
if ( !$force && isset($path) ) {
return $path;
}
$theme_id = $this->GetVar('m_theme');
if ( !$theme_id ) {
$theme_id = 'default'; // $this->GetDefaultThemeId(1); // 1 to force front-end mode!
}
$this->SetVar('m_theme', $theme_id);
$this->SetVar('theme.current_id', $theme_id); // KOSTJA: this is to fool theme' getPassedID
$theme = $this->recallObject('theme.current');
/* @var $theme ThemeItem */
if ( !$theme->isLoaded() || !$theme->GetDBField('Enabled') ) {
return false;
}
// assign & then return, since it's static variable
$path = '/themes/' . $theme->GetDBField('Name');
return $path;
}
/**
* Returns primary front/admin language id
*
* @param bool $init
* @return int
* @access public
*/
public function GetDefaultLanguageId($init = false)
{
$cache_key = 'primary_language_info[%LangSerial%]';
$language_info = $this->getCache($cache_key);
if ( $language_info === false ) {
// cache primary language info first
$table = $this->getUnitOption('lang', 'TableName');
$id_field = $this->getUnitOption('lang', 'IDField');
$this->Conn->nextQueryCachable = true;
$sql = 'SELECT ' . $id_field . ', IF(AdminInterfaceLang, "Admin", "Front") AS LanguageKey
FROM ' . $table . '
WHERE (AdminInterfaceLang = 1 OR PrimaryLang = 1) AND (Enabled = 1)';
$language_info = $this->Conn->GetCol($sql, 'LanguageKey');
if ( $language_info !== false ) {
$this->setCache($cache_key, $language_info);
}
}
$language_key = ($this->isAdmin && $init) || count($language_info) == 1 ? 'Admin' : 'Front';
if ( array_key_exists($language_key, $language_info) && $language_info[$language_key] > 0 ) {
// get from cache
return $language_info[$language_key];
}
$language_id = $language_info && array_key_exists($language_key, $language_info) ? $language_info[$language_key] : false;
if ( !$language_id && defined('IS_INSTALL') && IS_INSTALL ) {
$language_id = 1;
}
return $language_id;
}
/**
* Returns front-end primary theme id (even, when called from admin console)
*
* @param bool $force_front
* @return int
* @access public
*/
public function GetDefaultThemeId($force_front = false)
{
static $theme_id = 0;
if ( $theme_id > 0 ) {
return $theme_id;
}
if ( kUtil::constOn('DBG_FORCE_THEME') ) {
$theme_id = DBG_FORCE_THEME;
}
elseif ( !$force_front && $this->isAdmin ) {
$theme_id = 999;
}
else {
$cache_key = 'primary_theme[%ThemeSerial%]';
$theme_id = $this->getCache($cache_key);
if ( $theme_id === false ) {
$this->Conn->nextQueryCachable = true;
$sql = 'SELECT ' . $this->getUnitOption('theme', 'IDField') . '
FROM ' . $this->getUnitOption('theme', 'TableName') . '
WHERE (PrimaryTheme = 1) AND (Enabled = 1)';
$theme_id = $this->Conn->GetOne($sql);
if ( $theme_id !== false ) {
$this->setCache($cache_key, $theme_id);
}
}
}
return $theme_id;
}
/**
* Returns site primary currency ISO code
*
* @return string
* @access public
* @todo Move into In-Commerce
*/
public function GetPrimaryCurrency()
{
$cache_key = 'primary_currency[%CurrSerial%][%SiteDomainSerial%]:' . $this->siteDomainField('DomainId');
$currency_iso = $this->getCache($cache_key);
if ( $currency_iso === false ) {
if ( $this->isModuleEnabled('In-Commerce') ) {
$this->Conn->nextQueryCachable = true;
$currency_id = $this->siteDomainField('PrimaryCurrencyId');
$sql = 'SELECT ISO
FROM ' . $this->getUnitOption('curr', 'TableName') . '
WHERE ' . ($currency_id > 0 ? 'CurrencyId = ' . $currency_id : 'IsPrimary = 1');
$currency_iso = $this->Conn->GetOne($sql);
}
else {
$currency_iso = 'USD';
}
$this->setCache($cache_key, $currency_iso);
}
return $currency_iso;
}
/**
* Returns site domain field. When none of site domains are found false is returned.
*
* @param string $field
* @param bool $formatted
* @param string $format
* @return mixed
* @todo Move into separate module
*/
public function siteDomainField($field, $formatted = false, $format = null)
{
if ( $this->isAdmin ) {
// don't apply any filtering in administrative console
return false;
}
if ( !$this->siteDomain ) {
$this->siteDomain = $this->recallObject('site-domain.current');
/* @var $site_domain kDBItem */
}
if ( $this->siteDomain->isLoaded() ) {
return $formatted ? $this->siteDomain->GetField($field, $format) : $this->siteDomain->GetDBField($field);
}
return false;
}
/**
* Registers default classes such as kDBEventHandler, kUrlManager
*
* Called automatically while initializing kApplication
*
* @return void
* @access public
*/
public function RegisterDefaultClasses()
{
$this->registerClass('kHelper', KERNEL_PATH . '/kbase.php');
$this->registerClass('kMultipleFilter', KERNEL_PATH . '/utility/filters.php');
$this->registerClass('kiCacheable', KERNEL_PATH . '/interfaces/cacheable.php');
$this->registerClass('kEventManager', KERNEL_PATH . '/event_manager.php', 'EventManager');
$this->registerClass('kHookManager', KERNEL_PATH . '/managers/hook_manager.php');
$this->registerClass('kScheduledTaskManager', KERNEL_PATH . '/managers/scheduled_task_manager.php');
$this->registerClass('kRequestManager', KERNEL_PATH . '/managers/request_manager.php');
$this->registerClass('kUrlManager', KERNEL_PATH . '/managers/url_manager.php');
$this->registerClass('kUrlProcessor', KERNEL_PATH . '/managers/url_processor.php');
$this->registerClass('kPlainUrlProcessor', KERNEL_PATH . '/managers/plain_url_processor.php');
$this->registerClass('kRewriteUrlProcessor', KERNEL_PATH . '/managers/rewrite_url_processor.php');
$this->registerClass('kCacheManager', KERNEL_PATH . '/managers/cache_manager.php');
$this->registerClass('PhrasesCache', KERNEL_PATH . '/languages/phrases_cache.php', 'kPhraseCache');
$this->registerClass('kTempTablesHandler', KERNEL_PATH . '/utility/temp_handler.php');
$this->registerClass('kValidator', KERNEL_PATH . '/utility/validator.php');
$this->registerClass('kOpenerStack', KERNEL_PATH . '/utility/opener_stack.php');
$this->registerClass('kUnitConfigReader', KERNEL_PATH . '/utility/unit_config_reader.php');
// Params class descendants
$this->registerClass('kArray', KERNEL_PATH . '/utility/params.php');
$this->registerClass('Params', KERNEL_PATH . '/utility/params.php');
$this->registerClass('Params', KERNEL_PATH . '/utility/params.php', 'kActions');
$this->registerClass('kCache', KERNEL_PATH . '/utility/cache.php', 'kCache', 'Params');
$this->registerClass('kHTTPQuery', KERNEL_PATH . '/utility/http_query.php', 'HTTPQuery');
// session
$this->registerClass('Session', KERNEL_PATH . '/session/session.php');
$this->registerClass('SessionStorage', KERNEL_PATH . '/session/session_storage.php');
$this->registerClass('InpSession', KERNEL_PATH . '/session/inp_session.php', 'Session');
$this->registerClass('InpSessionStorage', KERNEL_PATH . '/session/inp_session_storage.php', 'SessionStorage');
// template parser
$this->registerClass('kTagProcessor', KERNEL_PATH . '/processors/tag_processor.php');
$this->registerClass('kMainTagProcessor', KERNEL_PATH . '/processors/main_processor.php', 'm_TagProcessor');
$this->registerClass('kDBTagProcessor', KERNEL_PATH . '/db/db_tag_processor.php');
$this->registerClass('kCatDBTagProcessor', KERNEL_PATH . '/db/cat_tag_processor.php');
$this->registerClass('NParser', KERNEL_PATH . '/nparser/nparser.php');
$this->registerClass('TemplatesCache', KERNEL_PATH . '/nparser/template_cache.php');
// database
$this->registerClass('kDBConnection', KERNEL_PATH . '/db/db_connection.php');
+ $this->registerClass('kDBConnectionDebug', KERNEL_PATH . '/db/db_connection.php');
$this->registerClass('kDBLoadBalancer', KERNEL_PATH . '/db/db_load_balancer.php');
$this->registerClass('kDBItem', KERNEL_PATH . '/db/dbitem.php');
$this->registerClass('kCatDBItem', KERNEL_PATH . '/db/cat_dbitem.php');
$this->registerClass('kDBList', KERNEL_PATH . '/db/dblist.php');
$this->registerClass('kCatDBList', KERNEL_PATH . '/db/cat_dblist.php');
$this->registerClass('kDBEventHandler', KERNEL_PATH . '/db/db_event_handler.php');
$this->registerClass('kCatDBEventHandler', KERNEL_PATH . '/db/cat_event_handler.php');
// email sending
$this->registerClass('kEmail', KERNEL_PATH . '/utility/email.php');
$this->registerClass('kEmailSendingHelper', KERNEL_PATH . '/utility/email_send.php', 'EmailSender');
$this->registerClass('kSocket', KERNEL_PATH . '/utility/socket.php', 'Socket');
// do not move to config - this helper is used before configs are read
$this->registerClass('kModulesHelper', KERNEL_PATH . self::MODULE_HELPER_PATH, 'ModulesHelper');
}
/**
* Registers default build events
*
* @return void
* @access protected
*/
protected function RegisterDefaultBuildEvents()
{
$this->EventManager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild');
}
/**
* Returns cached category information by given cache name. All given category
* information is recached, when at least one of 4 caches is missing.
*
* @param int $category_id
* @param string $name cache name = {filenames, category_designs, category_tree}
* @return string
* @access public
*/
public function getCategoryCache($category_id, $name)
{
return $this->cacheManager->getCategoryCache($category_id, $name);
}
/**
* Returns caching type (none, memory, temporary)
*
* @param int $caching_type
* @return bool
* @access public
*/
public function isCachingType($caching_type)
{
return $this->cacheManager->isCachingType($caching_type);
}
/**
* Increments serial based on prefix and it's ID (optional)
*
* @param string $prefix
* @param int $id ID (value of IDField) or ForeignKeyField:ID
* @param bool $increment
* @return string
* @access public
*/
public function incrementCacheSerial($prefix, $id = null, $increment = true)
{
return $this->cacheManager->incrementCacheSerial($prefix, $id, $increment);
}
/**
* Returns cached $key value from cache named $cache_name
*
* @param int $key key name from cache
* @param bool $store_locally store data locally after retrieved
* @param int $max_rebuild_seconds
* @return mixed
* @access public
*/
public function getCache($key, $store_locally = true, $max_rebuild_seconds = 0)
{
return $this->cacheManager->getCache($key, $store_locally, $max_rebuild_seconds);
}
/**
* Stores new $value in cache with $key name
*
* @param int $key key name to add to cache
* @param mixed $value value of cached record
* @param int $expiration when value expires (0 - doesn't expire)
* @return bool
* @access public
*/
public function setCache($key, $value, $expiration = 0)
{
return $this->cacheManager->setCache($key, $value, $expiration);
}
/**
* Stores new $value in cache with $key name (only if it's not there)
*
* @param int $key key name to add to cache
* @param mixed $value value of cached record
* @param int $expiration when value expires (0 - doesn't expire)
* @return bool
* @access public
*/
public function addCache($key, $value, $expiration = 0)
{
return $this->cacheManager->addCache($key, $value, $expiration);
}
/**
* Sets rebuilding mode for given cache
*
* @param string $name
* @param int $mode
* @param int $max_rebuilding_time
* @return bool
* @access public
*/
public function rebuildCache($name, $mode = null, $max_rebuilding_time = 0)
{
return $this->cacheManager->rebuildCache($name, $mode, $max_rebuilding_time);
}
/**
* Deletes key from cache
*
* @param string $key
* @return void
* @access public
*/
public function deleteCache($key)
{
$this->cacheManager->deleteCache($key);
}
/**
* Reset's all memory cache at once
*
* @return void
* @access public
*/
public function resetCache()
{
$this->cacheManager->resetCache();
}
/**
* Returns value from database cache
*
* @param string $name key name
* @param int $max_rebuild_seconds
* @return mixed
* @access public
*/
public function getDBCache($name, $max_rebuild_seconds = 0)
{
return $this->cacheManager->getDBCache($name, $max_rebuild_seconds);
}
/**
* Sets value to database cache
*
* @param string $name
* @param mixed $value
* @param int|bool $expiration
* @return void
* @access public
*/
public function setDBCache($name, $value, $expiration = false)
{
$this->cacheManager->setDBCache($name, $value, $expiration);
}
/**
* Sets rebuilding mode for given cache
*
* @param string $name
* @param int $mode
* @param int $max_rebuilding_time
* @return bool
* @access public
*/
public function rebuildDBCache($name, $mode = null, $max_rebuilding_time = 0)
{
return $this->cacheManager->rebuildDBCache($name, $mode, $max_rebuilding_time);
}
/**
* Deletes key from database cache
*
* @param string $name
* @return void
* @access public
*/
public function deleteDBCache($name)
{
$this->cacheManager->deleteDBCache($name);
}
/**
* Registers each module specific constants if any found
*
* @return bool
* @access protected
*/
protected function registerModuleConstants()
{
if ( file_exists(KERNEL_PATH . '/constants.php') ) {
kUtil::includeOnce(KERNEL_PATH . '/constants.php');
}
if ( !$this->ModuleInfo ) {
return false;
}
foreach ($this->ModuleInfo as $module_info) {
$constants_file = FULL_PATH . '/' . $module_info['Path'] . 'constants.php';
if ( file_exists($constants_file) ) {
kUtil::includeOnce($constants_file);
}
}
return true;
}
/**
* Performs redirect to hard maintenance template
*
* @return void
* @access public
*/
public function redirectToMaintenance()
{
$maintenance_page = WRITEBALE_BASE . '/maintenance.html';
$query_string = ''; // $this->isAdmin ? '' : '?next_template=' . urlencode($_SERVER['REQUEST_URI']);
if ( file_exists(FULL_PATH . $maintenance_page) ) {
header('Location: ' . BASE_PATH . $maintenance_page . $query_string);
exit;
}
}
/**
* Actually runs the parser against current template and stores parsing result
*
* This method gets 't' variable passed to the script, loads the template given in 't' variable and
* parses it. The result is store in {@link $this->HTML} property.
*
* @return void
* @access public
*/
public function Run()
{
// process maintenance mode redirect: begin
$maintenance_mode = $this->getMaintenanceMode();
if ( $maintenance_mode == MaintenanceMode::HARD ) {
$this->redirectToMaintenance();
}
elseif ( $maintenance_mode == MaintenanceMode::SOFT ) {
$maintenance_template = $this->isAdmin ? 'login' : $this->ConfigValue('SoftMaintenanceTemplate');
if ( $this->GetVar('t') != $maintenance_template ) {
$redirect_params = Array ();
if ( !$this->isAdmin ) {
$redirect_params['next_template'] = urlencode($_SERVER['REQUEST_URI']);
}
$this->Redirect($maintenance_template, $redirect_params);
}
}
// process maintenance mode redirect: end
if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
$this->Debugger->appendMemoryUsage('Application before Run:');
}
if ( $this->isAdminUser ) {
// for permission checking in events & templates
$this->LinkVar('module'); // for common configuration templates
$this->LinkVar('module_key'); // for common search templates
$this->LinkVar('section'); // for common configuration templates
if ( $this->GetVar('m_opener') == 'p' ) {
$this->LinkVar('main_prefix'); // window prefix, that opened selector
$this->LinkVar('dst_field'); // field to set value choosed in selector
}
if ( $this->GetVar('ajax') == 'yes' && !$this->GetVar('debug_ajax') ) {
// hide debug output from ajax requests automatically
kUtil::safeDefine('DBG_SKIP_REPORTING', 1); // safeDefine, because debugger also defines it
}
}
elseif ( $this->GetVar('admin') ) {
$admin_session = $this->recallObject('Session.admin');
/* @var $admin_session Session */
// store Admin Console User's ID to Front-End's session for cross-session permission checks
$this->StoreVar('admin_user_id', (int)$admin_session->RecallVar('user_id'));
if ( $this->CheckAdminPermission('CATEGORY.MODIFY', 0, $this->getBaseCategory()) ) {
// user can edit cms blocks (when viewing front-end through admin's frame)
$editing_mode = $this->GetVar('editing_mode');
define('EDITING_MODE', $editing_mode ? $editing_mode : EDITING_MODE_BROWSE);
}
}
kUtil::safeDefine('EDITING_MODE', ''); // user can't edit anything
$this->Phrases->setPhraseEditing();
$this->EventManager->ProcessRequest();
$this->InitParser();
$t = $this->GetVar('render_template', $this->GetVar('t'));
if ( !$this->TemplatesCache->TemplateExists($t) && !$this->isAdmin ) {
$cms_handler = $this->recallObject('st_EventHandler');
/* @var $cms_handler CategoriesEventHandler */
$t = ltrim($cms_handler->GetDesignTemplate(), '/');
if ( defined('DEBUG_MODE') && $this->isDebugMode() ) {
$this->Debugger->appendHTML('<strong>Design Template</strong>: ' . $t . '; <strong>CategoryID</strong>: ' . $this->GetVar('m_cat_id'));
}
}
/*else {
$cms_handler->SetCatByTemplate();
}*/
if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
$this->Debugger->appendMemoryUsage('Application before Parsing:');
}
$this->HTML = $this->Parser->Run($t);
if ( defined('DEBUG_MODE') && $this->isDebugMode() && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
$this->Debugger->appendMemoryUsage('Application after Parsing:');
}
}
/**
* Only renders template
*
* @see kDBEventHandler::_errorNotFound()
*/
public function QuickRun()
{
$this->InitParser();
$this->HTML = $this->ParseBlock(Array ('name' => $this->GetVar('t')));
}
/**
* Performs template parser/cache initialization
*
* @param bool|string $theme_name
* @return void
* @access public
*/
public function InitParser($theme_name = false)
{
if ( !is_object($this->Parser) ) {
$this->Parser = $this->recallObject('NParser');
$this->TemplatesCache = $this->recallObject('TemplatesCache');
}
$this->TemplatesCache->forceThemeName = $theme_name;
}
/**
* Send the parser results to browser
*
* Actually send everything stored in {@link $this->HTML}, to the browser by echoing it.
*
* @return void
* @access public
*/
public function Done()
{
$this->HandleEvent(new kEvent('adm:OnBeforeShutdown'));
$debug_mode = defined('DEBUG_MODE') && $this->isDebugMode();
if ( $debug_mode && kUtil::constOn('DBG_PROFILE_MEMORY') ) {
$this->Debugger->appendMemoryUsage('Application before Done:');
}
if ( $debug_mode ) {
$this->EventManager->runScheduledTasks();
$this->Session->SaveData();
$this->HTML = ob_get_clean() . $this->HTML . $this->Debugger->printReport(true);
}
else {
// send "Set-Cookie" header before any output is made
$this->Session->SetSession();
$this->HTML = ob_get_clean() . $this->HTML;
}
$this->setContentType();
if ( $this->UseOutputCompression() ) {
$compression_level = $this->ConfigValue('OutputCompressionLevel');
if ( !$compression_level || $compression_level < 0 || $compression_level > 9 ) {
$compression_level = 7;
}
header('Content-Encoding: gzip');
echo gzencode($this->HTML, $compression_level);
}
else {
echo $this->HTML;
}
$this->cacheManager->UpdateApplicationCache();
flush();
if ( !$debug_mode ) {
$this->EventManager->runScheduledTasks();
$this->Session->SaveData();
}
if ( defined('DBG_CAPTURE_STATISTICS') && DBG_CAPTURE_STATISTICS && !$this->isAdmin ) {
$this->_storeStatistics();
}
}
/**
* Stores script execution statistics to database
*
* @return void
* @access protected
*/
protected function _storeStatistics()
{
global $start;
$script_time = microtime(true) - $start;
$query_statistics = $this->Conn->getQueryStatistics(); // time & count
$sql = 'SELECT *
FROM ' . TABLE_PREFIX . 'StatisticsCapture
WHERE TemplateName = ' . $this->Conn->qstr($this->GetVar('t'));
$data = $this->Conn->GetRow($sql);
if ( $data ) {
$this->_updateAverageStatistics($data, 'ScriptTime', $script_time);
$this->_updateAverageStatistics($data, 'SqlTime', $query_statistics['time']);
$this->_updateAverageStatistics($data, 'SqlCount', $query_statistics['count']);
$data['Hits']++;
$data['LastHit'] = adodb_mktime();
$this->Conn->doUpdate($data, TABLE_PREFIX . 'StatisticsCapture', 'StatisticsId = ' . $data['StatisticsId']);
}
else {
$data['ScriptTimeMin'] = $data['ScriptTimeAvg'] = $data['ScriptTimeMax'] = $script_time;
$data['SqlTimeMin'] = $data['SqlTimeAvg'] = $data['SqlTimeMax'] = $query_statistics['time'];
$data['SqlCountMin'] = $data['SqlCountAvg'] = $data['SqlCountMax'] = $query_statistics['count'];
$data['TemplateName'] = $this->GetVar('t');
$data['Hits'] = 1;
$data['LastHit'] = adodb_mktime();
$this->Conn->doInsert($data, TABLE_PREFIX . 'StatisticsCapture');
}
}
/**
* Calculates average time for statistics
*
* @param Array $data
* @param string $field_prefix
* @param float $current_value
* @return void
* @access protected
*/
protected function _updateAverageStatistics(&$data, $field_prefix, $current_value)
{
$data[$field_prefix . 'Avg'] = (($data['Hits'] * $data[$field_prefix . 'Avg']) + $current_value) / ($data['Hits'] + 1);
if ( $current_value < $data[$field_prefix . 'Min'] ) {
$data[$field_prefix . 'Min'] = $current_value;
}
if ( $current_value > $data[$field_prefix . 'Max'] ) {
$data[$field_prefix . 'Max'] = $current_value;
}
}
/**
* Remembers slow query SQL and execution time into log
*
* @param string $slow_sql
* @param int $time
* @return void
* @access public
*/
public function logSlowQuery($slow_sql, $time)
{
$query_crc = crc32($slow_sql);
$sql = 'SELECT *
FROM ' . TABLE_PREFIX . 'SlowSqlCapture
WHERE QueryCrc = ' . $query_crc;
$data = $this->Conn->Query($sql, null, true);
if ( $data ) {
$this->_updateAverageStatistics($data, 'Time', $time);
$template_names = explode(',', $data['TemplateNames']);
array_push($template_names, $this->GetVar('t'));
$data['TemplateNames'] = implode(',', array_unique($template_names));
$data['Hits']++;
$data['LastHit'] = adodb_mktime();
$this->Conn->doUpdate($data, TABLE_PREFIX . 'SlowSqlCapture', 'CaptureId = ' . $data['CaptureId']);
}
else {
$data['TimeMin'] = $data['TimeAvg'] = $data['TimeMax'] = $time;
$data['SqlQuery'] = $slow_sql;
$data['QueryCrc'] = $query_crc;
$data['TemplateNames'] = $this->GetVar('t');
$data['Hits'] = 1;
$data['LastHit'] = adodb_mktime();
$this->Conn->doInsert($data, TABLE_PREFIX . 'SlowSqlCapture');
}
}
/**
* Checks if output compression options is available
*
* @return bool
* @access protected
*/
protected function UseOutputCompression()
{
if ( kUtil::constOn('IS_INSTALL') || kUtil::constOn('DBG_ZEND_PRESENT') || kUtil::constOn('SKIP_OUT_COMPRESSION') ) {
return false;
}
return $this->ConfigValue('UseOutputCompression') && function_exists('gzencode') && strstr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
}
// Facade
/**
* Returns current session id (SID)
*
* @return int
* @access public
*/
public function GetSID()
{
$session = $this->recallObject('Session');
/* @var $session Session */
return $session->GetID();
}
/**
* Destroys current session
*
* @return void
* @access public
* @see UserHelper::logoutUser()
*/
public function DestroySession()
{
$session = $this->recallObject('Session');
/* @var $session Session */
$session->Destroy();
}
/**
* Returns variable passed to the script as GET/POST/COOKIE
*
* @param string $name Name of variable to retrieve
* @param mixed $default default value returned in case if variable not present
* @return mixed
* @access public
*/
public function GetVar($name, $default = false)
{
return isset($this->HttpQuery->_Params[$name]) ? $this->HttpQuery->_Params[$name] : $default;
}
/**
* Returns variable passed to the script as $type
*
* @param string $name Name of variable to retrieve
* @param string $type Get/Post/Cookie
* @param mixed $default default value returned in case if variable not present
* @return mixed
* @access public
*/
public function GetVarDirect($name, $type, $default = false)
{
// $type = ucfirst($type);
$array = $this->HttpQuery->$type;
return isset($array[$name]) ? $array[$name] : $default;
}
/**
* Returns ALL variables passed to the script as GET/POST/COOKIE
*
* @return Array
* @access public
* @deprecated
*/
public function GetVars()
{
return $this->HttpQuery->GetParams();
}
/**
* Set the variable 'as it was passed to the script through GET/POST/COOKIE'
*
* This could be useful to set the variable when you know that
* other objects would relay on variable passed from GET/POST/COOKIE
* or you could use SetVar() / GetVar() pairs to pass the values between different objects.<br>
*
* @param string $var Variable name to set
* @param mixed $val Variable value
* @return void
* @access public
*/
public function SetVar($var,$val)
{
$this->HttpQuery->Set($var, $val);
}
/**
* Deletes kHTTPQuery variable
*
* @param string $var
* @return void
* @todo Think about method name
*/
public function DeleteVar($var)
{
$this->HttpQuery->Remove($var);
}
/**
* Deletes Session variable
*
* @param string $var
* @return void
* @access public
*/
public function RemoveVar($var)
{
$this->Session->RemoveVar($var);
}
/**
* Removes variable from persistent session
*
* @param string $var
* @return void
* @access public
*/
public function RemovePersistentVar($var)
{
$this->Session->RemovePersistentVar($var);
}
/**
* Restores Session variable to it's db version
*
* @param string $var
* @return void
* @access public
*/
public function RestoreVar($var)
{
$this->Session->RestoreVar($var);
}
/**
* Returns session variable value
*
* Return value of $var variable stored in Session. An optional default value could be passed as second parameter.
*
* @param string $var Variable name
* @param mixed $default Default value to return if no $var variable found in session
* @return mixed
* @access public
* @see Session::RecallVar()
*/
public function RecallVar($var,$default=false)
{
return $this->Session->RecallVar($var,$default);
}
/**
* Returns variable value from persistent session
*
* @param string $var
* @param mixed $default
* @return mixed
* @access public
* @see Session::RecallPersistentVar()
*/
public function RecallPersistentVar($var, $default = false)
{
return $this->Session->RecallPersistentVar($var, $default);
}
/**
* Stores variable $val in session under name $var
*
* Use this method to store variable in session. Later this variable could be recalled.
*
* @param string $var Variable name
* @param mixed $val Variable value
* @param bool $optional
* @return void
* @access public
* @see kApplication::RecallVar()
*/
public function StoreVar($var, $val, $optional = false)
{
$session = $this->recallObject('Session');
/* @var $session Session */
$this->Session->StoreVar($var, $val, $optional);
}
/**
* Stores variable to persistent session
*
* @param string $var
* @param mixed $val
* @param bool $optional
* @return void
* @access public
*/
public function StorePersistentVar($var, $val, $optional = false)
{
$this->Session->StorePersistentVar($var, $val, $optional);
}
/**
* Stores default value for session variable
*
* @param string $var
* @param string $val
* @param bool $optional
* @return void
* @access public
* @see Session::RecallVar()
* @see Session::StoreVar()
*/
public function StoreVarDefault($var, $val, $optional = false)
{
$session = $this->recallObject('Session');
/* @var $session Session */
$this->Session->StoreVarDefault($var, $val, $optional);
}
/**
* Links HTTP Query variable with session variable
*
* If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session.
* This method could be used for making sure that GetVar will return query or session value for given
* variable, when query variable should overwrite session (and be stored there for later use).<br>
* This could be used for passing item's ID into popup with multiple tab -
* in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id').
* After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session
*
* @param string $var HTTP Query (GPC) variable name
* @param mixed $ses_var Session variable name
* @param mixed $default Default variable value
* @param bool $optional
* @return void
* @access public
*/
public function LinkVar($var, $ses_var = null, $default = '', $optional = false)
{
if ( !isset($ses_var) ) {
$ses_var = $var;
}
if ( $this->GetVar($var) !== false ) {
$this->StoreVar($ses_var, $this->GetVar($var), $optional);
}
else {
$this->SetVar($var, $this->RecallVar($ses_var, $default));
}
}
/**
* Returns variable from HTTP Query, or from session if not passed in HTTP Query
*
* The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed.
* Returns the default value if variable does not exist in session and was not passed in HTTP Query
*
* @param string $var HTTP Query (GPC) variable name
* @param mixed $ses_var Session variable name
* @param mixed $default Default variable value
* @return mixed
* @access public
* @see LinkVar
*/
public function GetLinkedVar($var, $ses_var = null, $default = '')
{
$this->LinkVar($var, $ses_var, $default);
return $this->GetVar($var);
}
/**
* Renders given tag and returns it's output
*
* @param string $prefix
* @param string $tag
* @param Array $params
* @return mixed
* @access public
* @see kApplication::InitParser()
*/
public function ProcessParsedTag($prefix, $tag, $params)
{
$processor = $this->Parser->GetProcessor($prefix);
/* @var $processor kDBTagProcessor */
return $processor->ProcessParsedTag($tag, $params, $prefix);
}
/**
* Return ADODB Connection object
*
* Returns ADODB Connection object already connected to the project database, configurable in config.php
*
* @return kDBConnection
* @access public
*/
public function &GetADODBConnection()
{
return $this->Conn;
}
/**
* Allows to parse given block name or include template
*
* @param Array $params Parameters to pass to block. Reserved parameter "name" used to specify block name.
* @param bool $pass_params Forces to pass current parser params to this block/template. Use with caution, because you can accidentally pass "block_no_data" parameter.
* @param bool $as_template
* @return string
* @access public
*/
public function ParseBlock($params, $pass_params = false, $as_template = false)
{
if ( substr($params['name'], 0, 5) == 'html:' ) {
return substr($params['name'], 5);
}
return $this->Parser->ParseBlock($params, $pass_params, $as_template);
}
/**
* Checks, that we have given block defined
*
* @param string $name
* @return bool
* @access public
*/
public function ParserBlockFound($name)
{
return $this->Parser->blockFound($name);
}
/**
* Allows to include template with a given name and given parameters
*
* @param Array $params Parameters to pass to template. Reserved parameter "name" used to specify template name.
* @return string
* @access public
*/
public function IncludeTemplate($params)
{
return $this->Parser->IncludeTemplate($params, isset($params['is_silent']) ? 1 : 0);
}
/**
* Return href for template
*
* @param string $t Template path
* @param string $prefix index.php prefix - could be blank, 'admin'
* @param Array $params
* @param string $index_file
* @return string
*/
public function HREF($t, $prefix = '', $params = Array (), $index_file = null)
{
return $this->UrlManager->HREF($t, $prefix, $params, $index_file);
}
/**
* Returns theme template filename and it's corresponding page_id based on given seo template
*
* @param string $seo_template
* @return string
* @access public
*/
public function getPhysicalTemplate($seo_template)
{
return $this->UrlManager->getPhysicalTemplate($seo_template);
}
/**
* Returns template name, that corresponds with given virtual (not physical) page id
*
* @param int $page_id
* @return string|bool
* @access public
*/
public function getVirtualPageTemplate($page_id)
{
return $this->UrlManager->getVirtualPageTemplate($page_id);
}
/**
* Returns variables with values that should be passed through with this link + variable list
*
* @param Array $params
* @return Array
* @access public
*/
public function getPassThroughVariables(&$params)
{
return $this->UrlManager->getPassThroughVariables($params);
}
/**
* Builds url
*
* @param string $t
* @param Array $params
* @param string $pass
* @param bool $pass_events
* @param bool $env_var
* @return string
* @access public
*/
public function BuildEnv($t, $params, $pass = 'all', $pass_events = false, $env_var = true)
{
return $this->UrlManager->plain->build($t, $params, $pass, $pass_events, $env_var);
}
/**
* Process QueryString only, create
* events, ids, based on config
* set template name and sid in
* desired application variables.
*
* @param string $env_var environment string value
* @param string $pass_name
* @return Array
* @access public
*/
public function processQueryString($env_var, $pass_name = 'passed')
{
return $this->UrlManager->plain->parse($env_var, $pass_name);
}
/**
* Returns base part of all urls, build on website
*
* @param string $prefix
* @param bool $ssl
* @param bool $add_port
* @return string
* @access public
*/
public function BaseURL($prefix = '', $ssl = null, $add_port = true)
{
if ( $ssl === null ) {
// stay on same encryption level
return PROTOCOL . SERVER_NAME . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/';
}
if ( $ssl ) {
// going from http:// to https://
$base_url = $this->isAdmin ? $this->ConfigValue('AdminSSL_URL') : false;
if ( !$base_url ) {
$ssl_url = $this->siteDomainField('SSLUrl');
$base_url = $ssl_url !== false ? $ssl_url : $this->ConfigValue('SSL_URL');
}
return rtrim($base_url, '/') . $prefix . '/';
}
// going from https:// to http://
$domain = $this->siteDomainField('DomainName');
if ( $domain === false ) {
$domain = DOMAIN;
}
return 'http://' . $domain . ($add_port && defined('PORT') ? ':' . PORT : '') . BASE_PATH . $prefix . '/';
}
/**
* Redirects user to url, that's build based on given parameters
*
* @param string $t
* @param Array $params
* @param string $prefix
* @param string $index_file
* @return void
* @access public
*/
public function Redirect($t = '', $params = Array(), $prefix = '', $index_file = null)
{
$js_redirect = getArrayValue($params, 'js_redirect');
if ( $t == '' || $t === true ) {
$t = $this->GetVar('t');
}
// pass prefixes and special from previous url
if ( array_key_exists('js_redirect', $params) ) {
unset($params['js_redirect']);
}
// allows to send custom responce code along with redirect header
if ( array_key_exists('response_code', $params) ) {
$response_code = (int)$params['response_code'];
unset($params['response_code']);
}
else {
$response_code = 302; // Found
}
if ( !array_key_exists('pass', $params) ) {
$params['pass'] = 'all';
}
if ( $this->GetVar('ajax') == 'yes' && $t == $this->GetVar('t') ) {
// redirects to the same template as current
$params['ajax'] = 'yes';
}
$params['__URLENCODE__'] = 1;
$location = $this->HREF($t, $prefix, $params, $index_file);
if ( $this->isDebugMode() && (kUtil::constOn('DBG_REDIRECT') || (kUtil::constOn('DBG_RAISE_ON_WARNINGS') && $this->Debugger->WarningCount)) ) {
$this->Debugger->appendTrace();
echo '<strong>Debug output above !!!</strong><br/>' . "\n";
if ( array_key_exists('HTTP_REFERER', $_SERVER) ) {
echo 'Referer: <strong>' . $_SERVER['HTTP_REFERER'] . '</strong><br/>' . "\n";
}
echo "Proceed to redirect: <a href=\"{$location}\">{$location}</a><br/>\n";
}
else {
if ( $js_redirect ) {
// show "redirect" template instead of redirecting,
// because "Set-Cookie" header won't work, when "Location"
// header is used later
$this->SetVar('t', 'redirect');
$this->SetVar('redirect_to', $location);
// make all additional parameters available on "redirect" template too
foreach ($params as $name => $value) {
$this->SetVar($name, $value);
}
return;
}
else {
if ( $this->GetVar('ajax') == 'yes' && $t != $this->GetVar('t') ) {
// redirection to other then current template during ajax request
kUtil::safeDefine('DBG_SKIP_REPORTING', 1);
echo '#redirect#' . $location;
}
elseif ( headers_sent() != '' ) {
// some output occurred -> redirect using javascript
echo '<script type="text/javascript">window.location.href = \'' . $location . '\';</script>';
}
else {
// no output before -> redirect using HTTP header
// header('HTTP/1.1 302 Found');
header('Location: ' . $location, true, $response_code);
}
}
}
// session expiration is called from session initialization,
// that's why $this->Session may be not defined here
$session = $this->recallObject('Session');
/* @var $session Session */
$this->HandleEvent(new kEvent('adm:OnBeforeShutdown'));
$session->SaveData();
ob_end_flush();
exit;
}
/**
* Returns translation of given label
*
* @param string $label
* @param bool $allow_editing return translation link, when translation is missing on current language
* @param bool $use_admin use current Admin Console language to translate phrase
* @return string
* @access public
*/
public function Phrase($label, $allow_editing = true, $use_admin = false)
{
return $this->Phrases->GetPhrase($label, $allow_editing, $use_admin);
}
/**
* Replace language tags in exclamation marks found in text
*
* @param string $text
* @param bool $force_escape force escaping, not escaping of resulting string
* @return string
* @access public
*/
public function ReplaceLanguageTags($text, $force_escape = null)
{
return $this->Phrases->ReplaceLanguageTags($text, $force_escape);
}
/**
* Checks if user is logged in, and creates
* user object if so. User object can be recalled
* later using "u.current" prefix_special. Also you may
* get user id by getting "u.current_id" variable.
*
* @return void
* @access protected
*/
protected function ValidateLogin()
{
$session = $this->recallObject('Session');
/* @var $session Session */
$user_id = $session->GetField('PortalUserId');
if ( !$user_id && $user_id != USER_ROOT ) {
$user_id = USER_GUEST;
}
$this->SetVar('u.current_id', $user_id);
if ( !$this->isAdmin ) {
// needed for "profile edit", "registration" forms ON FRONT ONLY
$this->SetVar('u_id', $user_id);
}
$this->StoreVar('user_id', $user_id, $user_id == USER_GUEST); // storing Guest user_id (-2) is optional
$this->isAdminUser = $this->isAdmin && $this->LoggedIn();
if ( $this->GetVar('expired') == 1 ) {
// this parameter is set only from admin
$user = $this->recallObject('u.login-admin', null, Array ('form_name' => 'login'));
/* @var $user UsersItem */
$user->SetError('UserLogin', 'session_expired', 'la_text_sess_expired');
}
if ( ($user_id != USER_GUEST) && defined('DBG_REQUREST_LOG') && DBG_REQUREST_LOG ) {
$this->HttpQuery->writeRequestLog(DBG_REQUREST_LOG);
}
if ( $user_id != USER_GUEST ) {
// normal users + root
$this->LoadPersistentVars();
}
$user_timezone = $this->Session->GetField('TimeZone');
if ( $user_timezone ) {
putenv('TZ=' . $user_timezone);
}
}
/**
* Loads current user persistent session data
*
* @return void
* @access public
*/
public function LoadPersistentVars()
{
$this->Session->LoadPersistentVars();
}
/**
* Returns configuration option value by name
*
* @param string $name
* @return string
* @access public
*/
public function ConfigValue($name)
{
return $this->cacheManager->ConfigValue($name);
}
/**
* Changes value of individual configuration variable (+resets cache, when needed)
*
* @param string $name
* @param string $value
* @param bool $local_cache_only
* @return string
* @access public
*/
public function SetConfigValue($name, $value, $local_cache_only = false)
{
return $this->cacheManager->SetConfigValue($name, $value, $local_cache_only);
}
/**
* Allows to process any type of event
*
* @param kEvent $event
* @param Array $params
* @param Array $specific_params
* @access public
*/
public function HandleEvent($event, $params = null, $specific_params = null)
{
if ( isset($params) ) {
$event = new kEvent($params, $specific_params);
}
$this->EventManager->HandleEvent($event);
}
/**
* Registers new class in the factory
*
* @param string $real_class Real name of class as in class declaration
* @param string $file Filename in what $real_class is declared
* @param string $pseudo_class Name under this class object will be accessed using getObject method
* @return void
* @access public
*/
public function registerClass($real_class, $file, $pseudo_class = null)
{
$this->Factory->registerClass($real_class, $file, $pseudo_class);
}
/**
* Unregisters existing class from factory
*
* @param string $real_class Real name of class as in class declaration
* @param string $pseudo_class Name under this class object is accessed using getObject method
* @return void
* @access public
*/
public function unregisterClass($real_class, $pseudo_class = null)
{
$this->Factory->unregisterClass($real_class, $pseudo_class);
}
/**
* Add new scheduled task
*
* @param string $short_name name to be used to store last maintenance run info
* @param string $event_string
* @param int $run_interval run interval in seconds
* @param int $status
* @access public
*/
public function registerScheduledTask($short_name, $event_string, $run_interval, $status = STATUS_ACTIVE)
{
$this->EventManager->registerScheduledTask($short_name, $event_string, $run_interval, $status);
}
/**
* Registers Hook from subprefix event to master prefix event
*
* Pattern: Observer
*
* @param string $hook_event
* @param string $do_event
* @param int $mode
* @param bool $conditional
* @access public
*/
public function registerHook($hook_event, $do_event, $mode = hAFTER, $conditional = false)
{
$this->EventManager->registerHook($hook_event, $do_event, $mode, $conditional);
}
/**
* Registers build event for given pseudo class
*
* @param string $pseudo_class
* @param string $event_name
* @access public
*/
public function registerBuildEvent($pseudo_class, $event_name)
{
$this->EventManager->registerBuildEvent($pseudo_class, $event_name);
}
/**
* Allows one TagProcessor tag act as other TagProcessor tag
*
* @param Array $tag_info
* @return void
* @access public
*/
public function registerAggregateTag($tag_info)
{
$aggregator = $this->recallObject('TagsAggregator', 'kArray');
/* @var $aggregator kArray */
$tag_data = Array (
$tag_info['LocalPrefix'],
$tag_info['LocalTagName'],
getArrayValue($tag_info, 'LocalSpecial')
);
$aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], $tag_data);
}
/**
* Returns object using params specified, creates it if is required
*
* @param string $name
* @param string $pseudo_class
* @param Array $event_params
* @param Array $arguments
* @return kBase
*/
public function recallObject($name, $pseudo_class = null, $event_params = Array(), $arguments = Array ())
{
/*if ( !$this->hasObject($name) && $this->isDebugMode() && ($name == '_prefix_here_') ) {
// first time, when object with "_prefix_here_" prefix is accessed
$this->Debugger->appendTrace();
}*/
return $this->Factory->getObject($name, $pseudo_class, $event_params, $arguments);
}
/**
* Returns tag processor for prefix specified
*
* @param string $prefix
* @return kDBTagProcessor
* @access public
*/
public function recallTagProcessor($prefix)
{
$this->InitParser(); // because kDBTagProcesor is in NParser dependencies
return $this->recallObject($prefix . '_TagProcessor');
}
/**
* Checks if object with prefix passes was already created in factory
*
* @param string $name object pseudo_class, prefix
* @return bool
* @access public
*/
public function hasObject($name)
{
return $this->Factory->hasObject($name);
}
/**
* Removes object from storage by given name
*
* @param string $name Object's name in the Storage
* @return void
* @access public
*/
public function removeObject($name)
{
$this->Factory->DestroyObject($name);
}
/**
* Get's real class name for pseudo class, includes class file and creates class instance
*
* Pattern: Factory Method
*
* @param string $pseudo_class
* @param Array $arguments
* @return kBase
* @access public
*/
public function makeClass($pseudo_class, $arguments = Array ())
{
return $this->Factory->makeClass($pseudo_class, $arguments);
}
/**
* Checks if application is in debug mode
*
* @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant
* @return bool
* @author Alex
* @access public
*/
public function isDebugMode($check_debugger = true)
{
$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
if ($check_debugger) {
$debug_mode = $debug_mode && is_object($this->Debugger);
}
return $debug_mode;
}
/**
* Apply url rewriting used by mod_rewrite or not
*
* @param bool|null $ssl Force ssl link to be build
* @return bool
* @access public
*/
public function RewriteURLs($ssl = false)
{
// case #1,#4:
// we want to create https link from http mode
// we want to create https link from https mode
// conditions: ($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')
// case #2,#3:
// we want to create http link from https mode
// we want to create http link from http mode
// conditions: !$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')
$allow_rewriting =
(!$ssl && (PROTOCOL == 'https://' || PROTOCOL == 'http://')) // always allow mod_rewrite for http
|| // or allow rewriting for redirect TO httpS or when already in httpS
(($ssl || PROTOCOL == 'https://') && $this->ConfigValue('UseModRewriteWithSSL')); // but only if it's allowed in config!
return kUtil::constOn('MOD_REWRITE') && $allow_rewriting;
}
/**
* Reads unit (specified by $prefix)
* option specified by $option
*
* @param string $prefix
* @param string $option
* @param mixed $default
* @return string
* @access public
*/
public function getUnitOption($prefix, $option, $default = false)
{
return $this->UnitConfigReader->getUnitOption($prefix, $option, $default);
}
/**
* Set's new unit option value
*
* @param string $prefix
* @param string $option
* @param string $value
* @access public
*/
public function setUnitOption($prefix, $option, $value)
{
$this->UnitConfigReader->setUnitOption($prefix,$option,$value);
}
/**
* Read all unit with $prefix options
*
* @param string $prefix
* @return Array
* @access public
*/
public function getUnitOptions($prefix)
{
return $this->UnitConfigReader->getUnitOptions($prefix);
}
/**
* Returns true if config exists and is allowed for reading
*
* @param string $prefix
* @return bool
*/
public function prefixRegistred($prefix)
{
return $this->UnitConfigReader->prefixRegistred($prefix);
}
/**
* Splits any mixing of prefix and
* special into correct ones
*
* @param string $prefix_special
* @return Array
* @access public
*/
public function processPrefix($prefix_special)
{
return $this->Factory->processPrefix($prefix_special);
}
/**
* Set's new event for $prefix_special
* passed
*
* @param string $prefix_special
* @param string $event_name
* @return void
* @access public
*/
public function setEvent($prefix_special, $event_name)
{
$this->EventManager->setEvent($prefix_special, $event_name);
}
/**
* SQL Error Handler
*
* @param int $code
* @param string $msg
* @param string $sql
* @return bool
* @access public
*/
public function handleSQLError($code, $msg, $sql)
{
if ( isset($this->Debugger) ) {
$long_error_msg = '<span class="debug_error">' . $msg . ' (' . $code . ')</span><br/><a href="javascript:$Debugger.SetClipboard(\'' . htmlspecialchars($sql) . '\');"><strong>SQL</strong></a>: ' . $this->Debugger->formatSQL($sql);
$long_id = $this->Debugger->mapLongError($long_error_msg);
$error_msg = mb_substr($msg . ' (' . $code . ') [' . $sql . ']', 0, 1000) . ' #' . $long_id;
if ( kUtil::constOn('DBG_SQL_FAILURE') && !defined('IS_INSTALL') ) {
throw new Exception($error_msg);
}
else {
$this->Debugger->appendTrace();
}
}
else {
// when not debug mode, then fatal database query won't break anything
$error_msg = '<strong>SQL Error</strong> in sql: ' . $sql . ', code <strong>' . $code . '</strong> (' . $msg . ')';
}
trigger_error($error_msg, E_USER_WARNING);
return true;
}
/**
* Default error handler
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @param Array $errcontext
* @return bool
* @access public
*/
public function handleError($errno, $errstr, $errfile = null, $errline = null, $errcontext = Array ())
{
$this->errorLogSilent($errno, $errstr, $errfile, $errline);
$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
$skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING;
if ( !$this->errorHandlers || ($debug_mode && $skip_reporting) ) {
// when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request)
if ( $errno == E_USER_ERROR ) {
$this->errorDisplayFatal('<strong>Fatal Error: </strong>' . "{$errstr} in {$errfile} on line {$errline}");
}
if ( !$this->errorHandlers ) {
return true;
}
}
$res = false;
/* @var $handler Closure */
foreach ($this->errorHandlers as $handler) {
if ( is_array($handler) ) {
$object =& $handler[0];
$method = $handler[1];
$res = $object->$method($errno, $errstr, $errfile, $errline, $errcontext);
}
else {
$res = $handler($errno, $errstr, $errfile, $errline, $errcontext);
}
}
return $res;
}
/**
* Handles exception
*
* @param Exception $exception
* @return bool
* @access public
*/
public function handleException($exception)
{
// transform exception to regular error (no need to rewrite existing error handlers)
$errno = $exception->getCode();
$errstr = $exception->getMessage();
$errfile = $exception->getFile();
$errline = $exception->getLine();
$this->errorLogSilent($errno, $errstr, $errfile, $errline);
$debug_mode = defined('DEBUG_MODE') && DEBUG_MODE;
$skip_reporting = defined('DBG_SKIP_REPORTING') && DBG_SKIP_REPORTING;
if ( !$this->exceptionHandlers || ($debug_mode && $skip_reporting) ) {
// when debugger absent OR it's present, but we actually can't see it's error report (e.g. during ajax request)
$this->errorDisplayFatal('<strong>' . get_class($exception) . ': </strong>' . "{$errstr} in {$errfile} on line {$errline}");
if ( !$this->exceptionHandlers ) {
return true;
}
}
$res = false;
/* @var $handler Closure */
foreach ($this->exceptionHandlers as $handler) {
if ( is_array($handler) ) {
$object =& $handler[0];
$method = $handler[1];
$res = $object->$method($exception);
}
else {
$res = $handler($exception);
}
}
return $res;
}
/**
* Silently saves each given error message to "silent_log.txt" file, when silent log mode is enabled
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
* @return void
* @access protected
*/
protected function errorLogSilent($errno, $errstr = '', $errfile = '', $errline = null)
{
if ( !defined('SILENT_LOG') || !SILENT_LOG ) {
return;
}
if ( !(defined('DBG_IGNORE_STRICT_ERRORS') && DBG_IGNORE_STRICT_ERRORS && defined('E_STRICT') && ($errno == E_STRICT)) ) {
$time = adodb_date('d/m/Y H:i:s');
$fp = fopen((defined('RESTRICTED') ? RESTRICTED : FULL_PATH) . '/silent_log.txt', 'a');
fwrite($fp, '[' . $time . '] #' . $errno . ': ' . strip_tags($errstr) . ' in [' . $errfile . '] on line ' . $errline . "\n");
fclose($fp);
}
}
/**
* Displays div with given error message
*
* @param string $msg
* @return void
* @access protected
*/
protected function errorDisplayFatal($msg)
{
$margin = $this->isAdmin ? '8px' : 'auto';
echo '<div style="background-color: #FEFFBF; margin: ' . $margin . '; padding: 10px; border: 2px solid red; text-align: center">' . $msg . '</div>';
exit;
}
/**
* Prints trace, when debug mode is not available
*
* @param bool $return_result
* @param int $skip_levels
* @return string
* @access public
*/
public function printTrace($return_result = false, $skip_levels = 1)
{
$ret = Array ();
$trace = debug_backtrace(false);
for ($i = 0; $i < $skip_levels; $i++) {
array_shift($trace);
}
foreach ($trace as $level => $trace_info) {
if ( isset($trace_info['class']) ) {
$object = $trace_info['class'];
}
elseif ( isset($trace_info['object']) ) {
$object = get_class($trace_info['object']);
}
else {
$object = '';
}
$args = '';
$type = isset($trace_info['type']) ? $trace_info['type'] : '';
if ( isset($trace_info['args']) ) {
foreach ($trace_info['args'] as $argument) {
if ( is_object($argument) ) {
$args .= get_class($argument) . ' instance, ';
}
else {
$args .= is_array($argument) ? 'Array' : substr($argument, 0, 10) . ' ..., ';
}
}
$args = substr($args, 0, -2);
}
$ret[] = '#' . $level . ' ' . $object . $type . $trace_info['function'] . '(' . $args . ') called at [' . $trace_info['file'] . ':' . $trace_info['line'] . ']';
}
if ( $return_result ) {
return implode("\n", $ret);
}
echo implode("\n", $ret);
return '';
}
/**
* Returns & blocks next ResourceId available in system
*
* @return int
* @access public
*/
public function NextResourceId()
{
$table_name = TABLE_PREFIX . 'IdGenerator';
$this->Conn->Query('LOCK TABLES ' . $table_name . ' WRITE');
$this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1');
$id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name);
if ( $id === false ) {
$this->Conn->Query('INSERT INTO ' . $table_name . ' (lastid) VALUES (2)');
$id = 2;
}
$this->Conn->Query('UNLOCK TABLES');
return $id - 1;
}
/**
* Returns genealogical main prefix for sub-table prefix passes
* OR prefix, that has been found in REQUEST and some how is parent of passed sub-table prefix
*
* @param string $current_prefix
* @param bool $real_top if set to true will return real topmost prefix, regardless of its id is passed or not
* @return string
* @access public
*/
public function GetTopmostPrefix($current_prefix, $real_top = false)
{
// 1. get genealogical tree of $current_prefix
$prefixes = Array ($current_prefix);
while ($parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix')) {
if ( !$this->prefixRegistred($parent_prefix) ) {
// stop searching, when parent prefix is not registered
break;
}
$current_prefix = $parent_prefix;
array_unshift($prefixes, $current_prefix);
}
if ( $real_top ) {
return $current_prefix;
}
// 2. find what if parent is passed
$passed = explode(',', $this->GetVar('all_passed'));
foreach ($prefixes as $a_prefix) {
if ( in_array($a_prefix, $passed) ) {
return $a_prefix;
}
}
return $current_prefix;
}
/**
* Triggers email event of type Admin
*
* @param string $email_event_name
* @param int $to_user_id
* @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
* @return kEvent
* @access public
*/
public function EmailEventAdmin($email_event_name, $to_user_id = null, $send_params = Array ())
{
return $this->_emailEvent($email_event_name, EmailEvent::EVENT_TYPE_ADMIN, $to_user_id, $send_params);
}
/**
* Triggers email event of type User
*
* @param string $email_event_name
* @param int $to_user_id
* @param array $send_params associative array of direct send params, possible keys: to_email, to_name, from_email, from_name, message, message_text
* @return kEvent
* @access public
*/
public function EmailEventUser($email_event_name, $to_user_id = null, $send_params = Array ())
{
return $this->_emailEvent($email_event_name, EmailEvent::EVENT_TYPE_FRONTEND, $to_user_id, $send_params);
}
/**
* Triggers general email event
*
* @param string $email_event_name
* @param int $email_event_type (0 for User, 1 for Admin)
* @param int $to_user_id
* @param array $send_params associative array of direct send params,
* possible keys: to_email, to_name, from_email, from_name, message, message_text
* @return kEvent
* @access protected
*/
protected function _emailEvent($email_event_name, $email_event_type, $to_user_id = null, $send_params = Array ())
{
$email = $this->makeClass('kEmail');
/* @var $email kEmail */
if ( !$email->findEvent($email_event_name, $email_event_type) ) {
return false;
}
$email->setParams($send_params);
return $email->send($to_user_id);
}
/**
* Allows to check if user in this session is logged in or not
*
* @return bool
* @access public
*/
public function LoggedIn()
{
// no session during expiration process
return is_null($this->Session) ? false : $this->Session->LoggedIn();
}
/**
* Check current user permissions based on it's group permissions in specified category
*
* @param string $name permission name
* @param int $cat_id category id, current used if not specified
* @param int $type permission type {1 - system, 0 - per category}
* @return int
* @access public
*/
public function CheckPermission($name, $type = 1, $cat_id = null)
{
$perm_helper = $this->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
return $perm_helper->CheckPermission($name, $type, $cat_id);
}
/**
* Check current admin permissions based on it's group permissions in specified category
*
* @param string $name permission name
* @param int $cat_id category id, current used if not specified
* @param int $type permission type {1 - system, 0 - per category}
* @return int
* @access public
*/
public function CheckAdminPermission($name, $type = 1, $cat_id = null)
{
$perm_helper = $this->recallObject('PermissionsHelper');
/* @var $perm_helper kPermissionsHelper */
return $perm_helper->CheckAdminPermission($name, $type, $cat_id);
}
/**
* Set's any field of current visit
*
* @param string $field
* @param mixed $value
* @return void
* @access public
* @todo move to separate module
*/
public function setVisitField($field, $value)
{
if ( $this->isAdmin || !$this->ConfigValue('UseVisitorTracking') ) {
// admin logins are not registered in visits list
return;
}
$visit = $this->recallObject('visits', null, Array ('raise_warnings' => 0));
/* @var $visit kDBItem */
if ( $visit->isLoaded() ) {
$visit->SetDBField($field, $value);
$visit->Update();
}
}
/**
* Allows to check if in-portal is installed
*
* @return bool
* @access public
*/
public function isInstalled()
{
return $this->InitDone && (count($this->ModuleInfo) > 0);
}
/**
* Allows to determine if module is installed & enabled
*
* @param string $module_name
* @return bool
* @access public
*/
public function isModuleEnabled($module_name)
{
return $this->findModule('Name', $module_name) !== false;
}
/**
* Returns Window ID of passed prefix main prefix (in edit mode)
*
* @param string $prefix
* @return int
* @access public
*/
public function GetTopmostWid($prefix)
{
$top_prefix = $this->GetTopmostPrefix($prefix);
$mode = $this->GetVar($top_prefix . '_mode');
return $mode != '' ? substr($mode, 1) : '';
}
/**
* Get temp table name
*
* @param string $table
* @param mixed $wid
* @return string
* @access public
*/
public function GetTempName($table, $wid = '')
{
return $this->GetTempTablePrefix($wid) . $table;
}
/**
* Builds temporary table prefix based on given window id
*
* @param string $wid
* @return string
* @access public
*/
public function GetTempTablePrefix($wid = '')
{
if ( preg_match('/prefix:(.*)/', $wid, $regs) ) {
$wid = $this->GetTopmostWid($regs[1]);
}
return TABLE_PREFIX . 'ses_' . $this->GetSID() . ($wid ? '_' . $wid : '') . '_edit_';
}
/**
* Checks if given table is a temporary table
*
* @param string $table
* @return bool
* @access public
*/
public function IsTempTable($table)
{
static $cache = Array ();
if ( !array_key_exists($table, $cache) ) {
$cache[$table] = preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $table);
}
return (bool)$cache[$table];
}
/**
* Checks, that given prefix is in temp mode
*
* @param string $prefix
* @param string $special
* @return bool
* @access public
*/
public function IsTempMode($prefix, $special = '')
{
$top_prefix = $this->GetTopmostPrefix($prefix);
$var_names = Array (
$top_prefix,
rtrim($top_prefix . '_' . $special, '_'), // from post
rtrim($top_prefix . '.' . $special, '.'), // assembled locally
);
$var_names = array_unique($var_names);
$temp_mode = false;
foreach ($var_names as $var_name) {
$value = $this->GetVar($var_name . '_mode');
if ( $value && (substr($value, 0, 1) == 't') ) {
$temp_mode = true;
break;
}
}
return $temp_mode;
}
/**
* Return live table name based on temp table name
*
* @param string $temp_table
* @return string
*/
public function GetLiveName($temp_table)
{
if ( preg_match('/' . TABLE_PREFIX . 'ses_' . $this->GetSID() . '(_[\d]+){0,1}_edit_(.*)/', $temp_table, $rets) ) {
// cut wid from table end if any
return $rets[2];
}
else {
return $temp_table;
}
}
/**
* Stops processing of user request and displays given message
*
* @param string $message
* @access public
*/
public function ApplicationDie($message = '')
{
$message = ob_get_clean() . $message;
if ( $this->isDebugMode() ) {
$message .= $this->Debugger->printReport(true);
}
echo $this->UseOutputCompression() ? gzencode($message, DBG_COMPRESSION_LEVEL) : $message;
exit;
}
/**
* Returns comma-separated list of groups from given user
*
* @param int $user_id
* @return string
*/
public function getUserGroups($user_id)
{
switch ($user_id) {
case USER_ROOT:
$user_groups = $this->ConfigValue('User_LoggedInGroup');
break;
case USER_GUEST:
$user_groups = $this->ConfigValue('User_LoggedInGroup') . ',' . $this->ConfigValue('User_GuestGroup');
break;
default:
$sql = 'SELECT GroupId
FROM ' . TABLE_PREFIX . 'UserGroupRelations
WHERE PortalUserId = ' . (int)$user_id;
$res = $this->Conn->GetCol($sql);
$user_groups = Array ($this->ConfigValue('User_LoggedInGroup'));
if ( $res ) {
$user_groups = array_merge($user_groups, $res);
}
$user_groups = implode(',', $user_groups);
}
return $user_groups;
}
/**
* Allows to detect if page is browsed by spider (293 scheduled_tasks supported)
*
* @return bool
* @access public
*/
/*public function IsSpider()
{
static $is_spider = null;
if ( !isset($is_spider) ) {
$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
$robots = file(FULL_PATH . '/core/robots_list.txt');
foreach ($robots as $robot_info) {
$robot_info = explode("\t", $robot_info, 3);
if ( $user_agent == trim($robot_info[2]) ) {
$is_spider = true;
break;
}
}
}
return $is_spider;
}*/
/**
* 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)
{
return $this->Conn->TableFound($table_name, $force);
}
/**
* Returns counter value
*
* @param string $name counter name
* @param Array $params counter parameters
* @param string $query_name specify query name directly (don't generate from parameters)
* @param bool $multiple_results
* @return mixed
* @access public
*/
public function getCounter($name, $params = Array (), $query_name = null, $multiple_results = false)
{
$count_helper = $this->recallObject('CountHelper');
/* @var $count_helper kCountHelper */
return $count_helper->getCounter($name, $params, $query_name, $multiple_results);
}
/**
* Resets counter, which are affected by one of specified tables
*
* @param string $tables comma separated tables list used in counting sqls
* @return void
* @access public
*/
public function resetCounters($tables)
{
if ( kUtil::constOn('IS_INSTALL') ) {
return;
}
$count_helper = $this->recallObject('CountHelper');
/* @var $count_helper kCountHelper */
$count_helper->resetCounters($tables);
}
/**
* Sends XML header + optionally displays xml heading
*
* @param string|bool $xml_version
* @return string
* @access public
* @author Alex
*/
public function XMLHeader($xml_version = false)
{
$lang = $this->recallObject('lang.current');
/* @var $lang LanguagesItem */
$this->setContentType('text/xml');
return $xml_version ? '<?xml version="' . $xml_version . '" encoding="' . $lang->GetDBField('Charset') . '"?>' : '';
}
/**
* Returns category tree
*
* @param int $category_id
* @return Array
* @access public
*/
public function getTreeIndex($category_id)
{
$tree_index = $this->getCategoryCache($category_id, 'category_tree');
if ( $tree_index ) {
$ret = Array ();
list ($ret['TreeLeft'], $ret['TreeRight']) = explode(';', $tree_index);
return $ret;
}
return false;
}
/**
* Base category of all categories
* Usually replaced category, with ID = 0 in category-related operations.
*
* @return int
* @access public
*/
public function getBaseCategory()
{
// same, what $this->findModule('Name', 'Core', 'RootCat') does
// don't cache while IS_INSTALL, because of kInstallToolkit::createModuleCategory and upgrade
return $this->ModuleInfo['Core']['RootCat'];
}
/**
* Deletes all data, that was cached during unit config parsing (excluding unit config locations)
*
* @param Array $config_variables
* @access public
*/
public function DeleteUnitCache($config_variables = null)
{
$this->cacheManager->DeleteUnitCache($config_variables);
}
/**
* Deletes cached section tree, used during permission checking and admin console tree display
*
* @return void
* @access public
*/
public function DeleteSectionCache()
{
$this->cacheManager->DeleteSectionCache();
}
/**
* Sets data from cache to object
*
* @param Array $data
* @access public
*/
public function setFromCache(&$data)
{
$this->Factory->setFromCache($data);
$this->UnitConfigReader->setFromCache($data);
$this->EventManager->setFromCache($data);
$this->ReplacementTemplates = $data['Application.ReplacementTemplates'];
$this->RewriteListeners = $data['Application.RewriteListeners'];
$this->ModuleInfo = $data['Application.ModuleInfo'];
}
/**
* Gets object data for caching
* The following caches should be reset based on admin interaction (adjusting config, enabling modules etc)
*
* @access public
* @return Array
*/
public function getToCache()
{
return array_merge(
$this->Factory->getToCache(),
$this->UnitConfigReader->getToCache(),
$this->EventManager->getToCache(),
Array (
'Application.ReplacementTemplates' => $this->ReplacementTemplates,
'Application.RewriteListeners' => $this->RewriteListeners,
'Application.ModuleInfo' => $this->ModuleInfo,
)
);
}
public function delayUnitProcessing($method, $params)
{
$this->cacheManager->delayUnitProcessing($method, $params);
}
/**
* Returns current maintenance mode state
*
* @param bool $check_ips
* @return int
* @access public
*/
public function getMaintenanceMode($check_ips = true)
{
$exception_ips = defined('MAINTENANCE_MODE_IPS') ? MAINTENANCE_MODE_IPS : '';
$setting_name = $this->isAdmin ? 'MAINTENANCE_MODE_ADMIN' : 'MAINTENANCE_MODE_FRONT';
if ( defined($setting_name) && constant($setting_name) > MaintenanceMode::NONE ) {
$exception_ip = $check_ips ? kUtil::ipMatch($exception_ips) : false;
if ( !$exception_ip ) {
return constant($setting_name);
}
}
return MaintenanceMode::NONE;
}
/**
* Sets content type of the page
*
* @param string $content_type
* @param bool $include_charset
* @return void
* @access public
*/
public function setContentType($content_type = 'text/html', $include_charset = null)
{
static $aleady_set = false;
if ( $aleady_set ) {
return;
}
$header = 'Content-type: ' . $content_type;
if ( !isset($include_charset) ) {
$include_charset = $content_type = 'text/html' || $content_type = 'text/xml';
}
if ( $include_charset ) {
$language = $this->recallObject('lang.current');
/* @var $language LanguagesItem */
$header .= '; charset=' . $language->GetDBField('Charset');
}
$aleady_set = true;
header($header);
}
}
\ No newline at end of file
Index: branches/5.2.x/core/units/helpers/language_import_helper.php
===================================================================
--- branches/5.2.x/core/units/helpers/language_import_helper.php (revision 15238)
+++ branches/5.2.x/core/units/helpers/language_import_helper.php (revision 15239)
@@ -1,1222 +1,1218 @@
<?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.
*/
/**
* Language pack format version description
*
* v1
* ==========
* All language properties are separate nodes inside <LANGUAGE> node. There are
* two more nodes PHRASES and EVENTS for phrase and email event translations.
*
* v2
* ==========
* All data, that will end up in Language table is now attributes of LANGUAGE node
* and is name exactly as field name, that will be used to store that data.
*
* v4
* ==========
* Hint & Column translation added to each phrase translation
*
* v5
* ==========
* Use separate xml nodes for subject, headers, html & plain translations
*
* v6
* ==========
* Added e-mail design templates
*
*/
defined('FULL_PATH') or die('restricted access!');
define('LANG_OVERWRITE_EXISTING', 1);
define('LANG_SKIP_EXISTING', 2);
class LanguageImportHelper extends kHelper {
/**
* Current Language in import
*
* @var LanguagesItem
*/
var $lang_object = null;
/**
* Current user's IP address
*
* @var string
*/
var $ip_address = '';
/**
* Event type + name mapping to id (from system)
*
* @var Array
*/
var $events_hash = Array ();
/**
* Language pack import mode
*
* @var int
*/
var $import_mode = LANG_SKIP_EXISTING;
/**
* Language IDs, that were imported
*
* @var Array
*/
var $_languages = Array ();
/**
* Temporary table names to perform import on
*
* @var Array
*/
var $_tables = Array ();
/**
* Phrase types allowed for import/export operations
*
* @var Array
*/
var $phrase_types_allowed = Array ();
/**
* Encoding, used for language pack exporting
*
* @var string
*/
var $_exportEncoding = 'base64';
/**
* Exported data limits (all or only specified ones)
*
* @var Array
*/
var $_exportLimits = Array (
'phrases' => false,
'emailevents' => false,
'country-state' => false,
);
/**
* Debug language pack import process
*
* @var bool
*/
var $_debugMode = false;
/**
* Latest version of language pack format. Versions are not backwards compatible!
*
* @var int
*/
var $_latestVersion = 6;
/**
* Prefix-based serial numbers, that should be changed after import is finished
*
* @var Array
*/
var $changedPrefixes = Array ();
public function __construct()
{
parent::__construct();
// "core/install/english.lang", phrase count: 3318, xml parse time on windows: 10s, insert time: 0.058s
set_time_limit(0);
ini_set('memory_limit', -1);
$this->lang_object = $this->Application->recallObject('lang.import', null, Array ('skip_autoload' => true));
if (!(defined('IS_INSTALL') && IS_INSTALL)) {
// perform only, when not in installation mode
$this->_updateEventsCache();
}
$this->ip_address = getenv('HTTP_X_FORWARDED_FOR') ? getenv('HTTP_X_FORWARDED_FOR') : getenv('REMOTE_ADDR');
// $this->_debugMode = $this->Application->isDebugMode();
}
/**
* Performs import of given language pack (former Parse method)
*
* @param string $filename
* @param string $phrase_types
* @param Array $module_ids
* @param int $import_mode
* @return bool
*/
function performImport($filename, $phrase_types, $module_ids, $import_mode = LANG_SKIP_EXISTING)
{
// define the XML parsing routines/functions to call based on the handler path
if (!file_exists($filename) || !$phrase_types /*|| !$module_ids*/) {
return false;
}
if ($this->_debugMode) {
$start_time = microtime(true);
$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '")');
}
if (defined('IS_INSTALL') && IS_INSTALL) {
// new events could be added during module upgrade
$this->_updateEventsCache();
}
$this->_initImportTables();
$phrase_types = explode('|', substr($phrase_types, 1, -1) );
// $module_ids = explode('|', substr($module_ids, 1, -1) );
$this->phrase_types_allowed = array_flip($phrase_types);
$this->import_mode = $import_mode;
$this->_parseXML($filename);
// copy data from temp tables to live
foreach ($this->_languages as $language_id) {
$this->_performUpgrade($language_id, 'phrases', 'PhraseKey', Array ('l%s_Translation', 'l%s_HintTranslation', 'l%s_ColumnTranslation', 'PhraseType'));
$this->_performUpgrade($language_id, 'emailevents', 'EventId', Array ('l%s_Subject', 'Headers', 'l%s_HtmlBody', 'l%s_PlainTextBody'));
$this->_performUpgrade($language_id, 'country-state', 'CountryStateId', Array ('l%s_Name'));
}
$this->_initImportTables(true);
$this->changedPrefixes = array_unique($this->changedPrefixes);
foreach ($this->changedPrefixes as $prefix) {
$this->Application->incrementCacheSerial($prefix);
}
if ($this->_debugMode) {
$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (microtime(true) - $start_time));
}
return true;
}
/**
* Creates XML file with exported language data (former Create method)
*
* @param string $filename filename to export into
* @param Array $phrase_types phrases types to export from modules passed in $module_ids
* @param Array $language_ids IDs of languages to export
* @param Array $module_ids IDs of modules to export phrases from
*/
function performExport($filename, $phrase_types, $language_ids, $module_ids)
{
$fp = fopen($filename,'w');
if (!$fp || !$phrase_types || !$module_ids || !$language_ids) {
return false;
}
$phrase_types = explode('|', substr($phrase_types, 1, -1) );
$module_ids = explode('|', substr($module_ids, 1, -1) );
$ret = '<LANGUAGES Version="' . $this->_latestVersion . '">' . "\n";
$export_fields = $this->_getExportFields();
// get languages
$sql = 'SELECT *
FROM ' . $this->Application->getUnitOption('lang','TableName') . '
WHERE LanguageId IN (' . implode(',', $language_ids) . ')';
$languages = $this->Conn->Query($sql, 'LanguageId');
// get phrases
$phrase_modules = $module_ids;
array_push($phrase_modules, ''); // for old language packs without module
$phrase_modules = $this->Conn->qstrArray($phrase_modules);
// apply phrase selection limit
if ($this->_exportLimits['phrases']) {
$escaped_phrases = $this->Conn->qstrArray($this->_exportLimits['phrases']);
$limit_where = 'Phrase IN (' . implode(',', $escaped_phrases) . ')';
}
else {
$limit_where = 'TRUE';
}
$sql = 'SELECT *
FROM ' . $this->Application->getUnitOption('phrases','TableName') . '
WHERE PhraseType IN (' . implode(',', $phrase_types) . ') AND Module IN (' . implode(',', $phrase_modules) . ') AND ' . $limit_where . '
ORDER BY Phrase';
$phrases = $this->Conn->Query($sql, 'PhraseId');
// email events
$module_sql = preg_replace('/(.*),/U', 'INSTR(Module,\'\\1\') OR ', implode(',', $module_ids) . ',');
// apply event selection limit
if ($this->_exportLimits['emailevents']) {
$escaped_email_events = $this->Conn->qstrArray($this->_exportLimits['emailevents']);
$limit_where = '`Event` IN (' . implode(',', $escaped_email_events) . ')';
}
else {
$limit_where = 'TRUE';
}
$sql = 'SELECT *
FROM ' . $this->Application->getUnitOption('emailevents', 'TableName') . '
WHERE `Type` IN (' . implode(',', $phrase_types) . ') AND (' . substr($module_sql, 0, -4) . ') AND ' . $limit_where . '
ORDER BY `Event`, `Type`';
$events = $this->Conn->Query($sql, 'EventId');
if ( in_array('Core', $module_ids) ) {
if ($this->_exportLimits['country-state']) {
$escaped_countries = $this->Conn->qstrArray($this->_exportLimits['country-state']);
$limit_where = '`IsoCode` IN (' . implode(',', $escaped_countries) . ')';
}
else {
$limit_where = 'TRUE';
}
$country_table = $this->Application->getUnitOption('country-state', 'TableName');
// countries
$sql = 'SELECT *
FROM ' . $country_table . '
WHERE Type = ' . DESTINATION_TYPE_COUNTRY . ' AND ' . $limit_where . '
ORDER BY `IsoCode`';
$countries = $this->Conn->Query($sql, 'CountryStateId');
// states
$sql = 'SELECT state.*
FROM ' . $country_table . ' state
JOIN ' . $country_table . ' country ON country.CountryStateId = state.StateCountryId
WHERE state.Type = ' . DESTINATION_TYPE_STATE . ' AND ' . str_replace('`IsoCode`', 'country.`IsoCode`', $limit_where) . '
ORDER BY state.`IsoCode`';
$states = $this->Conn->Query($sql, 'CountryStateId');
foreach ($states as $state_id => $state_data) {
$country_id = $state_data['StateCountryId'];
if ( !array_key_exists('States', $countries[$country_id]) ) {
$countries[$country_id]['States'] = Array ();
}
$countries[$country_id]['States'][] = $state_id;
}
}
foreach ($languages as $language_id => $language_info) {
// language
$ret .= "\t" . '<LANGUAGE Encoding="' . $this->_exportEncoding . '"';
foreach ($export_fields as $export_field) {
$ret .= ' ' . $export_field . '="' . htmlspecialchars($language_info[$export_field]) . '"';
}
$ret .= '>' . "\n";
// filename replacements
$replacements = $language_info['FilenameReplacements'];
if ( $replacements ) {
$ret .= "\t\t" . '<REPLACEMENTS>' . $this->_exportConvert($replacements) . '</REPLACEMENTS>' . "\n";
}
// e-mail design templates
if ( $language_info['HtmlEmailTemplate'] || $language_info['TextEmailTemplate'] ) {
$ret .= "\t\t" . '<EMAILDESIGNS>' . "\n";
if ( $language_info['HtmlEmailTemplate'] ) {
$ret .= "\t\t\t" . '<HTML>' . $this->_exportConvert($language_info['HtmlEmailTemplate']) . '</HTML>' . "\n";
}
if ( $language_info['TextEmailTemplate'] ) {
$ret .= "\t\t\t" . '<TEXT>' . $this->_exportConvert($language_info['TextEmailTemplate']) . '</TEXT>' . "\n";
}
$ret .= "\t\t" . '</EMAILDESIGNS>' . "\n";
}
// phrases
if ($phrases) {
$ret .= "\t\t" . '<PHRASES>' . "\n";
foreach ($phrases as $phrase_id => $phrase) {
$translation = $phrase['l' . $language_id . '_Translation'];
$hint_translation = $phrase['l' . $language_id . '_HintTranslation'];
$column_translation = $phrase['l' . $language_id . '_ColumnTranslation'];
if (!$translation) {
// phrase is not translated on given language
continue;
}
if ( $this->_exportEncoding == 'base64' ) {
$hint_translation = base64_encode($hint_translation);
$column_translation = base64_encode($column_translation);
}
else {
$hint_translation = htmlspecialchars($hint_translation);
$column_translation = htmlspecialchars($column_translation);
}
$attributes = Array (
'Label="' . $phrase['Phrase'] . '"',
'Module="' . $phrase['Module'] . '"',
'Type="' . $phrase['PhraseType'] . '"'
);
if ( $phrase['l' . $language_id . '_HintTranslation'] ) {
$attributes[] = 'Hint="' . $hint_translation . '"';
}
if ( $phrase['l' . $language_id . '_ColumnTranslation'] ) {
$attributes[] = 'Column="' . $column_translation . '"';
}
$ret .= "\t\t\t" . '<PHRASE ' . implode(' ', $attributes) . '>' . $this->_exportConvert($translation) . '</PHRASE>' . "\n";
}
$ret .= "\t\t" . '</PHRASES>' . "\n";
}
// email events
if ($events) {
$ret .= "\t\t" . '<EVENTS>' . "\n";
foreach ($events as $event_data) {
$fields_hash = Array (
'HEADERS' => $event_data['Headers'],
'SUBJECT' => $event_data['l' . $language_id . '_Subject'],
'HTMLBODY' => $event_data['l' . $language_id . '_HtmlBody'],
'PLAINTEXTBODY' => $event_data['l' . $language_id . '_PlainTextBody'],
);
$data = '';
foreach ($fields_hash as $xml_node => $xml_content) {
if ( $xml_content ) {
$data .= "\t\t\t\t" . '<' . $xml_node . '>' . $this->_exportConvert($xml_content) . '</' . $xml_node . '>' . "\n";
}
}
if ( $data ) {
$ret .= "\t\t\t" . '<EVENT Event="' . $event_data['Event'] . '" Type="' . $event_data['Type'] . '">' . "\n" . $data . "\t\t\t" . '</EVENT>' . "\n";
}
}
$ret .= "\t\t" . '</EVENTS>' . "\n";
}
if (in_array('Core', $module_ids) && $countries) {
$ret .= "\t\t" . '<COUNTRIES>' . "\n";
foreach ($countries as $country_data) {
$translation = $country_data['l' . $language_id . '_Name'];
if (!$translation) {
// country is not translated on given language
continue;
}
$data = $this->_exportEncoding == 'base64' ? base64_encode($translation) : $translation;
if (array_key_exists('States', $country_data)) {
$ret .= "\t\t\t" . '<COUNTRY Iso="' . $country_data['IsoCode'] . '" Translation="' . $data . '">' . "\n";
foreach ($country_data['States'] as $state_id) {
$translation = $states[$state_id]['l' . $language_id . '_Name'];
if (!$translation) {
// state is not translated on given language
continue;
}
$data = $this->_exportEncoding == 'base64' ? base64_encode($translation) : $translation;
$ret .= "\t\t\t\t" . '<STATE Iso="' . $states[$state_id]['IsoCode'] . '" Translation="' . $data . '"/>' . "\n";
}
$ret .= "\t\t\t" . '</COUNTRY>' . "\n";
}
else {
$ret .= "\t\t\t" . '<COUNTRY Iso="' . $country_data['IsoCode'] . '" Translation="' . $data . '"/>' . "\n";
}
}
$ret .= "\t\t" . '</COUNTRIES>' . "\n";
}
$ret .= "\t" . '</LANGUAGE>' . "\n";
}
$ret .= '</LANGUAGES>';
fwrite($fp, $ret);
fclose($fp);
return true;
}
/**
* Converts string before placing into export file
*
* @param string $string
* @return string
* @access protected
*/
protected function _exportConvert($string)
{
return $this->_exportEncoding == 'base64' ? base64_encode($string) : '<![CDATA[' . $string . ']]>';
}
/**
* Sets language pack encoding (not charset) used during export
*
* @param string $encoding
*/
function setExportEncoding($encoding)
{
$this->_exportEncoding = $encoding;
}
/**
* Sets language pack data limit for export
*
* @param string $prefix
* @param string $data
*/
function setExportLimit($prefix, $data = null)
{
if ( !isset($data) ) {
$key_field = $prefix == 'phrases' ? 'Phrase' : 'Event';
$ids = $this->getExportIDs($prefix);
$sql = 'SELECT ' . $key_field . '
FROM ' . $this->Application->getUnitOption($prefix, 'TableName') . '
WHERE ' . $this->Application->getUnitOption($prefix, 'IDField') . ' IN (' . $ids . ')';
- $rs = $this->Conn->QueryRaw($sql);
+ $rows = $this->Conn->GetIterator($sql);
- if ( $this->Conn->RowCount($rs) ) {
+ if ( count($rows) ) {
$data = '';
- while ( ($row = $this->Conn->GetNextRow($rs)) ) {
+ foreach ($rows as $row) {
$data .= ',' . $row[$key_field];
}
$data = substr($data, 1);
}
-
- $this->Conn->Destroy($rs);
}
if ( !is_array($data) ) {
$data = str_replace(',', "\n", $data);
$data = preg_replace("/\n+/", "\n", str_replace("\r", '', trim($data)));
$data = $data ? array_map('trim', explode("\n", $data)) : Array ();
}
$this->_exportLimits[$prefix] = $data;
}
/**
* Performs upgrade of given language pack part
*
* @param int $language_id
* @param string $prefix
* @param string $unique_field
* @param Array $data_fields
*/
function _performUpgrade($language_id, $prefix, $unique_field, $data_fields)
{
$live_records = $this->_getTableData($language_id, $prefix, $unique_field, $data_fields[0], false);
$temp_records = $this->_getTableData($language_id, $prefix, $unique_field, $data_fields[0], true);
if (!$temp_records) {
// no data for given language
return ;
}
// perform insert for records, that are missing in live table
$to_insert = array_diff($temp_records, $live_records);
if ($to_insert) {
$to_insert = $this->Conn->qstrArray($to_insert);
$sql = 'INSERT INTO ' . $this->Application->getUnitOption($prefix, 'TableName') . '
SELECT *
FROM ' . $this->_tables[$prefix] . '
WHERE ' . $unique_field . ' IN (' . implode(',', $to_insert) . ')';
$this->Conn->Query($sql);
// new records were added
$this->changedPrefixes[] = $prefix;
}
// perform update for records, that are present in live table
$to_update = array_diff($temp_records, $to_insert);
if ($to_update) {
$to_update = $this->Conn->qstrArray($to_update);
$sql = 'UPDATE ' . $this->Application->getUnitOption($prefix, 'TableName') . ' live
SET ';
foreach ($data_fields as $index => $data_field) {
$data_field = sprintf($data_field, $language_id);
$sql .= ' live.' . $data_field . ' = (
SELECT temp' . $index . '.' . $data_field . '
FROM ' . $this->_tables[$prefix] . ' temp' . $index . '
WHERE temp' . $index . '.' . $unique_field . ' = live.' . $unique_field . '
),';
}
$sql = substr($sql, 0, -1); // cut last comma
$where_clause = Array (
// this won't make any difference, but just in case
$unique_field . ' IN (' . implode(',', $to_update) . ')',
);
if ($this->import_mode == LANG_SKIP_EXISTING) {
// empty OR not set
$data_field = sprintf($data_fields[0], $language_id);
$where_clause[] = '(' . $data_field . ' = "") OR (' . $data_field . ' IS NULL)';
}
if ($where_clause) {
$sql .= "\n" . 'WHERE (' . implode(') AND (', $where_clause) . ')';
}
$this->Conn->Query($sql);
if ($this->Conn->getAffectedRows() > 0) {
// existing records were updated
$this->changedPrefixes[] = $prefix;
}
}
}
/**
* Returns data from given table used for language pack upgrade
*
* @param int $language_id
* @param string $prefix
* @param string $unique_field
* @param string $data_field
* @param bool $temp_mode
* @return Array
*/
function _getTableData($language_id, $prefix, $unique_field, $data_field, $temp_mode = false)
{
$data_field = sprintf($data_field, $language_id);
$table_name = $this->Application->getUnitOption($prefix, 'TableName');
if ($temp_mode) {
// for temp table get only records, that have contents on given language (not empty and isset)
$sql = 'SELECT ' . $unique_field . '
FROM ' . $this->Application->GetTempName($table_name, 'prefix:' . $prefix) . '
WHERE (' . $data_field . ' <> "") AND (' . $data_field . ' IS NOT NULL)';
}
else {
// for live table get all records, no matter on what language
$sql = 'SELECT ' . $unique_field . '
FROM ' . $table_name;
}
return $this->Conn->GetCol($sql);
}
function _parseXML($filename)
{
if ( $this->_debugMode ) {
$start_time = microtime(true);
$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '")');
}
$languages = simplexml_load_file($filename);
if ( $languages === false) {
// invalid language pack contents
return false;
}
// PHP 5.3 version would be: $languages->count()
if ( count($languages->children()) ) {
$this->_processLanguages($languages);
}
if ( $this->_debugMode ) {
$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (microtime(true) - $start_time));
}
return true;
}
/**
* Creates temporary tables, used during language import
*
* @param bool $drop_only
*/
function _initImportTables($drop_only = false)
{
$this->_tables['phrases'] = $this->_prepareTempTable('phrases', $drop_only);
$this->_tables['emailevents'] = $this->_prepareTempTable('emailevents', $drop_only);
$this->_tables['country-state'] = $this->_prepareTempTable('country-state', $drop_only);
}
/**
* Create temp table for prefix, if table already exists, then delete it and create again
*
* @param string $prefix
* @param bool $drop_only
* @return string Name of created temp table
* @access protected
*/
protected function _prepareTempTable($prefix, $drop_only = false)
{
$id_field = $this->Application->getUnitOption($prefix, 'IDField');
$table = $this->Application->getUnitOption($prefix,'TableName');
$temp_table = $this->Application->GetTempName($table);
$sql = 'DROP TABLE IF EXISTS %s';
$this->Conn->Query( sprintf($sql, $temp_table) );
if (!$drop_only) {
$sql = 'CREATE TABLE ' . $temp_table . ' SELECT * FROM ' . $table . ' WHERE 0';
$this->Conn->Query($sql);
$sql = 'ALTER TABLE %1$s CHANGE %2$s %2$s INT(11) NOT NULL DEFAULT "0"';
$this->Conn->Query( sprintf($sql, $temp_table, $id_field) );
switch ($prefix) {
case 'phrases':
$unique_field = 'PhraseKey';
break;
case 'emailevents':
$unique_field = 'EventId';
break;
case 'country-state':
$unique_field = 'CountryStateId';
break;
default:
throw new Exception('Unknown prefix "<strong>' . $prefix . '</strong>" during language pack import');
break;
}
$sql = 'ALTER TABLE ' . $temp_table . ' ADD UNIQUE (' . $unique_field . ')';
$this->Conn->Query($sql);
}
return $temp_table;
}
/**
* Prepares mapping between event name+type and their ids in database
*
*/
function _updateEventsCache()
{
$sql = 'SELECT EventId, CONCAT(Event,"_",Type) AS EventMix
FROM ' . TABLE_PREFIX . 'EmailEvents';
$this->events_hash = $this->Conn->GetCol($sql, 'EventMix');
}
/**
* Returns language fields to be exported
*
* @return Array
*/
function _getExportFields()
{
return Array (
'PackName', 'LocalName', 'DateFormat', 'TimeFormat', 'InputDateFormat', 'InputTimeFormat',
'DecimalPoint', 'ThousandSep', 'Charset', 'UnitSystem', 'Locale', 'UserDocsUrl'
);
}
/**
* Processes parsed XML
*
* @param SimpleXMLElement $languages
*/
function _processLanguages($languages)
{
$version = (int)$languages['Version'];
if ( !$version ) {
// version missing -> guess it
if ( $languages->DATEFORMAT->getName() ) {
$version = 1;
}
elseif ( (string)$languages->LANGUAGE['Charset'] != '' ) {
$version = 2;
}
}
if ( $version == 1 ) {
$field_mapping = Array (
'DATEFORMAT' => 'DateFormat',
'TIMEFORMAT' => 'TimeFormat',
'INPUTDATEFORMAT' => 'InputDateFormat',
'INPUTTIMEFORMAT' => 'InputTimeFormat',
'DECIMAL' => 'DecimalPoint',
'THOUSANDS' => 'ThousandSep',
'CHARSET' => 'Charset',
'UNITSYSTEM' => 'UnitSystem',
'DOCS_URL' => 'UserDocsUrl',
);
}
else {
$export_fields = $this->_getExportFields();
}
foreach ($languages as $language_node) {
$language_id = false;
$fields_hash = Array (
'PackName' => (string)$language_node['PackName'],
'LocalName' => (string)$language_node['PackName'],
'Encoding' => (string)$language_node['Encoding'],
'Charset' => 'utf-8',
'SynchronizationModes' => Language::SYNCHRONIZE_DEFAULT,
);
if ( $version > 1 ) {
foreach ($export_fields as $export_field) {
if ( (string)$language_node[$export_field] ) {
$fields_hash[$export_field] = (string)$language_node[$export_field];
}
}
}
$container_nodes = Array ('PHRASES', 'EVENTS', 'COUNTRIES');
foreach ($language_node as $sub_node) {
/* @var $sub_node SimpleXMLElement */
if ( in_array($sub_node->getName(), $container_nodes) ) {
// PHP 5.3 version would be: !$sub_node->count()
if ( !count($sub_node->children()) ) {
continue;
}
if ( !$language_id ) {
$language_id = $this->_processLanguage($fields_hash);
}
}
switch ($sub_node->getName()) {
case 'PHRASES':
$this->_processPhrases($sub_node, $language_id, $fields_hash['Encoding']);
break;
case 'EVENTS':
$this->_processEvents($sub_node, $language_id, $fields_hash['Encoding']);
break;
case 'COUNTRIES':
$this->_processCountries($sub_node, $language_id, $fields_hash['Encoding']);
break;
case 'REPLACEMENTS':
// added since v2
$replacements = (string)$sub_node;
if ( $fields_hash['Encoding'] != 'plain' ) {
$replacements = base64_decode($replacements);
}
$fields_hash['FilenameReplacements'] = $replacements;
break;
case 'EMAILDESIGNS':
// added since v6
$this->_decodeEmailDesignTemplate($fields_hash, 'HtmlEmailTemplate', (string)$sub_node->HTML);
$this->_decodeEmailDesignTemplate($fields_hash, 'TextEmailTemplate', (string)$sub_node->TEXT);
break;
default:
if ( $version == 1 ) {
$fields_hash[$field_mapping[$sub_node->Name]] = (string)$sub_node;
}
break;
}
}
}
}
/**
* Decodes e-mail template design from language pack
*
* @param Array $fields_hash
* @param string $field
* @param string $design_template
*/
protected function _decodeEmailDesignTemplate(&$fields_hash, $field, $design_template)
{
if ( $fields_hash['Encoding'] != 'plain' ) {
$design_template = base64_decode($design_template);
}
if ( $design_template ) {
$fields_hash[$field] = $design_template;
}
}
/**
* Performs phases import
*
* @param SimpleXMLElement $phrases
* @param int $language_id
* @param string $language_encoding
*/
function _processPhrases($phrases, $language_id, $language_encoding)
{
static $other_translations = Array ();
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->profileStart('L[' . $language_id . ']P', 'Language: ' . $language_id . '; Phrases Import');
}
foreach ($phrases as $phrase_node) {
/* @var $phrase_node SimpleXMLElement */
$phrase_key = mb_strtoupper($phrase_node['Label']);
$fields_hash = Array (
'Phrase' => (string)$phrase_node['Label'],
'PhraseKey' => $phrase_key,
'PhraseType' => (int)$phrase_node['Type'],
'Module' => (string)$phrase_node['Module'] ? (string)$phrase_node['Module'] : 'Core',
'LastChanged' => TIMENOW,
'LastChangeIP' => $this->ip_address,
);
$translation = (string)$phrase_node;
$hint_translation = (string)$phrase_node['Hint'];
$column_translation = (string)$phrase_node['Column'];
if ( array_key_exists($fields_hash['PhraseType'], $this->phrase_types_allowed) ) {
if ( $language_encoding != 'plain' ) {
$translation = base64_decode($translation);
$hint_translation = base64_decode($hint_translation);
$column_translation = base64_decode($column_translation);
}
if ( array_key_exists($phrase_key, $other_translations) ) {
$other_translations[$phrase_key]['l' . $language_id . '_Translation'] = $translation;
$other_translations[$phrase_key]['l' . $language_id . '_HintTranslation'] = $hint_translation;
$other_translations[$phrase_key]['l' . $language_id . '_ColumnTranslation'] = $column_translation;
}
else {
$other_translations[$phrase_key] = Array (
'l' . $language_id . '_Translation' => $translation,
'l' . $language_id . '_HintTranslation' => $hint_translation,
'l' . $language_id . '_ColumnTranslation' => $column_translation,
);
}
$fields_hash = array_merge($fields_hash, $other_translations[$phrase_key]);
$this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'REPLACE', false);
}
}
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->profileFinish('L[' . $language_id . ']P', 'Language: ' . $language_id . '; Phrases Import');
}
$this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'REPLACE');
}
/**
* Performs email event import
*
* @param SimpleXMLElement $events
* @param int $language_id
* @param string $language_encoding
*/
function _processEvents($events, $language_id, $language_encoding)
{
static $other_translations = Array ();
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->profileStart('L[' . $language_id . ']E', 'Language: ' . $language_id . '; Events Import');
}
$email_message_helper = $this->Application->recallObject('kEmailMessageHelper');
/* @var $email_message_helper kEmailMessageHelper */
foreach ($events as $event_node) {
/* @var $event_node SimpleXMLElement */
$message_type = (string)$event_node['MessageType'];
$event_id = $this->_getEventId((string)$event_node['Event'], (int)$event_node['Type']);
if ( !$event_id ) {
continue;
}
$fields_hash = Array (
'EventId' => $event_id,
'Event' => (string)$event_node['Event'],
'Type' => (int)$event_node['Type'],
);
if ( $message_type == '' ) {
$parsed = $email_message_helper->parseTemplate($event_node, '');
$parsed = array_map($language_encoding == 'plain' ? 'rtrim' : 'base64_decode', $parsed);
}
else {
$template = $language_encoding == 'plain' ? rtrim($event_node) : base64_decode($event_node);
$parsed = $email_message_helper->parseTemplate($template, $message_type);
}
if ( array_key_exists($event_id, $other_translations) ) {
$other_translations[$event_id]['l' . $language_id . '_Subject'] = $parsed['Subject'];
$other_translations[$event_id]['l' . $language_id . '_HtmlBody'] = $parsed['HtmlBody'];
$other_translations[$event_id]['l' . $language_id . '_PlainTextBody'] = $parsed['PlainTextBody'];
}
else {
$other_translations[$event_id] = Array (
'l' . $language_id . '_Subject' => $parsed['Subject'],
'l' . $language_id . '_HtmlBody' => $parsed['HtmlBody'],
'l' . $language_id . '_PlainTextBody' => $parsed['PlainTextBody'],
);
}
if ( $parsed['Headers'] ) {
$other_translations[$event_id]['Headers'] = $parsed['Headers'];
}
elseif ( !$parsed['Headers'] && !array_key_exists('Headers', $other_translations[$event_id]) ) {
$other_translations[$event_id]['Headers'] = $parsed['Headers'];
}
$fields_hash = array_merge($fields_hash, $other_translations[$event_id]);
$this->Conn->doInsert($fields_hash, $this->_tables['emailevents'], 'REPLACE', false);
}
if ( $this->Application->isDebugMode() ) {
$this->Application->Debugger->profileFinish('L[' . $language_id . ']E', 'Language: ' . $language_id . '; Events Import');
}
if ( isset($fields_hash) ) {
// at least one email event in language pack was found in database
$this->Conn->doInsert($fields_hash, $this->_tables['emailevents'], 'REPLACE');
}
}
/**
* Performs country_state translation import
*
* @param SimpleXMLElement $country_states
* @param int $language_id
* @param string $language_encoding
* @param bool $process_states
* @return void
*/
function _processCountries($country_states, $language_id, $language_encoding, $process_states = false)
{
static $other_translations = Array ();
foreach ($country_states as $country_state_node) {
/* @var $country_state_node SimpleXMLElement */
if ( $process_states ) {
$country_state_id = $this->_getStateId((string)$country_states['Iso'], (string)$country_state_node['Iso']);
}
else {
$country_state_id = $this->_getCountryId((string)$country_state_node['Iso']);
}
if ( !$country_state_id ) {
continue;
}
if ( $language_encoding == 'plain' ) {
$translation = rtrim($country_state_node['Translation']);
}
else {
$translation = base64_decode($country_state_node['Translation']);
}
$fields_hash = Array ('CountryStateId' => $country_state_id);
if ( array_key_exists($country_state_id, $other_translations) ) {
$other_translations[$country_state_id]['l' . $language_id . '_Name'] = $translation;
}
else {
$other_translations[$country_state_id] = Array ('l' . $language_id . '_Name' => $translation);
}
$fields_hash = array_merge($fields_hash, $other_translations[$country_state_id]);
$this->Conn->doInsert($fields_hash, $this->_tables['country-state'], 'REPLACE', false);
// PHP 5.3 version would be: $country_state_node->count()
if ( !$process_states && count($country_state_node->children()) ) {
$this->_processCountries($country_state_node, $language_id, $language_encoding, true);
}
}
$this->Conn->doInsert($fields_hash, $this->_tables['country-state'], 'REPLACE');
}
/**
* Creates/updates language based on given fields and returns it's id
*
* @param Array $fields_hash
* @return int
*/
function _processLanguage($fields_hash)
{
// 1. get language from database
$sql = 'SELECT ' . $this->lang_object->IDField . '
FROM ' . $this->lang_object->TableName . '
WHERE PackName = ' . $this->Conn->qstr($fields_hash['PackName']);
$language_id = $this->Conn->GetOne($sql);
if ($language_id) {
// 2. language found -> update, when allowed
$this->lang_object->Load($language_id);
if ($this->import_mode == LANG_OVERWRITE_EXISTING) {
// update live language record based on data from xml
$this->lang_object->SetFieldsFromHash($fields_hash);
$this->lang_object->Update();
}
}
else {
// 3. language not found -> create
$this->lang_object->SetFieldsFromHash($fields_hash);
$this->lang_object->SetDBField('Enabled', STATUS_ACTIVE);
if ($this->lang_object->Create()) {
$language_id = $this->lang_object->GetID();
if (defined('IS_INSTALL') && IS_INSTALL) {
// language created during install becomes admin interface language
$this->lang_object->setPrimary(true, true);
}
}
}
// 4. collect ID of every processed language
if (!in_array($language_id, $this->_languages)) {
$this->_languages[] = $language_id;
}
return $language_id;
}
/**
* Returns event id based on it's name and type
*
* @param string $event_name
* @param string $event_type
* @return int
*/
function _getEventId($event_name, $event_type)
{
$cache_key = $event_name . '_' . $event_type;
return array_key_exists($cache_key, $this->events_hash) ? $this->events_hash[$cache_key] : 0;
}
/**
* Returns country id based on it's 3letter ISO code
*
* @param string $iso
* @return int
*/
function _getCountryId($iso)
{
static $cache = null;
if (!isset($cache)) {
$sql = 'SELECT CountryStateId, IsoCode
FROM ' . TABLE_PREFIX . 'CountryStates
WHERE Type = ' . DESTINATION_TYPE_COUNTRY;
$cache = $this->Conn->GetCol($sql, 'IsoCode');
}
return array_key_exists($iso, $cache) ? $cache[$iso] : false;
}
/**
* Returns state id based on 3letter country ISO code and 2letter state ISO code
*
* @param string $country_iso
* @param string $state_iso
* @return int
*/
function _getStateId($country_iso, $state_iso)
{
static $cache = null;
if (!isset($cache)) {
$sql = 'SELECT CountryStateId, CONCAT(StateCountryId, "-", IsoCode) AS IsoCode
FROM ' . TABLE_PREFIX . 'CountryStates
WHERE Type = ' . DESTINATION_TYPE_STATE;
$cache = $this->Conn->GetCol($sql, 'IsoCode');
}
$country_id = $this->_getCountryId($country_iso);
return array_key_exists($country_id . '-' . $state_iso, $cache) ? $cache[$country_id . '-' . $state_iso] : false;
}
/**
* Returns comma-separated list of IDs, that will be exported
*
* @param string $prefix
* @return string
* @access public
*/
public function getExportIDs($prefix)
{
$ids = $this->Application->RecallVar($prefix . '_selected_ids');
if ( $ids ) {
// some records were selected in grid
return $ids;
}
$tag_params = Array (
'grid' => $prefix == 'phrases' ? 'Phrases' : 'Emails',
'skip_counting' => 1,
'per_page' => -1
);
$list = $this->Application->recallObject($prefix, $prefix . '_List', $tag_params);
/* @var $list kDBList */
$sql = $list->getCountSQL($list->GetSelectSQL());
$sql = str_replace('COUNT(*) AS count', $list->TableName . '.' . $list->IDField, $sql);
$ids = '';
- $rs = $this->Conn->QueryRaw($sql);
+ $rows = $this->Conn->GetIterator($sql);
- if ( $this->Conn->RowCount($rs) ) {
- while ( ($row = $this->Conn->GetNextRow($rs)) ) {
+ if ( count($rows) ) {
+ foreach ($rows as $row) {
$ids .= ',' . $row[$list->IDField];
}
$ids = substr($ids, 1);
}
- $this->Conn->Destroy($rs);
-
return $ids;
}
}
\ No newline at end of file

Event Timeline