Index: branches/5.0.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.0.x/core/kernel/utility/temp_handler.php	(revision 12775)
+++ branches/5.0.x/core/kernel/utility/temp_handler.php	(revision 12776)
@@ -1,932 +1,934 @@
 <?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 $CopiedTables =  Array();
 
 
 	/**
 	 * IDs of newly cloned items (key - prefix.special, value - array of ids)
 	 *
 	 * @var Array
 	 */
 	var $savedIDs = Array();
 
 	/**
 	* Description
 	*
 	* @var kDBConnection
 	* @access public
 	*/
 	var $Conn;
 
 
 	/**
 	 * Window ID of current window
 	 *
 	 * @var mixed
 	 */
 	var $WindowID = '';
 
 	function kTempTablesHandler()
 	{
 		parent::kBase();
 		$this->Conn =& $this->Application->GetADODBConnection();
 	}
 
 	function SetTables($tables)
 	{
 		// set tablename as key for tables array
 		$ret = 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)
 	{
 		$query = sprintf("CREATE TABLE %s SELECT * FROM %s WHERE 0",
 										$this->GetTempName($table),
 										$table);
 
 		$this->Conn->Query($query);
 	}
 
 	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
 
 		$SubItems = $this->Application->getUnitOption($prefix,'SubItems');
 		if (is_array($SubItems)) {
 			foreach ($SubItems 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'];
 		}
 
 		$SubItems = $this->Application->getUnitOption($prefix,'SubItems');
 		$same_sub_counter = 1;
 		if( is_array($SubItems) )
 		{
 			foreach($SubItems 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';
 		}
 
 		$object =& $this->Application->recallObject($prefix.'.'.$special, $prefix, Array('skip_autoload' => true));
 		$object->PopulateMultiLangFields();
 
 		foreach ($ids as $id) {
 			$mode = 'create';
 			if ( $cloned_ids = getArrayValue($this->AlreadyProcessed, $master['TableName']) ) {
 				// 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->FieldValues;
 
 			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);
 			}
 
 			$res = $mode == 'update' ? $object->Update() : $object->Create();
 
 			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 ($object->mode == 't') {
 					$object->setTempID();
 				}
 				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->mode == 't') ? $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 (!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';
 		$object =& $this->Application->recallObject($recall_prefix, $prefix, Array('skip_autoload' => true));
 
 		foreach ($ids as $id)
 		{
 			$object->Load($id);
 			$original_values = $object->FieldValues;
 			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->mode == 't') ? $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;
 
 					$query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.'
 										WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field];
 
 					$sub_ids = $this->Conn->GetCol($query);
 
 					$parent_key = $object->GetDBField(is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$prefix] : $sub_table['ParentTableKey']);
 
 					$this->DeleteItems($sub_table['Prefix'], '', $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'];
 		}
 
 		if ( $cached = getArrayValue($this->FKeysCache, $master['TableName'].'.'.$parent_key_field) ) {
 			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]);
 		}
 	}
 
 	function DoCopyTempToOriginal($master, $parent_prefix = null, $current_ids = Array())
 	{
 		if (!$current_ids) {
 			$query = 'SELECT '.$master['IdField'].' FROM '.$this->GetTempName($master['TableName']);
 			if (isset($master['Constrain'])) $query .= ' WHERE '.$master['Constrain'];
 			$current_ids = $this->Conn->GetCol($query);
 		}
 
 		$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;
 
 				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 beggining of the method for MasterTable
 				// or in parent table processing for sub-tables
 				$this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', $current_ids);
 
 				$live_ids = array();
 				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 negaitve ids should be copied one by one to get their InsertId
 						// reseting 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;
 
 		/*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'] ];
 	}
 
 	/**
 	 * Create separate connection for locking purposes
 	 *
 	 * @return kDBConnection
 	 */
 	function &_getSeparateConnection()
 	{
 		static $connection = null;
 
 		if (!isset($connection)) {
 			$connection = new kDBConnection(SQL_TYPE, Array(&$this->Application, 'handleSQLError') );
 			$connection->debugMode = $this->Application->isDebugMode();
 			$connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB, true);
 		}
 
 		return $connection;
 	}
 
 	function UpdateChangeLogForeignKeys($master, $live_id, $temp_id)
 	{
 		$main_prefix = $this->Application->GetTopmostPrefix($master['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'] == $master['Prefix']) {
 				if ($rec['ItemId'] == $temp_id) {
 					$changes[$key]['ItemId'] = $live_id;
 				}
 			}
 			if ($rec['MasterPrefix'] == $master['Prefix']) {
 				if ($rec['MasterId'] == $temp_id) {
 					$changes[$key]['MasterId'] = $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) return;
 
 			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']);
 		}
 	}
 
 	function RaiseEvent($name, $prefix, $special, $ids, $foreign_key = null, $add_params = null)
 	{
 		if ( !is_array($ids) ) return ;
 
 		$event_key = $prefix.($special ? '.' : '').$special.':'.$name;
 		$event = new kEvent($event_key);
 		if (isset($foreign_key)) {
 			$event->setEventParam('foreign_key', $foreign_key);
 		}
 
 		foreach($ids as $id)
 		{
 			$event->setEventParam('id', $id);
 			if (is_array($add_params)) {
 				foreach ($add_params as $name => $val) {
 					$event->setEventParam($name, $val);
 				}
 			}
 			$this->Application->HandleEvent($event);
 		}
 		return $event->status == erSUCCESS;
 	}
 
 	function DropTempTable($table)
 	{
 		if( in_array($table, $this->DroppedTables) ) return false;
 		$query = sprintf("DROP TABLE IF EXISTS %s",
 											$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;
 		do {
 			// aquire lock
 			$conn->ChangeQuery('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
 
 			$sql = 'SELECT SessionKey
 					FROM ' . TABLE_PREFIX . 'Semaphores
 					WHERE (MainPrefix = ' . $conn->qstr($this->Tables['Prefix']) . ')';
 			$another_coping_active = $conn->GetOne($sql);
 
 			if ($another_coping_active) {
 				// another user is coping data from temp table to live -> release lock and try again after 1 second
 				$conn->ChangeQuery('UNLOCK TABLES');
 				$sleep_count++;
 				sleep(1);
 			}
 		} while ($another_coping_active && ($sleep_count <= 30));
 
 		if ($sleep_count > 30) {
 			// another coping process failed to finished in 30 seconds
 			$error_message = $this->Application->Phrase('la_error_TemporaryTableCopingFailed');
 			$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'],
 		);
 
 		$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 = -1, \'root\',
 							IF (s.PortalUserId = -2, \'Guest\',
 								CONCAT(FirstName, \' \', LastName, \' (\', Login, \')\')
 							)
 						), \' IP: \', s.IpAddress, \'\') FROM ' . TABLE_PREFIX . 'UserSession AS s
 					LEFT JOIN ' . TABLE_PREFIX . 'PortalUser AS u
 					ON u.PortalUserId = s.PortalUserId
 					WHERE s.SessionKey IN (' . implode(',', $sids) . ')';
 			$users = $this->Conn->GetCol($sql);
 
 			if ($users) {
 				$this->Application->SetVar('_simultanious_edit_message',
 					sprintf($this->Application->Phrase('la_record_being_edited_by'), join(",\n", $users))
 				);
 
 				return false;
 			}
 		}
 
 		return true;
 	}
 
 }
\ No newline at end of file