Page MenuHomeIn-Portal Phabricator

temp_handler.php
No OneTemporary

File Metadata

Created
Sat, Feb 1, 8:58 AM

temp_handler.php

<?php
/**
* @version $Id: temp_handler.php 13603 2010-05-23 09:54:28Z alex $
* @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);
}
$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->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) {
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']);
}
}
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);
}
$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 == 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;
}
}

Event Timeline