Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Sat, Feb 22, 12:05 AM

in-portal

Index: branches/5.2.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.2.x/core/kernel/utility/temp_handler.php (revision 16732)
+++ branches/5.2.x/core/kernel/utility/temp_handler.php (revision 16733)
@@ -1,1087 +1,1112 @@
<?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!');
class kTempTablesHandler extends kBase {
var $Tables = Array();
/**
* Master table name for temp handler
*
* @var string
* @access private
*/
var $MasterTable = '';
/**
* IDs from master table
*
* @var Array
* @access private
*/
var $MasterIDs = Array();
var $AlreadyProcessed = Array();
var $DroppedTables = Array();
var $FinalRefs = Array();
var $TableIdCounter = 0;
var $CopiedTables = Array();
/**
* Foreign key cache
*
* @var Array
*/
var $FKeysCache = Array ();
/**
* IDs of newly cloned items (key - prefix.special, value - array of ids)
*
* @var Array
*/
var $savedIDs = Array();
/**
* Window ID of current window
*
* @var mixed
*/
var $WindowID = '';
/**
* Event, that was used to create this object
*
* @var kEvent
* @access protected
*/
protected $parentEvent = null;
/**
* Sets new parent event to the object
*
* @param kEvent $event
* @return void
* @access public
*/
public function setParentEvent($event)
{
$this->parentEvent = $event;
}
function SetTables($tables)
{
// set table name as key for tables array
$this->Tables = $tables;
$this->MasterTable = $tables['TableName'];
}
function saveID($prefix, $special = '', $id = null)
{
if (!isset($this->savedIDs[$prefix.($special ? '.' : '').$special])) {
$this->savedIDs[$prefix.($special ? '.' : '').$special] = array();
}
if (is_array($id)) {
foreach ($id as $tmp_id => $live_id) {
$this->savedIDs[$prefix.($special ? '.' : '').$special][$tmp_id] = $live_id;
}
}
else {
$this->savedIDs[$prefix.($special ? '.' : '').$special][] = $id;
}
}
/**
* Get temp table name
*
* @param string $table
* @return string
*/
function GetTempName($table)
{
return $this->Application->GetTempName($table, $this->WindowID);
}
function GetTempTablePrefix()
{
return $this->Application->GetTempTablePrefix($this->WindowID);
}
/**
* Return live table name based on temp table name
*
* @param string $temp_table
* @return string
*/
function GetLiveName($temp_table)
{
return $this->Application->GetLiveName($temp_table);
}
function IsTempTable($table)
{
return $this->Application->IsTempTable($table);
}
/**
* Return temporary table name for master table
*
* @return string
* @access public
*/
function GetMasterTempName()
{
return $this->GetTempName($this->MasterTable);
}
function CreateTempTable($table)
{
- $sql = 'CREATE TABLE ' . $this->GetTempName($table) . '
+ $sql = 'CREATE TABLE ' . $this->GetTempName($table) . ' ENGINE = ' . $this->getTableEngine($table) . '
SELECT *
FROM ' . $table . '
WHERE 0';
$this->Conn->Query($sql);
}
+ /**
+ * Returns an engine of a given table.
+ *
+ * @param string $table Table.
+ *
+ * @return string
+ */
+ protected function getTableEngine($table)
+ {
+ static $cache;
+
+ if ( $cache === null ) {
+ $sql = 'SHOW TABLE STATUS WHERE `Name` LIKE ' . $this->Conn->qstr(TABLE_PREFIX . '%');
+ $table_statuses = $this->Conn->GetIterator($sql, 'Name');
+
+ $cache = array();
+
+ foreach ( $table_statuses as $table_status_name => $table_status_data ) {
+ $cache[$table_status_name] = $table_status_data['Engine'];
+ }
+ }
+
+ return isset($cache[$table]) ? $cache[$table] : null;
+ }
+
function BuildTables($prefix, $ids)
{
$this->WindowID = $this->Application->GetVar('m_wid');
$this->TableIdCounter = 0;
$tables = Array(
'TableName' => $this->Application->getUnitOption($prefix, 'TableName'),
'IdField' => $this->Application->getUnitOption($prefix, 'IDField'),
'IDs' => $ids,
'Prefix' => $prefix,
'TableId' => $this->TableIdCounter++,
);
/*$parent_prefix = $this->Application->getUnitOption($prefix, 'ParentPrefix');
if ($parent_prefix) {
$tables['ForeignKey'] = $this->Application->getUnitOption($prefix, 'ForeignKey');
$tables['ParentPrefix'] = $parent_prefix;
$tables['ParentTableKey'] = $this->Application->getUnitOption($prefix, 'ParentTableKey');
}*/
$this->FinalRefs[ $tables['TableName'] ] = $tables['TableId']; // don't forget to add main table to FinalRefs too
/** @var Array $sub_items */
$sub_items = $this->Application->getUnitOption($prefix, 'SubItems', Array ());
if ( is_array($sub_items) ) {
foreach ($sub_items as $prefix) {
$this->AddTables($prefix, $tables);
}
}
$this->SetTables($tables);
}
/**
* Searches through TempHandler tables info for required prefix
*
* @param string $prefix
* @param Array $master
* @return mixed
*/
function SearchTable($prefix, $master = null)
{
if (is_null($master)) {
$master = $this->Tables;
}
if ($master['Prefix'] == $prefix) {
return $master;
}
if (isset($master['SubTables'])) {
foreach ($master['SubTables'] as $sub_table) {
$found = $this->SearchTable($prefix, $sub_table);
if ($found !== false) {
return $found;
}
}
}
return false;
}
function AddTables($prefix, &$tables)
{
if ( !$this->Application->prefixRegistred($prefix) ) {
// allows to skip subitem processing if subitem module not enabled/installed
return ;
}
$tmp = Array(
'TableName' => $this->Application->getUnitOption($prefix,'TableName'),
'IdField' => $this->Application->getUnitOption($prefix,'IDField'),
'ForeignKey' => $this->Application->getUnitOption($prefix,'ForeignKey'),
'ParentPrefix' => $this->Application->getUnitOption($prefix, 'ParentPrefix'),
'ParentTableKey' => $this->Application->getUnitOption($prefix,'ParentTableKey'),
'Prefix' => $prefix,
'AutoClone' => $this->Application->getUnitOption($prefix,'AutoClone'),
'AutoDelete' => $this->Application->getUnitOption($prefix,'AutoDelete'),
'TableId' => $this->TableIdCounter++,
);
$this->FinalRefs[ $tmp['TableName'] ] = $tmp['TableId'];
$constrain = $this->Application->getUnitOption($prefix, 'Constrain');
if ( $constrain ) {
$tmp['Constrain'] = $constrain;
$this->FinalRefs[ $tmp['TableName'] . $tmp['Constrain'] ] = $tmp['TableId'];
}
/** @var Array $sub_items */
$sub_items = $this->Application->getUnitOption($prefix, 'SubItems', Array ());
if ( is_array($sub_items) ) {
foreach ($sub_items as $prefix) {
$this->AddTables($prefix, $tmp);
}
}
if ( !is_array(getArrayValue($tables, 'SubTables')) ) {
$tables['SubTables'] = Array ();
}
$tables['SubTables'][] = $tmp;
}
function CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false)
{
if (!isset($master)) $master = $this->Tables;
// recalling by different name, because we may get kDBList, if we recall just by prefix
if (!preg_match('/(.*)-item$/', $special)) {
$special .= '-item';
}
/** @var kCatDBItem $object */
$object = $this->Application->recallObject($prefix.'.'.$special, $prefix, Array('skip_autoload' => true, 'parent_event' => $this->parentEvent));
$object->PopulateMultiLangFields();
foreach ($ids as $id) {
$mode = 'create';
$cloned_ids = getArrayValue($this->AlreadyProcessed, $master['TableName']);
if ( $cloned_ids ) {
// if we have already cloned the id, replace it with cloned id and set mode to update
// update mode is needed to update second ForeignKey for items cloned by first ForeignKey
if ( getArrayValue($cloned_ids, $id) ) {
$id = $cloned_ids[$id];
$mode = 'update';
}
}
$object->Load($id);
$original_values = $object->GetFieldValues();
if (!$skip_filenames) {
$object->NameCopy($master, $foreign_key);
}
elseif ($master['TableName'] == $this->MasterTable) {
// kCatDBItem class only has this attribute
$object->useFilenames = false;
}
if (isset($foreign_key)) {
$master_foreign_key_field = is_array($master['ForeignKey']) ? $master['ForeignKey'][$parent_prefix] : $master['ForeignKey'];
$object->SetDBField($master_foreign_key_field, $foreign_key);
}
if ($mode == 'create') {
$this->RaiseEvent('OnBeforeClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key);
}
$object->inCloning = true;
$res = $mode == 'update' ? $object->Update() : $object->Create();
$object->inCloning = false;
if ($res)
{
if ( $mode == 'create' && is_array( getArrayValue($master, 'ForeignKey')) ) {
// remember original => clone mapping for dual ForeignKey updating
$this->AlreadyProcessed[$master['TableName']][$id] = $object->GetId();
}
if ($mode == 'create') {
$this->RaiseEvent('OnAfterClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key, array('original_id' => $id) );
$this->saveID($master['Prefix'], $special, $object->GetID());
}
if ( is_array(getArrayValue($master, 'SubTables')) ) {
foreach($master['SubTables'] as $sub_table) {
if (!getArrayValue($sub_table, 'AutoClone')) continue;
$sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
$foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey'];
$parent_key_field = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey'];
if (!$foreign_key_field || !$parent_key_field) continue;
$query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.'
WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field];
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
$sub_ids = $this->Conn->GetCol($query);
if ( is_array(getArrayValue($sub_table, 'ForeignKey')) ) {
// $sub_ids could containt newly cloned items, we need to remove it here
// to escape double cloning
$cloned_ids = getArrayValue($this->AlreadyProcessed, $sub_table['TableName']);
if ( !$cloned_ids ) $cloned_ids = Array();
$new_ids = array_values($cloned_ids);
$sub_ids = array_diff($sub_ids, $new_ids);
}
$parent_key = $object->GetDBField($parent_key_field);
$this->CloneItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key, $master['Prefix']);
}
}
}
}
if (!$ids) {
$this->savedIDs[$prefix.($special ? '.' : '').$special] = Array();
}
return $this->savedIDs[$prefix.($special ? '.' : '').$special];
}
function DeleteItems($prefix, $special, $ids, $master=null, $foreign_key=null)
{
if ( !$ids ) {
return;
}
if ( !isset($master) ) {
$master = $this->Tables;
}
if ( strpos($prefix, '.') !== false ) {
list($prefix, $special) = explode('.', $prefix, 2);
}
$prefix_special = rtrim($prefix . '.' . $special, '.');
//recalling by different name, because we may get kDBList, if we recall just by prefix
$recall_prefix = $prefix_special . ($special ? '' : '.') . '-item';
/** @var kDBItem $object */
$object = $this->Application->recallObject($recall_prefix, $prefix, Array ('skip_autoload' => true, 'parent_event' => $this->parentEvent));
foreach ($ids as $id) {
$object->Load($id);
$original_values = $object->GetFieldValues();
if ( !$object->Delete($id) ) {
continue;
}
if ( is_array(getArrayValue($master, 'SubTables')) ) {
foreach ($master['SubTables'] as $sub_table) {
if ( !getArrayValue($sub_table, 'AutoDelete') ) {
continue;
}
$sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
$parent_key_field = is_array($sub_table['ParentTableKey']) ? getArrayValue($sub_table, 'ParentTableKey', $master['Prefix']) : $sub_table['ParentTableKey'];
if ( !$foreign_key_field || !$parent_key_field ) {
continue;
}
$sql = 'SELECT ' . $sub_table['IdField'] . '
FROM ' . $sub_TableName . '
WHERE ' . $foreign_key_field . ' = ' . $original_values[$parent_key_field];
$sub_ids = $this->Conn->GetCol($sql);
$parent_key = $object->GetDBField(is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$prefix] : $sub_table['ParentTableKey']);
$this->DeleteItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key);
}
}
}
}
function DoCopyLiveToTemp($master, $ids, $parent_prefix=null)
{
// when two tables refers the same table as sub-sub-table, and ForeignKey and ParentTableKey are arrays
// the table will be first copied by first sub-table, then dropped and copied over by last ForeignKey in the array
// this should not do any problems :)
if ( !preg_match("/.*\.[0-9]+/", $master['Prefix']) ) {
if( $this->DropTempTable($master['TableName']) )
{
$this->CreateTempTable($master['TableName']);
}
}
if (is_array($ids)) {
$ids = join(',', $ids);
}
$table_sig = $master['TableName'].(isset($master['Constrain']) ? $master['Constrain'] : '');
if ($ids != '' && !in_array($table_sig, $this->CopiedTables)) {
if ( getArrayValue($master, 'ForeignKey') ) {
if ( is_array($master['ForeignKey']) ) {
$key_field = $master['ForeignKey'][$parent_prefix];
}
else {
$key_field = $master['ForeignKey'];
}
}
else {
$key_field = $master['IdField'];
}
$query = 'INSERT INTO '.$this->GetTempName($master['TableName']).'
SELECT * FROM '.$master['TableName'].'
WHERE '.$key_field.' IN ('.$ids.')';
if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain'];
$this->Conn->Query($query);
$this->CopiedTables[] = $table_sig;
$query = 'SELECT '.$master['IdField'].' FROM '.$master['TableName'].'
WHERE '.$key_field.' IN ('.$ids.')';
if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain'];
$this->RaiseEvent( 'OnAfterCopyToTemp', $master['Prefix'], '', $this->Conn->GetCol($query) );
}
if ( getArrayValue($master, 'SubTables') ) {
foreach ($master['SubTables'] as $sub_table) {
$parent_key = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey'];
if (!$parent_key) continue;
if ( $ids != '' && $parent_key != $key_field ) {
$query = 'SELECT '.$parent_key.' FROM '.$master['TableName'].'
WHERE '.$key_field.' IN ('.$ids.')';
$sub_foreign_keys = join(',', $this->Conn->GetCol($query));
}
else {
$sub_foreign_keys = $ids;
}
$this->DoCopyLiveToTemp($sub_table, $sub_foreign_keys, $master['Prefix']);
}
}
}
function GetForeignKeys($master, $sub_table, $live_id, $temp_id=null)
{
$mode = 1; //multi
if (!is_array($live_id)) {
$live_id = Array($live_id);
$mode = 2; //single
}
if (isset($temp_id) && !is_array($temp_id)) $temp_id = Array($temp_id);
if ( isset($sub_table['ParentTableKey']) ) {
if ( is_array($sub_table['ParentTableKey']) ) {
$parent_key_field = $sub_table['ParentTableKey'][$master['Prefix']];
}
else {
$parent_key_field = $sub_table['ParentTableKey'];
}
}
else {
$parent_key_field = $master['IdField'];
}
$cached = getArrayValue($this->FKeysCache, $master['TableName'].'.'.$parent_key_field);
if ( $cached ) {
if ( array_key_exists(serialize($live_id), $cached) ) {
list($live_foreign_key, $temp_foreign_key) = $cached[serialize($live_id)];
if ($mode == 1) {
return $live_foreign_key;
}
else {
return Array($live_foreign_key[0], $temp_foreign_key[0]);
}
}
}
if ($parent_key_field != $master['IdField']) {
$query = 'SELECT '.$parent_key_field.' FROM '.$master['TableName'].'
WHERE '.$master['IdField'].' IN ('.join(',', $live_id).')';
$live_foreign_key = $this->Conn->GetCol($query);
if (isset($temp_id)) {
// because DoCopyTempToOriginal resets negative IDs to 0 in temp table (one by one) before copying to live
$temp_key = $temp_id < 0 ? 0 : $temp_id;
$query = 'SELECT '.$parent_key_field.' FROM '.$this->GetTempName($master['TableName']).'
WHERE '.$master['IdField'].' IN ('.join(',', $temp_key).')';
$temp_foreign_key = $this->Conn->GetCol($query);
}
else {
$temp_foreign_key = Array();
}
}
else {
$live_foreign_key = $live_id;
$temp_foreign_key = $temp_id;
}
$this->FKeysCache[$master['TableName'].'.'.$parent_key_field][serialize($live_id)] = Array($live_foreign_key, $temp_foreign_key);
if ($mode == 1) {
return $live_foreign_key;
}
else {
return Array($live_foreign_key[0], $temp_foreign_key[0]);
}
}
/**
* Copies data from temp to live table and returns IDs of copied records
*
* @param Array $master
* @param string $parent_prefix
* @param Array $current_ids
* @return Array
* @access public
*/
public function DoCopyTempToOriginal($master, $parent_prefix = null, $current_ids = Array())
{
$current_ids = $this->getMainIDs($master, $current_ids);
$table_sig = $master['TableName'] . (isset($master['Constrain']) ? $master['Constrain'] : '');
if ($current_ids) {
// delete all ids from live table - for MasterTable ONLY!
// because items from Sub Tables get deteleted in CopySubTablesToLive !BY ForeignKey!
if ( $master['TableName'] == $this->MasterTable ) {
$this->RaiseEvent('OnBeforeDeleteFromLive', $master['Prefix'], '', $current_ids);
$query = 'DELETE FROM ' . $master['TableName'] . ' WHERE ' . $master['IdField'] . ' IN (' . join(',', $current_ids) . ')';
$this->Conn->Query($query);
}
if ( getArrayValue($master, 'SubTables') ) {
if ( in_array($table_sig, $this->CopiedTables) || $this->FinalRefs[$table_sig] != $master['TableId'] ) {
return Array ();
}
foreach ($current_ids AS $id) {
$this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', Array ($id));
//reset negative ids to 0, so autoincrement in live table works fine
if ( $id < 0 ) {
$query = ' UPDATE ' . $this->GetTempName($master['TableName']) . '
SET ' . $master['IdField'] . ' = 0
WHERE ' . $master['IdField'] . ' = ' . $id;
if ( isset($master['Constrain']) ) {
$query .= ' AND ' . $master['Constrain'];
}
$this->Conn->Query($query);
$id_to_copy = 0;
}
else {
$id_to_copy = $id;
}
//copy current id_to_copy (0 for new or real id) to live table
$query = ' INSERT INTO ' . $master['TableName'] . '
SELECT * FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = ' . $id_to_copy;
$this->Conn->Query($query);
$insert_id = $id_to_copy == 0 ? $this->Conn->getInsertID() : $id_to_copy;
$this->saveID($master['Prefix'], '', array ($id => $insert_id));
$this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', Array ($insert_id), null, Array ('temp_id' => $id));
$this->UpdateForeignKeys($master, $insert_id, $id);
//delete already copied record from master temp table
$query = ' DELETE FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = ' . $id_to_copy;
if ( isset($master['Constrain']) ) {
$query .= ' AND ' . $master['Constrain'];
}
$this->Conn->Query($query);
}
$this->CopiedTables[] = $table_sig;
// when all of ids in current master has been processed, copy all sub-tables data
$this->CopySubTablesToLive($master, $current_ids);
}
elseif ( !in_array($table_sig, $this->CopiedTables) && ($this->FinalRefs[$table_sig] == $master['TableId']) ) { //If current master doesn't have sub-tables - we could use mass operations
// We don't need to delete items from live here, as it get deleted in the beginning of the method for MasterTable
// or in parent table processing for sub-tables
$live_ids = Array ();
$this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', $current_ids);
foreach ($current_ids as $an_id) {
if ( $an_id > 0 ) {
$live_ids[$an_id] = $an_id;
// positive (already live) IDs will be copied in on query all togather below,
// so we just store it here
continue;
}
else { // zero or negative ids should be copied one by one to get their InsertId
// resetting to 0 so it get inserted into live table with autoincrement
$query = ' UPDATE ' . $this->GetTempName($master['TableName']) . '
SET ' . $master['IdField'] . ' = 0
WHERE ' . $master['IdField'] . ' = ' . $an_id;
// constrain is not needed here because ID is already unique
$this->Conn->Query($query);
// copying
$query = ' INSERT INTO ' . $master['TableName'] . '
SELECT * FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = 0';
$this->Conn->Query($query);
$live_ids[$an_id] = $this->Conn->getInsertID(); //storing newly created live id
//delete already copied record from master temp table
$query = ' DELETE FROM ' . $this->GetTempName($master['TableName']) . '
WHERE ' . $master['IdField'] . ' = 0';
$this->Conn->Query($query);
$this->UpdateChangeLogForeignKeys($master, $live_ids[$an_id], $an_id);
}
}
// copy ALL records to live table
$query = ' INSERT INTO ' . $master['TableName'] . '
SELECT * FROM ' . $this->GetTempName($master['TableName']);
if ( isset($master['Constrain']) ) {
$query .= ' WHERE ' . $master['Constrain'];
}
$this->Conn->Query($query);
$this->CopiedTables[] = $table_sig;
$this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', $live_ids);
$this->saveID($master['Prefix'], '', $live_ids);
// no need to clear temp table - it will be dropped by next statement
}
}
if ( $this->FinalRefs[ $master['TableName'] ] != $master['TableId'] ) {
return Array ();
}
/*if ( is_array(getArrayValue($master, 'ForeignKey')) ) { //if multiple ForeignKeys
if ( $master['ForeignKey'][$parent_prefix] != end($master['ForeignKey']) ) {
return; // Do not delete temp table if not all ForeignKeys have been processed (current is not the last)
}
}*/
$this->DropTempTable($master['TableName']);
$this->Application->resetCounters($master['TableName']);
if ( !isset($this->savedIDs[ $master['Prefix'] ]) ) {
$this->savedIDs[ $master['Prefix'] ] = Array ();
}
return $this->savedIDs[ $master['Prefix'] ];
}
/**
* Returns IDs from main temp table.
*
* @param array $master Master table configuration.
* @param array $current_ids User provided IDs.
*
* @return array
*/
protected function getMainIDs(array $master, array $current_ids = array())
{
if ( $current_ids ) {
return $current_ids;
}
$sql = 'SELECT ' . $master['IdField'] . '
FROM ' . $this->GetTempName($master['TableName']);
if ( isset($master['Constrain']) ) {
$sql .= ' WHERE ' . $master['Constrain'];
}
return $this->Conn->GetCol($sql);
}
/**
* Create separate connection for locking purposes
*
* @return kDBConnection
*/
function &_getSeparateConnection()
{
static $connection = null;
if (!isset($connection)) {
/** @var kDBConnection $connection */
$connection = $this->Application->makeClass( 'kDBConnection', Array (SQL_TYPE, Array ($this->Application, 'handleSQLError')) );
$connection->debugMode = $this->Application->isDebugMode();
$connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB);
}
return $connection;
}
function UpdateChangeLogForeignKeys($master, $live_id, $temp_id)
{
if ($live_id == $temp_id) {
return ;
}
$prefix = $master['Prefix'];
$main_prefix = $this->Application->GetTopmostPrefix($prefix);
$ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
$changes = $this->Application->RecallVar($ses_var_name);
$changes = $changes ? unserialize($changes) : Array ();
foreach ($changes as $key => $rec) {
if ($rec['Prefix'] == $prefix && $rec['ItemId'] == $temp_id) {
// main item change log record
$changes[$key]['ItemId'] = $live_id;
}
if ($rec['MasterPrefix'] == $prefix && $rec['MasterId'] == $temp_id) {
// sub item change log record
$changes[$key]['MasterId'] = $live_id;
}
if (in_array($prefix, $rec['ParentPrefix']) && $rec['ParentId'][$prefix] == $temp_id) {
// parent item change log record
$changes[$key]['ParentId'][$prefix] = $live_id;
if (array_key_exists('DependentFields', $rec)) {
// these are fields from table of $rec['Prefix'] table!
// when one of dependent fields goes into idfield of it's parent item, that was changed
$parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$prefix] : $parent_table_key;
if ($parent_table_key == $master['IdField']) {
$foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
$foreign_key = is_array($foreign_key) ? $foreign_key[$prefix] : $foreign_key;
$changes[$key]['DependentFields'][$foreign_key] = $live_id;
}
}
}
}
$this->Application->StoreVar($ses_var_name, serialize($changes));
}
function UpdateForeignKeys($master, $live_id, $temp_id)
{
$this->UpdateChangeLogForeignKeys($master, $live_id, $temp_id);
foreach ($master['SubTables'] as $sub_table) {
$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
if (!$foreign_key_field) {
continue;
}
list ($live_foreign_key, $temp_foreign_key) = $this->GetForeignKeys($master, $sub_table, $live_id, $temp_id);
//Update ForeignKey in sub TEMP table
if ($live_foreign_key != $temp_foreign_key) {
$query = 'UPDATE '.$this->GetTempName($sub_table['TableName']).'
SET '.$foreign_key_field.' = '.$live_foreign_key.'
WHERE '.$foreign_key_field.' = '.$temp_foreign_key;
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
$this->Conn->Query($query);
}
}
}
function CopySubTablesToLive($master, $current_ids) {
foreach ($master['SubTables'] as $sub_table) {
$table_sig = $sub_table['TableName'].(isset($sub_table['Constrain']) ? $sub_table['Constrain'] : '');
// delete records from live table by foreign key, so that records deleted from temp table
// get deleted from live
if (count($current_ids) > 0 && !in_array($table_sig, $this->CopiedTables) ) {
$foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
if (!$foreign_key_field) continue;
$foreign_keys = $this->GetForeignKeys($master, $sub_table, $current_ids);
if (count($foreign_keys) > 0) {
$query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_table['TableName'].'
WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')';
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
if ( $this->RaiseEvent( 'OnBeforeDeleteFromLive', $sub_table['Prefix'], '', $this->Conn->GetCol($query), $foreign_keys ) ){
$query = 'DELETE FROM '.$sub_table['TableName'].'
WHERE '.$foreign_key_field.' IN ('.join(',', $foreign_keys).')';
if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
$this->Conn->Query($query);
}
}
}
//sub_table passed here becomes master in the method, and recursively updated and copy its sub tables
$this->DoCopyTempToOriginal($sub_table, $master['Prefix']);
}
}
/**
* Raises event using IDs, that are currently being processed in temp handler
*
* @param string $name
* @param string $prefix
* @param string $special
* @param Array $ids
* @param string $foreign_key
* @param Array $add_params
* @return bool
* @access protected
*/
protected function RaiseEvent($name, $prefix, $special, $ids, $foreign_key = null, $add_params = null)
{
if ( !is_array($ids) ) {
return true;
}
$event_key = $prefix . ($special ? '.' : '') . $special . ':' . $name;
$event = new kEvent($event_key);
$event->MasterEvent = $this->parentEvent;
if ( isset($foreign_key) ) {
$event->setEventParam('foreign_key', $foreign_key);
}
$set_temp_id = ($name == 'OnAfterCopyToLive') && (!is_array($add_params) || !array_key_exists('temp_id', $add_params));
foreach ($ids as $index => $id) {
$event->setEventParam('id', $id);
if ( $set_temp_id ) {
$event->setEventParam('temp_id', $index);
}
if ( is_array($add_params) ) {
foreach ($add_params as $name => $val) {
$event->setEventParam($name, $val);
}
}
$this->Application->HandleEvent($event);
}
return $event->status == kEvent::erSUCCESS;
}
function DropTempTable($table)
{
if ( in_array($table, $this->DroppedTables) ) {
return false;
}
$query = 'DROP TABLE IF EXISTS ' . $this->GetTempName($table);
array_push($this->DroppedTables, $table);
$this->DroppedTables = array_unique($this->DroppedTables);
$this->Conn->Query($query);
return true;
}
function PrepareEdit()
{
$this->DoCopyLiveToTemp($this->Tables, $this->Tables['IDs']);
if ($this->Application->getUnitOption($this->Tables['Prefix'],'CheckSimulatniousEdit')) {
$this->CheckSimultaniousEdit();
}
}
function SaveEdit($master_ids = Array())
{
// SessionKey field is required for deleting records from expired sessions
$conn =& $this->_getSeparateConnection();
$sleep_count = 0;
$master_ids = $this->getMainIDs($this->Tables, $master_ids);
do {
// acquire lock
$conn->ChangeQuery('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
$another_coping_active = false;
$sql = 'SELECT MainIDs
FROM ' . TABLE_PREFIX . 'Semaphores
WHERE MainPrefix = ' . $conn->qstr($this->Tables['Prefix']) . ' AND MainIDs <> "0"';
$other_semaphores_main_ids = $conn->GetCol($sql);
foreach ( $other_semaphores_main_ids as $other_semaphore_main_ids ) {
$other_semaphore_main_ids = explode(',', $other_semaphore_main_ids);
if ( array_intersect($master_ids, $other_semaphore_main_ids) ) {
$another_coping_active = true;
break;
}
}
if ($another_coping_active) {
// another user is coping data from temp table to live -> release lock and try again after 1 second
$conn->ChangeQuery('UNLOCK TABLES');
$sleep_count++;
sleep(1);
}
} while ($another_coping_active && ($sleep_count <= 30));
if ($sleep_count > 30) {
// another coping process failed to finished in 30 seconds
$error_message = $this->Application->Phrase('la_error_TemporaryTableCopyingFailed');
$this->Application->SetVar('_temp_table_message', $error_message);
return false;
}
// mark, that we are coping from temp to live right now, so other similar attempt (from another script) will fail
$fields_hash = Array (
'SessionKey' => $this->Application->GetSID(),
'Timestamp' => adodb_mktime(),
'MainPrefix' => $this->Tables['Prefix'],
'MainIDs' => implode(',', $master_ids),
);
$conn->doInsert($fields_hash, TABLE_PREFIX.'Semaphores');
$semaphore_id = $conn->getInsertID();
// unlock table now to prevent permanent lock in case, when coping will end with SQL error in the middle
$conn->ChangeQuery('UNLOCK TABLES');
$ids = $this->DoCopyTempToOriginal($this->Tables, null, $master_ids);
// remove mark, that we are coping from temp to live
$conn->Query('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
$sql = 'DELETE FROM ' . TABLE_PREFIX . 'Semaphores
WHERE SemaphoreId = ' . $semaphore_id;
$conn->ChangeQuery($sql);
$conn->ChangeQuery('UNLOCK TABLES');
return $ids;
}
function CancelEdit($master=null)
{
if (!isset($master)) $master = $this->Tables;
$this->DropTempTable($master['TableName']);
if ( getArrayValue($master, 'SubTables') ) {
foreach ($master['SubTables'] as $sub_table) {
$this->CancelEdit($sub_table);
}
}
}
/**
* Checks, that someone is editing selected records and returns true, when no one.
*
* @param Array $ids
*
* @return bool
*/
function CheckSimultaniousEdit($ids = null)
{
$tables = $this->Conn->GetCol('SHOW TABLES');
$mask_edit_table = '/' . TABLE_PREFIX . 'ses_(.*)_edit_' . $this->MasterTable . '$/';
$my_sid = $this->Application->GetSID();
$my_wid = $this->Application->GetVar('m_wid');
$ids = implode(',', isset($ids) ? $ids : $this->Tables['IDs']);
$sids = Array ();
if (!$ids) {
return true;
}
foreach ($tables as $table) {
if ( preg_match($mask_edit_table, $table, $rets) ) {
$sid = preg_replace('/(.*)_(.*)/', '\\1', $rets[1]); // remove popup's wid from sid
if ($sid == $my_sid) {
if ($my_wid) {
// using popups for editing
if (preg_replace('/(.*)_(.*)/', '\\2', $rets[1]) == $my_wid) {
// don't count window, that is being opened right now
continue;
}
}
else {
// not using popups for editing -> don't count my session tables
continue;
}
}
$sql = 'SELECT COUNT(' . $this->Tables['IdField'] . ')
FROM ' . $table . '
WHERE ' . $this->Tables['IdField'] . ' IN (' . $ids . ')';
$found = $this->Conn->GetOne($sql);
if (!$found || in_array($sid, $sids)) {
continue;
}
$sids[] = $sid;
}
}
if ($sids) {
// detect who is it
$sql = 'SELECT
CONCAT(IF (s.PortalUserId = ' . USER_ROOT . ', \'root\',
IF (s.PortalUserId = ' . USER_GUEST . ', \'Guest\',
CONCAT(u.FirstName, \' \', u.LastName, \' (\', u.Username, \')\')
)
), \' IP: \', s.IpAddress, \'\') FROM ' . TABLE_PREFIX . 'UserSessions AS s
LEFT JOIN ' . TABLE_PREFIX . 'Users AS u
ON u.PortalUserId = s.PortalUserId
WHERE s.SessionKey IN (' . implode(',', $sids) . ')';
$users = $this->Conn->GetCol($sql);
if ($users) {
$this->Application->SetVar('_simultaneous_edit_message',
sprintf($this->Application->Phrase('la_record_being_edited_by'), join(",\n", $users))
);
return false;
}
}
return true;
}
}

Event Timeline