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 @@ 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; } }