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