Conn =& $this->Application->GetADODBConnection(); } function SetTables($tables) { // set tablename as key for tables array $ret = Array(); $this->Tables = $tables; $this->MasterTable = $tables['TableName']; } /** * Get temp table name * * @param string $table * @return string */ function GetTempName($table) { // function is sometimes called as static, so we CAN'T use $this->GetTempTablePrefix() here return TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_edit_'.$table; } function GetTempTablePrefix() { return TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_edit_'; } /** * Return live table name based on temp table name * * @param string $temp_table * @return string */ function GetLiveName($temp_table) { if( preg_match('/'.TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_edit_(.*)/',$temp_table,$rets) ) { return $rets[1]; } else { return $temp_table; } } function IsTempTable($table) { return strpos($table, TABLE_PREFIX.'ses_'.$this->Application->GetSID().'_edit_') !== false; } /** * 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->TableIdCounter = 0; $tables = Array( 'TableName' => $this->Application->getUnitOption($prefix,'TableName'), 'IdField' => $this->Application->getUnitOption($prefix,'IDField'), 'IDs' => $ids, 'Prefix' => $prefix, 'TableId' => $this->TableIdCounter++, ); $SubItems = $this->Application->getUnitOption($prefix,'SubItems'); if (is_array($SubItems)) { foreach ($SubItems as $prefix) { $this->AddTables($prefix, $tables); } } $this->SetTables($tables); } function AddTables($prefix, &$tables) { $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) { 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'; $this->Application->setUnitOption($prefix, 'AutoLoad', false); $object =& $this->Application->recallObject($recall_prefix, $prefix); 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; $object->NameCopy($master, $foreign_key); 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'], 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'], Array($object->GetId()), $foreign_key ); } 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'], '', $sub_ids, $sub_table, $parent_key, $master['Prefix']); } } } } } 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'; $this->Application->setUnitOption($prefix,'AutoLoad',false); $object =& $this->Application->recallObject($recall_prefix, $prefix); 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']) ? $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]; $sub_ids = $this->Conn->GetCol($query); $parent_key = $object->GetDBField($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 ( $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)) { $query = 'SELECT '.$parent_key_field.' FROM '.$this->GetTempName($master['TableName']).' WHERE '.$master['IdField'].' IN ('.join(',', $temp_id).')'; $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) { $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->RaiseEvent( 'OnAfterCopyToLive', $master['Prefix'], Array($insert_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']); } function UpdateForeignKeys($master, $live_id, $temp_id) { foreach ($master['SubTables'] as $sub_table) { list ($live_foreign_key, $temp_foreign_key) = $this->GetForeignKeys($master, $sub_table, $live_id, $temp_id); $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey']; //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_keys = $this->GetForeignKeys($master, $sub_table, $current_ids); $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey']; 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']; $this->RaiseEvent( 'OnBeforeDeleteFromLive', $sub_table['Prefix'], $this->Conn->GetCol($query) ); $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, $ids, $foreign_key = null) { if ( !is_array($ids) ) return; $event = new kEvent( Array('name'=>$name, 'prefix'=>$prefix, 'special'=>'') ); if( isset($foreign_key) ) $event->setEventParam('foreign_key', $foreign_key); foreach($ids as $id) { $event->setEventParam('id', $id); $this->Application->HandleEvent($event); } } 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($skip_master=0) { $this->DoCopyTempToOriginal($this->Tables); } 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); } } } } ?>