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) { $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']; $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'], '', $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); // reset ALL negative IDs to 0 so it get inserted into live table with autoincrement $query = 'UPDATE '.$this->GetTempName($master['TableName']).' SET '.$master['IdField'].' = 0 WHERE '.$master['IdField'].' < 0'; if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain']; $this->Conn->Query($query); // 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; /* !!! WE NEED TO FIND A WAY TO DETERMINE IF OnAfterCopyToLive is not an empty method, and do on-by-one insert and pass Ids to OnAfterCopyToLive, otherwise it's not smart to do on-by-one insert for any object OR WE COULD FIND A WAY TO GET ALL INSERTED IDS as an array and iterate them !!! $this->RaiseEvent('OnAfterCopyToLive', 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'] ]; } function UpdateForeignKeys($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']); } function SaveEdit($master_ids = Array()) { return $this->DoCopyTempToOriginal($this->Tables, null, $master_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); } } } } ?>