Index: branches/5.0.x/core/kernel/utility/temp_handler.php =================================================================== --- branches/5.0.x/core/kernel/utility/temp_handler.php (revision 13602) +++ branches/5.0.x/core/kernel/utility/temp_handler.php (revision 13603) @@ -1,946 +1,944 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kTempTablesHandler extends kBase { var $Tables = Array(); /** * Master table name for temp handler * * @var string * @access private */ var $MasterTable = ''; /** * IDs from master table * * @var Array * @access private */ var $MasterIDs = Array(); var $AlreadyProcessed = Array(); var $DroppedTables = Array(); var $FinalRefs = Array(); var $CopiedTables = Array(); /** * IDs of newly cloned items (key - prefix.special, value - array of ids) * * @var Array */ var $savedIDs = Array(); /** * Description * * @var kDBConnection * @access public */ var $Conn; /** * Window ID of current window * * @var mixed */ var $WindowID = ''; function kTempTablesHandler() { parent::kBase(); $this->Conn =& $this->Application->GetADODBConnection(); } function SetTables($tables) { // set tablename as key for tables array $ret = Array(); $this->Tables = $tables; $this->MasterTable = $tables['TableName']; } function saveID($prefix, $special = '', $id = null) { if (!isset($this->savedIDs[$prefix.($special ? '.' : '').$special])) { $this->savedIDs[$prefix.($special ? '.' : '').$special] = array(); } if (is_array($id)) { foreach ($id as $tmp_id => $live_id) { $this->savedIDs[$prefix.($special ? '.' : '').$special][$tmp_id] = $live_id; } } else { $this->savedIDs[$prefix.($special ? '.' : '').$special][] = $id; } } /** * Get temp table name * * @param string $table * @return string */ function GetTempName($table) { return $this->Application->GetTempName($table, $this->WindowID); } function GetTempTablePrefix() { return $this->Application->GetTempTablePrefix($this->WindowID); } /** * Return live table name based on temp table name * * @param string $temp_table * @return string */ function GetLiveName($temp_table) { return $this->Application->GetLiveName($temp_table); } function IsTempTable($table) { return $this->Application->IsTempTable($table); } /** * Return temporary table name for master table * * @return string * @access public */ function GetMasterTempName() { return $this->GetTempName($this->MasterTable); } function CreateTempTable($table) { $query = sprintf("CREATE TABLE %s SELECT * FROM %s WHERE 0", $this->GetTempName($table), $table); $this->Conn->Query($query); } function BuildTables($prefix, $ids) { $this->WindowID = $this->Application->GetVar('m_wid'); $this->TableIdCounter = 0; $tables = Array( 'TableName' => $this->Application->getUnitOption($prefix, 'TableName'), 'IdField' => $this->Application->getUnitOption($prefix, 'IDField'), 'IDs' => $ids, 'Prefix' => $prefix, 'TableId' => $this->TableIdCounter++, ); /*$parent_prefix = $this->Application->getUnitOption($prefix, 'ParentPrefix'); if ($parent_prefix) { $tables['ForeignKey'] = $this->Application->getUnitOption($prefix, 'ForeignKey'); $tables['ParentPrefix'] = $parent_prefix; $tables['ParentTableKey'] = $this->Application->getUnitOption($prefix, 'ParentTableKey'); }*/ $this->FinalRefs[ $tables['TableName'] ] = $tables['TableId']; // don't forget to add main table to FinalRefs too $SubItems = $this->Application->getUnitOption($prefix,'SubItems'); if (is_array($SubItems)) { foreach ($SubItems as $prefix) { $this->AddTables($prefix, $tables); } } $this->SetTables($tables); } /** * Searches through TempHandler tables info for required prefix * * @param string $prefix * @param Array $master * @return mixed */ function SearchTable($prefix, $master = null) { if (is_null($master)) { $master = $this->Tables; } if ($master['Prefix'] == $prefix) { return $master; } if (isset($master['SubTables'])) { foreach ($master['SubTables'] as $sub_table) { $found = $this->SearchTable($prefix, $sub_table); if ($found !== false) { return $found; } } } return false; } function AddTables($prefix, &$tables) { if (!$this->Application->prefixRegistred($prefix)) { // allows to skip subitem processing if subitem module not enabled/installed return ; } $tmp = Array( 'TableName' => $this->Application->getUnitOption($prefix,'TableName'), 'IdField' => $this->Application->getUnitOption($prefix,'IDField'), 'ForeignKey' => $this->Application->getUnitOption($prefix,'ForeignKey'), 'ParentPrefix' => $this->Application->getUnitOption($prefix, 'ParentPrefix'), 'ParentTableKey' => $this->Application->getUnitOption($prefix,'ParentTableKey'), 'Prefix' => $prefix, 'AutoClone' => $this->Application->getUnitOption($prefix,'AutoClone'), 'AutoDelete' => $this->Application->getUnitOption($prefix,'AutoDelete'), 'TableId' => $this->TableIdCounter++, ); $this->FinalRefs[ $tmp['TableName'] ] = $tmp['TableId']; $constrain = $this->Application->getUnitOption($prefix,'Constrain'); if ($constrain) { $tmp['Constrain'] = $constrain; $this->FinalRefs[ $tmp['TableName'].$tmp['Constrain'] ] = $tmp['TableId']; } $SubItems = $this->Application->getUnitOption($prefix,'SubItems'); $same_sub_counter = 1; if( is_array($SubItems) ) { foreach($SubItems as $prefix) { $this->AddTables($prefix, $tmp); } } if ( !is_array(getArrayValue($tables, 'SubTables')) ) { $tables['SubTables'] = array(); } $tables['SubTables'][] = $tmp; } function CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false) { if (!isset($master)) $master = $this->Tables; // recalling by different name, because we may get kDBList, if we recall just by prefix if (!preg_match('/(.*)-item$/', $special)) { $special .= '-item'; } $object =& $this->Application->recallObject($prefix.'.'.$special, $prefix, Array('skip_autoload' => true)); $object->PopulateMultiLangFields(); foreach ($ids as $id) { $mode = 'create'; if ( $cloned_ids = getArrayValue($this->AlreadyProcessed, $master['TableName']) ) { // if we have already cloned the id, replace it with cloned id and set mode to update // update mode is needed to update second ForeignKey for items cloned by first ForeignKey if ( getArrayValue($cloned_ids, $id) ) { $id = $cloned_ids[$id]; $mode = 'update'; } } $object->Load($id); $original_values = $object->FieldValues; if (!$skip_filenames) { $object->NameCopy($master, $foreign_key); } elseif ($master['TableName'] == $this->MasterTable) { // kCatDBItem class only has this attribute $object->useFilenames = false; } if (isset($foreign_key)) { $master_foreign_key_field = is_array($master['ForeignKey']) ? $master['ForeignKey'][$parent_prefix] : $master['ForeignKey']; $object->SetDBField($master_foreign_key_field, $foreign_key); } if ($mode == 'create') { $this->RaiseEvent('OnBeforeClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key); } $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 ($object->mode == 't') { - $object->setTempID(); - } + if ($mode == 'create') { $this->RaiseEvent('OnAfterClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key, array('original_id' => $id) ); $this->saveID($master['Prefix'], $special, $object->GetID()); } if ( is_array(getArrayValue($master, 'SubTables')) ) { foreach($master['SubTables'] as $sub_table) { if (!getArrayValue($sub_table, 'AutoClone')) continue; $sub_TableName = ($object->mode == 't') ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName']; $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey']; $parent_key_field = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey']; if (!$foreign_key_field || !$parent_key_field) continue; $query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.' WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field]; if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain']; $sub_ids = $this->Conn->GetCol($query); if ( is_array(getArrayValue($sub_table, 'ForeignKey')) ) { // $sub_ids could containt newly cloned items, we need to remove it here // to escape double cloning $cloned_ids = getArrayValue($this->AlreadyProcessed, $sub_table['TableName']); if ( !$cloned_ids ) $cloned_ids = Array(); $new_ids = array_values($cloned_ids); $sub_ids = array_diff($sub_ids, $new_ids); } $parent_key = $object->GetDBField($parent_key_field); $this->CloneItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key, $master['Prefix']); } } } } if (!$ids) { $this->savedIDs[$prefix.($special ? '.' : '').$special] = Array(); } return $this->savedIDs[$prefix.($special ? '.' : '').$special]; } function DeleteItems($prefix, $special, $ids, $master=null, $foreign_key=null) { if (!isset($master)) $master = $this->Tables; if( strpos($prefix,'.') !== false ) list($prefix,$special) = explode('.', $prefix, 2); $prefix_special = rtrim($prefix.'.'.$special, '.'); //recalling by different name, because we may get kDBList, if we recall just by prefix $recall_prefix = $prefix_special.($special ? '' : '.').'-item'; $object =& $this->Application->recallObject($recall_prefix, $prefix, Array('skip_autoload' => true)); foreach ($ids as $id) { $object->Load($id); $original_values = $object->FieldValues; if( !$object->Delete($id) ) continue; if ( is_array(getArrayValue($master, 'SubTables')) ) { foreach($master['SubTables'] as $sub_table) { if (!getArrayValue($sub_table, 'AutoDelete')) continue; $sub_TableName = ($object->mode == 't') ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName']; $foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey']; $parent_key_field = is_array($sub_table['ParentTableKey']) ? getArrayValue($sub_table, 'ParentTableKey', $master['Prefix']) : $sub_table['ParentTableKey']; if (!$foreign_key_field || !$parent_key_field) continue; $query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.' WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field]; $sub_ids = $this->Conn->GetCol($query); $parent_key = $object->GetDBField(is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$prefix] : $sub_table['ParentTableKey']); $this->DeleteItems($sub_table['Prefix'], '', $sub_ids, $sub_table, $parent_key); } } } } function DoCopyLiveToTemp($master, $ids, $parent_prefix=null) { // when two tables refers the same table as sub-sub-table, and ForeignKey and ParentTableKey are arrays // the table will be first copied by first sub-table, then dropped and copied over by last ForeignKey in the array // this should not do any problems :) if ( !preg_match("/.*\.[0-9]+/", $master['Prefix']) ) { if( $this->DropTempTable($master['TableName']) ) { $this->CreateTempTable($master['TableName']); } } if (is_array($ids)) { $ids = join(',', $ids); } $table_sig = $master['TableName'].(isset($master['Constrain']) ? $master['Constrain'] : ''); if ($ids != '' && !in_array($table_sig, $this->CopiedTables)) { if ( getArrayValue($master, 'ForeignKey') ) { if ( is_array($master['ForeignKey']) ) { $key_field = $master['ForeignKey'][$parent_prefix]; } else { $key_field = $master['ForeignKey']; } } else { $key_field = $master['IdField']; } $query = 'INSERT INTO '.$this->GetTempName($master['TableName']).' SELECT * FROM '.$master['TableName'].' WHERE '.$key_field.' IN ('.$ids.')'; if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain']; $this->Conn->Query($query); $this->CopiedTables[] = $table_sig; $query = 'SELECT '.$master['IdField'].' FROM '.$master['TableName'].' WHERE '.$key_field.' IN ('.$ids.')'; if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain']; $this->RaiseEvent( 'OnAfterCopyToTemp', $master['Prefix'], '', $this->Conn->GetCol($query) ); } if ( getArrayValue($master, 'SubTables') ) { foreach ($master['SubTables'] as $sub_table) { $parent_key = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey']; if (!$parent_key) continue; if ( $ids != '' && $parent_key != $key_field ) { $query = 'SELECT '.$parent_key.' FROM '.$master['TableName'].' WHERE '.$key_field.' IN ('.$ids.')'; $sub_foreign_keys = join(',', $this->Conn->GetCol($query)); } else { $sub_foreign_keys = $ids; } $this->DoCopyLiveToTemp($sub_table, $sub_foreign_keys, $master['Prefix']); } } } function GetForeignKeys($master, $sub_table, $live_id, $temp_id=null) { $mode = 1; //multi if (!is_array($live_id)) { $live_id = Array($live_id); $mode = 2; //single } if (isset($temp_id) && !is_array($temp_id)) $temp_id = Array($temp_id); if ( isset($sub_table['ParentTableKey']) ) { if ( is_array($sub_table['ParentTableKey']) ) { $parent_key_field = $sub_table['ParentTableKey'][$master['Prefix']]; } else { $parent_key_field = $sub_table['ParentTableKey']; } } else { $parent_key_field = $master['IdField']; } if ( $cached = getArrayValue($this->FKeysCache, $master['TableName'].'.'.$parent_key_field) ) { if ( array_key_exists(serialize($live_id), $cached) ) { list($live_foreign_key, $temp_foreign_key) = $cached[serialize($live_id)]; if ($mode == 1) { return $live_foreign_key; } else { return Array($live_foreign_key[0], $temp_foreign_key[0]); } } } if ($parent_key_field != $master['IdField']) { $query = 'SELECT '.$parent_key_field.' FROM '.$master['TableName'].' WHERE '.$master['IdField'].' IN ('.join(',', $live_id).')'; $live_foreign_key = $this->Conn->GetCol($query); if (isset($temp_id)) { // because DoCopyTempToOriginal resets negative IDs to 0 in temp table (one by one) before copying to live $temp_key = $temp_id < 0 ? 0 : $temp_id; $query = 'SELECT '.$parent_key_field.' FROM '.$this->GetTempName($master['TableName']).' WHERE '.$master['IdField'].' IN ('.join(',', $temp_key).')'; $temp_foreign_key = $this->Conn->GetCol($query); } else { $temp_foreign_key = Array(); } } else { $live_foreign_key = $live_id; $temp_foreign_key = $temp_id; } $this->FKeysCache[$master['TableName'].'.'.$parent_key_field][serialize($live_id)] = Array($live_foreign_key, $temp_foreign_key); if ($mode == 1) { return $live_foreign_key; } else { return Array($live_foreign_key[0], $temp_foreign_key[0]); } } function DoCopyTempToOriginal($master, $parent_prefix = null, $current_ids = Array()) { if (!$current_ids) { $query = 'SELECT '.$master['IdField'].' FROM '.$this->GetTempName($master['TableName']); if (isset($master['Constrain'])) $query .= ' WHERE '.$master['Constrain']; $current_ids = $this->Conn->GetCol($query); } $table_sig = $master['TableName'].(isset($master['Constrain']) ? $master['Constrain'] : ''); if ($current_ids) { // delete all ids from live table - for MasterTable ONLY! // because items from Sub Tables get deteleted in CopySubTablesToLive !BY ForeignKey! if ($master['TableName'] == $this->MasterTable) { $this->RaiseEvent( 'OnBeforeDeleteFromLive', $master['Prefix'], '', $current_ids ); $query = 'DELETE FROM '.$master['TableName'].' WHERE '.$master['IdField'].' IN ('.join(',', $current_ids).')'; $this->Conn->Query($query); } if ( getArrayValue($master, 'SubTables') ) { if( in_array($table_sig, $this->CopiedTables) || $this->FinalRefs[$table_sig] != $master['TableId'] ) return; foreach($current_ids AS $id) { $this->RaiseEvent( 'OnBeforeCopyToLive', $master['Prefix'], '', Array($id) ); //reset negative ids to 0, so autoincrement in live table works fine if($id < 0) { $query = 'UPDATE '.$this->GetTempName($master['TableName']).' SET '.$master['IdField'].' = 0 WHERE '.$master['IdField'].' = '.$id; if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain']; $this->Conn->Query($query); $id_to_copy = 0; } else { $id_to_copy = $id; } //copy current id_to_copy (0 for new or real id) to live table $query = 'INSERT INTO '.$master['TableName'].' SELECT * FROM '.$this->GetTempName($master['TableName']).' WHERE '.$master['IdField'].' = '.$id_to_copy; $this->Conn->Query($query); $insert_id = $id_to_copy == 0 ? $this->Conn->getInsertID() : $id_to_copy; $this->saveID($master['Prefix'], '', array($id => $insert_id)); $this->RaiseEvent( 'OnAfterCopyToLive', $master['Prefix'], '', Array($insert_id), null, array('temp_id' => $id) ); $this->UpdateForeignKeys($master, $insert_id, $id); //delete already copied record from master temp table $query = 'DELETE FROM '.$this->GetTempName($master['TableName']).' WHERE '.$master['IdField'].' = '.$id_to_copy; if (isset($master['Constrain'])) $query .= ' AND '.$master['Constrain']; $this->Conn->Query($query); } $this->CopiedTables[] = $table_sig; // when all of ids in current master has been processed, copy all sub-tables data $this->CopySubTablesToLive($master, $current_ids); } elseif( !in_array($table_sig, $this->CopiedTables) && ($this->FinalRefs[$table_sig] == $master['TableId']) ) { //If current master doesn't have sub-tables - we could use mass operations // We don't need to delete items from live here, as it get deleted in the beggining of the method for MasterTable // or in parent table processing for sub-tables $this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', $current_ids); $live_ids = array(); foreach ($current_ids as $an_id) { if ($an_id > 0) { $live_ids[$an_id] = $an_id; // positive (already live) IDs will be copied in on query all togather below, // so we just store it here continue; } else { // zero or negaitve ids should be copied one by one to get their InsertId // reseting to 0 so it get inserted into live table with autoincrement $query = 'UPDATE '.$this->GetTempName($master['TableName']).' SET '.$master['IdField'].' = 0 WHERE '.$master['IdField'].' = '.$an_id; // constrain is not needed here because ID is already unique $this->Conn->Query($query); // copying $query = 'INSERT INTO '.$master['TableName'].' SELECT * FROM '.$this->GetTempName($master['TableName']).' WHERE '.$master['IdField'].' = 0'; $this->Conn->Query($query); $live_ids[$an_id] = $this->Conn->getInsertID(); //storing newly created live id //delete already copied record from master temp table $query = 'DELETE FROM '.$this->GetTempName($master['TableName']).' WHERE '.$master['IdField'].' = 0'; $this->Conn->Query($query); $this->UpdateChangeLogForeignKeys($master, $live_ids[$an_id], $an_id); } } // copy ALL records to live table $query = 'INSERT INTO '.$master['TableName'].' SELECT * FROM '.$this->GetTempName($master['TableName']); if (isset($master['Constrain'])) $query .= ' WHERE '.$master['Constrain']; $this->Conn->Query($query); $this->CopiedTables[] = $table_sig; $this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', $live_ids); $this->saveID($master['Prefix'], '', $live_ids); // no need to clear temp table - it will be dropped by next statement } } if ($this->FinalRefs[ $master['TableName'] ] != $master['TableId']) return; /*if ( is_array(getArrayValue($master, 'ForeignKey')) ) { //if multiple ForeignKeys if ( $master['ForeignKey'][$parent_prefix] != end($master['ForeignKey']) ) { return; // Do not delete temp table if not all ForeignKeys have been processed (current is not the last) } }*/ $this->DropTempTable($master['TableName']); $this->Application->resetCounters($master['TableName']); if (!isset($this->savedIDs[ $master['Prefix'] ])) { $this->savedIDs[ $master['Prefix'] ] = Array(); } return $this->savedIDs[ $master['Prefix'] ]; } /** * Create separate connection for locking purposes * * @return kDBConnection */ function &_getSeparateConnection() { static $connection = null; if (!isset($connection)) { $connection = new kDBConnection(SQL_TYPE, Array(&$this->Application, 'handleSQLError') ); $connection->debugMode = $this->Application->isDebugMode(); $connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB, true); } return $connection; } function UpdateChangeLogForeignKeys($master, $live_id, $temp_id) { $main_prefix = $this->Application->GetTopmostPrefix($master['Prefix']); $ses_var_name = $main_prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); $changes = $this->Application->RecallVar($ses_var_name); $changes = $changes ? unserialize($changes) : array(); foreach ($changes as $key => $rec) { if ($rec['Prefix'] == $master['Prefix']) { if ($rec['ItemId'] == $temp_id) { $changes[$key]['ItemId'] = $live_id; } } if ($rec['MasterPrefix'] == $master['Prefix']) { if ($rec['MasterId'] == $temp_id) { $changes[$key]['MasterId'] = $live_id; } } } $this->Application->StoreVar($ses_var_name, serialize($changes)); } function UpdateForeignKeys($master, $live_id, $temp_id) { $this->UpdateChangeLogForeignKeys($master, $live_id, $temp_id); foreach ($master['SubTables'] as $sub_table) { $foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey']; if (!$foreign_key_field) { 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; } } \ No newline at end of file Index: branches/5.0.x/core/kernel/db/db_event_handler.php =================================================================== --- branches/5.0.x/core/kernel/db/db_event_handler.php (revision 13602) +++ branches/5.0.x/core/kernel/db/db_event_handler.php (revision 13603) @@ -1,2603 +1,2597 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); define('EH_CUSTOM_PROCESSING_BEFORE',1); define('EH_CUSTOM_PROCESSING_AFTER',2); /** * Note: * 1. When adressing variables from submit containing * Prefix_Special as part of their name use * $event->getPrefixSpecial(true) instead of * $event->Prefix_Special as usual. This is due PHP * is converting "." symbols in variable names during * submit info "_". $event->getPrefixSpecial optional * 1st parameter returns correct corrent Prefix_Special * for variables beeing submitted such way (e.g. variable * name that will be converted by PHP: "users.read_only_id" * will be submitted as "users_read_only_id". * * 2. When using $this->Application-LinkVar on variables submitted * from form which contain $Prefix_Special then note 1st item. Example: * LinkVar($event->getPrefixSpecial(true).'_varname',$event->Prefix_Special.'_varname') * */ /** * EventHandler that is used to process * any database related events * */ class kDBEventHandler extends kEventHandler { /** * Description * * @var kDBConnection * @access public */ var $Conn; /** * Adds ability to address db connection * * @return kDBEventHandler * @access public */ function kDBEventHandler() { parent::kBase(); $this->Conn =& $this->Application->GetADODBConnection(); } /** * Checks permissions of user * * @param kEvent $event */ function CheckPermission(&$event) { if (!$this->Application->isAdmin) { $allow_events = Array('OnSearch', 'OnSearchReset', 'OnNew'); if (in_array($event->Name, $allow_events)) { // allow search on front return true; } } $section = $event->getSection(); if (!preg_match('/^CATEGORY:(.*)/', $section)) { // only if not category item events if ((substr($event->Name, 0, 9) == 'OnPreSave') || ($event->Name == 'OnSave')) { if ($this->isNewItemCreate($event)) { return $this->Application->CheckPermission($section.'.add', 1); } else { return $this->Application->CheckPermission($section.'.add', 1) || $this->Application->CheckPermission($section.'.edit', 1); } } } if ($event->Name == 'OnPreCreate') { // save category_id before item create (for item category selector not to destroy permission checking category) $this->Application->LinkVar('m_cat_id'); } if ($event->Name == 'OnSaveWidths') { return $this->Application->isAdminUser; } return parent::CheckPermission($event); } /** * Allows to override standart permission mapping * */ function mapPermissions() { parent::mapPermissions(); $permissions = Array( 'OnLoad' => Array('self' => 'view', 'subitem' => 'view'), 'OnItemBuild' => Array('self' => 'view', 'subitem' => 'view'), 'OnSuggestValues' => Array('self' => 'view', 'subitem' => 'view'), 'OnBuild' => Array('self' => true), 'OnNew' => Array('self' => 'add', 'subitem' => 'add|edit'), 'OnCreate' => Array('self' => 'add', 'subitem' => 'add|edit'), 'OnUpdate' => Array('self' => 'edit', 'subitem' => 'add|edit'), 'OnSetPrimary' => Array('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnDelete' => Array('self' => 'delete', 'subitem' => 'add|edit'), 'OnDeleteAll' => Array('self' => 'delete', 'subitem' => 'add|edit'), 'OnMassDelete' => Array('self' => 'delete', 'subitem' => 'add|edit'), 'OnMassClone' => Array('self' => 'add', 'subitem' => 'add|edit'), 'OnCut' => array('self'=>'edit', 'subitem' => 'edit'), 'OnCopy' => array('self'=>'edit', 'subitem' => 'edit'), 'OnPaste' => array('self'=>'edit', 'subitem' => 'edit'), 'OnSelectItems' => Array('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnProcessSelected' => Array('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnSelectUser' => Array('self' => 'add|edit', 'subitem' => 'add|edit'), 'OnMassApprove' => Array('self' => 'advanced:approve|edit', 'subitem' => 'advanced:approve|add|edit'), 'OnMassDecline' => Array('self' => 'advanced:decline|edit', 'subitem' => 'advanced:decline|add|edit'), 'OnMassMoveUp' => Array('self' => 'advanced:move_up|edit', 'subitem' => 'advanced:move_up|add|edit'), 'OnMassMoveDown' => Array('self' => 'advanced:move_down|edit', 'subitem' => 'advanced:move_down|add|edit'), 'OnPreCreate' => Array('self' => 'add|add.pending', 'subitem' => 'edit|edit.pending'), 'OnEdit' => Array('self' => 'edit|edit.pending', 'subitem' => 'edit|edit.pending'), 'OnExport' => Array('self' => 'view|advanced:export'), 'OnExportBegin' => Array('self' => 'view|advanced:export'), 'OnExportProgress' => Array('self' => 'view|advanced:export'), 'OnSetAutoRefreshInterval' => Array ('self' => true, 'subitem' => true), 'OnAutoRefreshToggle' => Array ('self' => true, 'subitem' => true), // theese event do not harm, but just in case check them too :) 'OnCancelEdit' => Array('self' => true, 'subitem' => true), 'OnCancel' => Array('self' => true, 'subitem' => true), 'OnReset' => Array('self' => true, 'subitem' => true), 'OnSetSorting' => Array('self' => true, 'subitem' => true), 'OnSetSortingDirect' => Array('self' => true, 'subitem' => true), 'OnResetSorting' => Array('self' => true, 'subitem' => true), 'OnSetFilter' => Array('self' => true, 'subitem' => true), 'OnApplyFilters' => Array('self' => true, 'subitem' => true), 'OnRemoveFilters' => Array('self' => true, 'subitem' => true), 'OnSetFilterPattern' => Array('self' => true, 'subitem' => true), 'OnSetPerPage' => Array('self' => true, 'subitem' => true), 'OnSetPage' => Array('self' => true, 'subitem' => true), 'OnSearch' => Array('self' => true, 'subitem' => true), 'OnSearchReset' => Array('self' => true, 'subitem' => true), 'OnGoBack' => Array('self' => true, 'subitem' => true), // it checks permission itself since flash uploader does not send cookies 'OnUploadFile' => Array ('self' => true, 'subitem' => true), 'OnDeleteFile' => Array ('self' => true, 'subitem' => true), 'OnViewFile' => Array ('self' => true, 'subitem' => true), 'OnSaveWidths' => Array ('self' => true, 'subitem' => true), 'OnValidateMInputFields' => Array ('self' => 'view'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } function mapEvents() { $events_map = Array( 'OnRemoveFilters' => 'FilterAction', 'OnApplyFilters' => 'FilterAction', 'OnMassApprove'=>'iterateItems', 'OnMassDecline'=>'iterateItems', 'OnMassMoveUp'=>'iterateItems', 'OnMassMoveDown'=>'iterateItems', ); $this->eventMethods = array_merge($this->eventMethods, $events_map); } /** * Returns ID of current item to be edited * by checking ID passed in get/post as prefix_id * or by looking at first from selected ids, stored. * Returned id is also stored in Session in case * it was explicitly passed as get/post * * @param kEvent $event * @return int */ function getPassedID(&$event) { if ($event->getEventParam('raise_warnings') === false) { $event->setEventParam('raise_warnings', 1); } if (preg_match('/^auto-(.*)/', $event->Special, $regs) && $this->Application->prefixRegistred($regs[1])) { // <inp2:lang.auto-phrase_Field name="DateFormat"/> - returns field DateFormat value from language (LanguageId is extracted from current phrase object) $main_object =& $this->Application->recallObject($regs[1]); /* @var $main_object kDBItem */ $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); return $main_object->GetDBField($id_field); } // 1. get id from post (used in admin) $ret = $this->Application->GetVar($event->getPrefixSpecial(true).'_id'); if (($ret !== false) && ($ret != '')) { return $ret; } // 2. get id from env (used in front) $ret = $this->Application->GetVar($event->getPrefixSpecial().'_id'); if (($ret !== false) && ($ret != '')) { return $ret; } // recall selected ids array and use the first one $ids = $this->Application->GetVar($event->getPrefixSpecial().'_selected_ids'); if ($ids != '') { $ids = explode(',',$ids); if ($ids) { $ret = array_shift($ids); } } else { // if selected ids are not yet stored $this->StoreSelectedIDs($event); return $this->Application->GetVar($event->getPrefixSpecial().'_id'); // StoreSelectedIDs sets this variable } return $ret; } /** * Prepares and stores selected_ids string * in Session and Application Variables * by getting all checked ids from grid plus * id passed in get/post as prefix_id * * @param kEvent $event * @param Array $direct_ids * * @return Array ids stored */ function StoreSelectedIDs(&$event, $direct_ids = null) { $wid = $this->Application->GetTopmostWid($event->Prefix); $session_name = rtrim($event->getPrefixSpecial().'_selected_ids_'.$wid, '_'); $ids = $event->getEventParam('ids'); if (isset($direct_ids) || ($ids !== false)) { // save ids directly if they given + reset array indexes $resulting_ids = $direct_ids ? array_values($direct_ids) : ($ids ? array_values($ids) : false); if ($resulting_ids) { $this->Application->SetVar($event->getPrefixSpecial() . '_selected_ids', implode(',', $resulting_ids)); $this->Application->LinkVar($event->getPrefixSpecial() . '_selected_ids', $session_name); $this->Application->SetVar($event->getPrefixSpecial() . '_id', $resulting_ids[0]); return $resulting_ids; } return Array (); } $ret = Array(); // May be we don't need this part: ? $passed = $this->Application->GetVar($event->getPrefixSpecial(true).'_id'); if($passed !== false && $passed != '') { array_push($ret, $passed); } $ids = Array(); // get selected ids from post & save them to session $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) { $id_field = $this->Application->getUnitOption($event->Prefix,'IDField'); foreach($items_info as $id => $field_values) { if( getArrayValue($field_values,$id_field) ) array_push($ids,$id); } //$ids=array_keys($items_info); } $ret = array_unique(array_merge($ret, $ids)); $this->Application->SetVar($event->getPrefixSpecial().'_selected_ids', implode(',',$ret)); $this->Application->LinkVar($event->getPrefixSpecial().'_selected_ids', $session_name, '', !$ret); // optional when IDs are missing // This is critical - otherwise getPassedID will return last ID stored in session! (not exactly true) // this smells... needs to be refactored $first_id = getArrayValue($ret,0); if (($first_id === false) && ($event->getEventParam('raise_warnings') == 1)) { if ($this->Application->isDebugMode()) { $this->Application->Debugger->appendTrace(); } trigger_error('Requested ID for prefix <b>'.$event->getPrefixSpecial().'</b> <span class="debug_error">not passed</span>',E_USER_NOTICE); } $this->Application->SetVar($event->getPrefixSpecial() . '_id', $first_id); return $ret; } /** * Returns stored selected ids as an array * * @param kEvent $event * @param bool $from_session return ids from session (written, when editing was started) * @return array */ function getSelectedIDs(&$event, $from_session = false) { if ($from_session) { $wid = $this->Application->GetTopmostWid($event->Prefix); $var_name = rtrim($event->getPrefixSpecial().'_selected_ids_'.$wid, '_'); $ret = $this->Application->RecallVar($var_name); } else { $ret = $this->Application->GetVar($event->getPrefixSpecial().'_selected_ids'); } return explode(',', $ret); } /** * Returs associative array of submitted fields for current item * Could be used while creating/editing single item - * meaning on any edit form, except grid edit * * @param kEvent $event */ function getSubmittedFields(&$event) { $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); $field_values = $items_info ? array_shift($items_info) : Array(); return $field_values; } /** * Removes any information about current/selected ids * from Application variables and Session * * @param kEvent $event */ function clearSelectedIDs(&$event) { $prefix_special = $event->getPrefixSpecial(); $ids = implode(',', $this->getSelectedIDs($event, true)); $event->setEventParam('ids', $ids); $wid = $this->Application->GetTopmostWid($event->Prefix); $session_name = rtrim($prefix_special.'_selected_ids_'.$wid, '_'); $this->Application->RemoveVar($session_name); $this->Application->SetVar($prefix_special.'_selected_ids', ''); $this->Application->SetVar($prefix_special.'_id', ''); // $event->getPrefixSpecial(true).'_id' too may be } /*function SetSaveEvent(&$event) { $this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnUpdate'); $this->Application->LinkVar($event->Prefix_Special.'_SaveEvent'); }*/ /** * Common builder part for Item & List * * @param kDBBase $object * @param kEvent $event * @access private */ function dbBuild(&$object, &$event) { // for permission checking inside item/list build events $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); $object->Configure( $event->getEventParam('populate_ml_fields') || $this->Application->getUnitOption($event->Prefix, 'PopulateMlFields') ); $this->PrepareObject($object, $event); // force live table if specified or is original item $live_table = $event->getEventParam('live_table') || $event->Special == 'original'; if( $this->UseTempTables($event) && !$live_table ) { $object->SwitchToTemp(); } // This strange constuction creates hidden field for storing event name in form submit // It pass SaveEvent to next screen, otherwise after unsuccsefull create it will try to update rather than create $current_event = $this->Application->GetVar($event->Prefix_Special.'_event'); // $this->Application->setEvent($event->Prefix_Special, $current_event); $this->Application->setEvent($event->Prefix_Special, ''); $save_event = $this->UseTempTables($event) && $this->Application->GetTopmostPrefix($event->Prefix) == $event->Prefix ? 'OnSave' : 'OnUpdate'; $this->Application->SetVar($event->Prefix_Special.'_SaveEvent',$save_event); } /** * Checks, that currently loaded item is allowed for viewing (non permission-based) * * @param kEvent $event * @return bool */ function checkItemStatus(&$event) { $status_fields = $this->Application->getUnitOption($event->Prefix,'StatusField'); if (!$status_fields) { return true; } $status_field = array_shift($status_fields); if ($status_field == 'Status' || $status_field == 'Enabled') { $object =& $event->getObject(); if (!$object->isLoaded()) { return true; } return $object->GetDBField($status_field) == STATUS_ACTIVE; } return true; } /** * Shows not found template content * * @param kEvent $event * */ function _errorNotFound(&$event) { if ($event->getEventParam('raise_warnings') === 0) { // when it's possible, that autoload fails do nothing return ; } if ($this->Application->isDebugMode()) { $this->Application->Debugger->appendTrace(); } trigger_error('ItemLoad Permission Failed for prefix [' . $event->getPrefixSpecial() . '] in <strong>checkItemStatus</strong>, leading to "404 Not Found"', E_USER_NOTICE); header('HTTP/1.0 404 Not Found'); while (ob_get_level()) { ob_end_clean(); } // object is used inside template parsing, so break out any parsing and return error document $error_template = $this->Application->ConfigValue('ErrorTemplate'); $themes_helper =& $this->Application->recallObject('ThemesHelper'); /* @var $themes_helper kThemesHelper */ $this->Application->SetVar('t', $error_template); $this->Application->SetVar('m_cat_id', $themes_helper->getPageByTemplate($error_template)); // in case if missing item is recalled first from event (not from template) $this->Application->InitParser(); $this->Application->HTML = $this->Application->ParseBlock( Array ('name' => $error_template) ); $this->Application->Done(); exit; } /** * Builds item (loads if needed) * * @param kEvent $event * @access protected */ function OnItemBuild(&$event) { $object =& $event->getObject(); $this->dbBuild($object,$event); $sql = $this->ItemPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); // 2. loads if allowed $auto_load = $this->Application->getUnitOption($event->Prefix,'AutoLoad'); $skip_autload = $event->getEventParam('skip_autoload'); if ($auto_load && !$skip_autload) { $perm_status = true; $user_id = $this->Application->RecallVar('user_id'); $event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); $status_checked = false; if ($user_id == -1 || $this->CheckPermission($event)) { // don't autoload item, when user doesn't have view permission $this->LoadItem($event); $status_checked = true; $editing_mode = defined('EDITING_MODE') ? EDITING_MODE : false; if ($user_id != -1 && !$this->Application->isAdmin && !($editing_mode || $this->checkItemStatus($event))) { // non-root user AND on front-end AND (not editing mode || incorrect status) $perm_status = false; } } else { $perm_status = false; } if (!$perm_status) { // when no permission to view item -> redirect to no pemrission template if ($this->Application->isDebugMode()) { $this->Application->Debugger->appendTrace(); } trigger_error('ItemLoad Permission Failed for prefix ['.$event->getPrefixSpecial().'] in <strong>'.($status_checked ? 'checkItemStatus' : 'CheckPermission').'</strong>', E_USER_NOTICE); $template = $this->Application->isAdmin ? 'no_permission' : $this->Application->ConfigValue('NoPermissionTemplate'); if (MOD_REWRITE) { $redirect_params = Array ( 'm_cat_id' => 0, 'next_template' => urlencode('external:' . $_SERVER['REQUEST_URI']), ); } else { $redirect_params = Array ( 'next_template' => $this->Application->GetVar('t'), ); } $this->Application->Redirect($template, $redirect_params); } } $actions =& $this->Application->recallObject('kActions'); $actions->Set($event->Prefix_Special.'_GoTab', ''); $actions->Set($event->Prefix_Special.'_GoId', ''); } /** * Build subtables array from configs * * @param kEvent $event */ function OnTempHandlerBuild(&$event) { $object =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); /* @var $object kTempTablesHandler */ $object->BuildTables( $event->Prefix, $this->getSelectedIDs($event) ); } /** * Checks, that object used in event should use temp tables * * @param kEvent $event * @return bool */ function UseTempTables(&$event) { $top_prefix = $this->Application->GetTopmostPrefix($event->Prefix); // passed parent, not always actual $special = ($top_prefix == $event->Prefix) ? $event->Special : $this->getMainSpecial($event); return $this->Application->IsTempMode($event->Prefix, $special); } /** * Returns table prefix from event (temp or live) * * @param kEvent $event * @return string * @todo Needed? Should be refactored (by Alex) */ function TablePrefix(&$event) { return $this->UseTempTables($event) ? $this->Application->GetTempTablePrefix('prefix:'.$event->Prefix).TABLE_PREFIX : TABLE_PREFIX; } /** * Load item if id is available * * @param kEvent $event */ function LoadItem(&$event) { $object =& $event->getObject(); $id = $this->getPassedID($event); if ($object->isLoaded() && !is_array($id) && ($object->GetID() == $id)) { // object is already loaded by same id return ; } if ($object->Load($id)) { $actions =& $this->Application->recallObject('kActions'); $actions->Set($event->Prefix_Special.'_id', $object->GetID() ); } else { $object->setID($id); } } /** * Builds list * * @param kEvent $event * @access protected */ function OnListBuild(&$event) { $object =& $event->getObject(); /* @var $object kDBList */ $this->dbBuild($object,$event); $sql = $this->ListPrepareQuery($event); $sql = $this->Application->ReplaceLanguageTags($sql); $object->setSelectSQL($sql); $object->Counted = false; // when requery="1" should re-count records too! $object->ClearOrderFields(); // prevents duplicate order fields, when using requery="1" $object->linkToParent( $this->getMainSpecial($event) ); $this->AddFilters($event); $this->SetCustomQuery($event); // new!, use this for dynamic queries based on specials for ex. $this->SetPagination($event); $this->SetSorting($event); // $object->CalculateTotals(); // Now called in getTotals to avoid extra query $actions =& $this->Application->recallObject('kActions'); $actions->Set('remove_specials['.$event->Prefix_Special.']', '0'); $actions->Set($event->Prefix_Special.'_GoTab', ''); } /** * Get's special of main item for linking with subitem * * @param kEvent $event * @return string */ function getMainSpecial(&$event) { $main_special = $event->getEventParam('main_special'); if ($main_special === false) { // main item's special not passed if (substr($event->Special, -5) == '-item') { // temp handler added "-item" to given special -> process that here return substr($event->Special, 0, -5); } // by default subitem's special is used for main item searching return $event->Special; } return $main_special; } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @access protected * @see OnListBuild */ function SetCustomQuery(&$event) { } /** * Set's new perpage for grid * * @param kEvent $event */ function OnSetPerPage(&$event) { $per_page = $this->Application->GetVar($event->getPrefixSpecial(true).'_PerPage'); $this->Application->StoreVar($event->getPrefixSpecial().'_PerPage', $per_page); $view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view'); $this->Application->StorePersistentVar($event->getPrefixSpecial().'_PerPage.'.$view_name, $per_page); } /** * Occurs when page is changed (only for hooking) * * @param kEvent $event */ function OnSetPage(&$event) { $page = $this->Application->GetVar($event->getPrefixSpecial(true) . '_Page'); $event->SetRedirectParam($event->getPrefixSpecial().'_Page', $page); } /** * Set's correct page for list * based on data provided with event * * @param kEvent $event * @access private * @see OnListBuild */ function SetPagination(&$event) { // get PerPage (forced -> session -> config -> 10) $per_page = $this->getPerPage($event); $object =& $event->getObject(); $object->SetPerPage($per_page); $this->Application->StoreVarDefault($event->getPrefixSpecial().'_Page', 1, true); // true for optional $page = $this->Application->GetVar($event->getPrefixSpecial().'_Page'); if (!$page) { $page = $this->Application->GetVar($event->getPrefixSpecial(true).'_Page'); } if (!$page) { $page = $this->Application->RecallVar($event->getPrefixSpecial().'_Page'); } else { $this->Application->StoreVar($event->getPrefixSpecial().'_Page', $page); } if( !$event->getEventParam('skip_counting') ) { $pages = $object->GetTotalPages(); if($page > $pages) { $this->Application->StoreVar($event->getPrefixSpecial().'_Page', 1); $page = 1; } } /*$per_page = $event->getEventParam('per_page'); if ($per_page == 'list_next') { $cur_page = $page; $cur_per_page = $per_page; $object->SetPerPage(1); $object =& $this->Application->recallObject($event->Prefix); $cur_item_index = $object->CurrentIndex; $page = ($cur_page-1) * $cur_per_page + $cur_item_index + 1; $object->SetPerPage(1); }*/ $object->SetPage($page); } /** * Returns current per-page setting for list * * @param kEvent $event * @return int */ function getPerPage(&$event) { // 1. per-page is passed as tag parameter to PrintList, InitList, etc. $per_page = $event->getEventParam('per_page'); /*if ($per_page == 'list_next') { $per_page = ''; }*/ // 2. per-page variable name is store into config variable $config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping'); if ($config_mapping) { switch ( $per_page ){ case 'short_list' : $per_page = $this->Application->ConfigValue($config_mapping['ShortListPerPage']); break; case 'default' : $per_page = $this->Application->ConfigValue($config_mapping['PerPage']); break; } } if (!$per_page) { // per-page is stored to persistent session $view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view'); $storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->getPrefixSpecial(); $per_page = $this->Application->RecallPersistentVar($storage_prefix.'_PerPage.'.$view_name, ALLOW_DEFAULT_SETTINGS); if (!$per_page) { // per-page is stored to current session $per_page = $this->Application->RecallVar($storage_prefix.'_PerPage'); } if (!$per_page) { if ($config_mapping) { if (!isset($config_mapping['PerPage'])) { trigger_error('Incorrect mapping of <span class="debug_error">PerPage</span> key in config for prefix <b>'.$event->Prefix.'</b>', E_USER_WARNING); } $per_page = $this->Application->ConfigValue($config_mapping['PerPage']); } if (!$per_page) { // none of checked above per-page locations are useful, then try default value $default_per_page = $event->getEventParam('default_per_page'); $per_page = is_numeric($default_per_page) ? $default_per_page : 10; } } } return $per_page; } /** * Set's correct sorting for list * based on data provided with event * * @param kEvent $event * @access private * @see OnListBuild */ function SetSorting(&$event) { $event->setPseudoClass('_List'); $object =& $event->getObject(); $storage_prefix = $event->getEventParam('same_special') ? $event->Prefix : $event->Prefix_Special; $cur_sort1 = $this->Application->RecallVar($storage_prefix.'_Sort1'); $cur_sort1_dir = $this->Application->RecallVar($storage_prefix.'_Sort1_Dir'); $cur_sort2 = $this->Application->RecallVar($storage_prefix.'_Sort2'); $cur_sort2_dir = $this->Application->RecallVar($storage_prefix.'_Sort2_Dir'); $sorting_configs = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping'); $list_sortings = $this->Application->getUnitOption($event->Prefix, 'ListSortings'); $sorting_prefix = array_key_exists($event->Special, $list_sortings) ? $event->Special : ''; $tag_sort_by = $event->getEventParam('sort_by'); if ($tag_sort_by) { if ($tag_sort_by == 'random') { $object->AddOrderField('RAND()', ''); } else { $tag_sort_by = explode('|', $tag_sort_by); foreach ($tag_sort_by as $sorting_element) { list ($by, $dir) = explode(',', $sorting_element); $object->AddOrderField($by, $dir); } } } if ($sorting_configs && isset ($sorting_configs['DefaultSorting1Field'])){ $list_sortings[$sorting_prefix]['Sorting'] = Array( $this->Application->ConfigValue($sorting_configs['DefaultSorting1Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting1Dir']), $this->Application->ConfigValue($sorting_configs['DefaultSorting2Field']) => $this->Application->ConfigValue($sorting_configs['DefaultSorting2Dir']), ); } // Use default if not specified if ( !$cur_sort1 || !$cur_sort1_dir) { if ( $sorting = getArrayValue($list_sortings, $sorting_prefix, 'Sorting') ) { reset($sorting); $cur_sort1 = key($sorting); $cur_sort1_dir = current($sorting); if (next($sorting)) { $cur_sort2 = key($sorting); $cur_sort2_dir = current($sorting); } } } if ( $forced_sorting = getArrayValue($list_sortings, $sorting_prefix, 'ForcedSorting') ) { foreach ($forced_sorting as $field => $dir) { $object->AddOrderField($field, $dir); } } if($cur_sort1 != '' && $cur_sort1_dir != '') { $object->AddOrderField($cur_sort1, $cur_sort1_dir); } if($cur_sort2 != '' && $cur_sort2_dir != '') { $object->AddOrderField($cur_sort2, $cur_sort2_dir); } } /** * Add filters found in session * * @param kEvent $event */ function AddFilters(&$event) { $object =& $event->getObject(); $edit_mark = rtrim($this->Application->GetSID().'_'.$this->Application->GetTopmostWid($event->Prefix), '_'); // add search filter $filter_data = $this->Application->RecallVar($event->getPrefixSpecial().'_search_filter'); if ($filter_data) { $filter_data = unserialize($filter_data); foreach ($filter_data as $filter_field => $filter_params) { $filter_type = ($filter_params['type'] == 'having') ? HAVING_FILTER : WHERE_FILTER; $filter_value = str_replace(EDIT_MARK, $edit_mark, $filter_params['value']); $object->addFilter($filter_field, $filter_value, $filter_type, FLT_SEARCH); } } // add custom filter $view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view'); $custom_filters = $this->Application->RecallPersistentVar($event->getPrefixSpecial().'_custom_filter.'.$view_name); if ($custom_filters) { $grid_name = $event->getEventParam('grid'); $custom_filters = unserialize($custom_filters); if (isset($custom_filters[$grid_name])) { foreach ($custom_filters[$grid_name] as $field_name => $field_options) { list ($filter_type, $field_options) = each($field_options); if (isset($field_options['value']) && $field_options['value']) { $filter_type = ($field_options['sql_filter_type'] == 'having') ? HAVING_FILTER : WHERE_FILTER; $filter_value = str_replace(EDIT_MARK, $edit_mark, $field_options['value']); $object->addFilter($field_name, $filter_value, $filter_type, FLT_CUSTOM); } } } } $view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter'); if($view_filter) { $view_filter = unserialize($view_filter); $temp_filter =& $this->Application->makeClass('kMultipleFilter'); $filter_menu = $this->Application->getUnitOption($event->Prefix,'FilterMenu'); $group_key = 0; $group_count = count($filter_menu['Groups']); while($group_key < $group_count) { $group_info = $filter_menu['Groups'][$group_key]; $temp_filter->setType( constant('FLT_TYPE_'.$group_info['mode']) ); $temp_filter->clearFilters(); foreach ($group_info['filters'] as $flt_id) { $sql_key = getArrayValue($view_filter,$flt_id) ? 'on_sql' : 'off_sql'; if ($filter_menu['Filters'][$flt_id][$sql_key] != '') { $temp_filter->addFilter('view_filter_'.$flt_id, $filter_menu['Filters'][$flt_id][$sql_key]); } } $object->addFilter('view_group_'.$group_key, $temp_filter, $group_info['type'] , FLT_VIEW); $group_key++; } } } /** * Set's new sorting for list * * @param kEvent $event * @access protected */ function OnSetSorting(&$event) { $cur_sort1 = $this->Application->RecallVar($event->Prefix_Special.'_Sort1'); $cur_sort1_dir = $this->Application->RecallVar($event->Prefix_Special.'_Sort1_Dir'); $use_double_sorting = $this->Application->ConfigValue('UseDoubleSorting'); if ($use_double_sorting) { $cur_sort2 = $this->Application->RecallVar($event->Prefix_Special.'_Sort2'); $cur_sort2_dir = $this->Application->RecallVar($event->Prefix_Special.'_Sort2_Dir'); } $passed_sort1 = $this->Application->GetVar($event->getPrefixSpecial(true).'_Sort1'); if ($cur_sort1 == $passed_sort1) { $cur_sort1_dir = $cur_sort1_dir == 'asc' ? 'desc' : 'asc'; } else { if ($use_double_sorting) { $cur_sort2 = $cur_sort1; $cur_sort2_dir = $cur_sort1_dir; } $cur_sort1 = $passed_sort1; $cur_sort1_dir = 'asc'; } $this->Application->StoreVar($event->Prefix_Special.'_Sort1', $cur_sort1); $this->Application->StoreVar($event->Prefix_Special.'_Sort1_Dir', $cur_sort1_dir); if ($use_double_sorting) { $this->Application->StoreVar($event->Prefix_Special.'_Sort2', $cur_sort2); $this->Application->StoreVar($event->Prefix_Special.'_Sort2_Dir', $cur_sort2_dir); } } /** * Set sorting directly to session (used for category item sorting (front-end), grid sorting (admin, view menu) * * @param kEvent $event */ function OnSetSortingDirect(&$event) { $combined = $this->Application->GetVar($event->Prefix.'_CombinedSorting'); if ($combined) { list($field, $dir) = explode('|', $combined); $this->Application->StoreVar($event->Prefix.'_Sort1', $field); $this->Application->StoreVar($event->Prefix.'_Sort1_Dir', $dir); return ; } $field_pos = $this->Application->GetVar($event->Prefix.'_SortPos'); $this->Application->LinkVar($event->Prefix.'_Sort'.$field_pos, $event->Prefix.'_Sort'.$field_pos); $this->Application->LinkVar($event->Prefix.'_Sort'.$field_pos.'_Dir', $event->Prefix.'_Sort'.$field_pos.'_Dir'); } /** * Reset grid sorting to default (from config) * * @param kEvent $event */ function OnResetSorting(&$event) { $this->Application->RemoveVar($event->Prefix_Special.'_Sort1'); $this->Application->RemoveVar($event->Prefix_Special.'_Sort1_Dir'); $this->Application->RemoveVar($event->Prefix_Special.'_Sort2'); $this->Application->RemoveVar($event->Prefix_Special.'_Sort2_Dir'); } /** * Sets grid refresh interval * * @param kEvent $event */ function OnSetAutoRefreshInterval(&$event) { $refresh_interval = $this->Application->GetVar('refresh_interval'); $view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view'); $this->Application->StorePersistentVar($event->getPrefixSpecial().'_refresh_interval.'.$view_name, $refresh_interval); } /** * Changes auto-refresh state for grid * * @param kEvent $event */ function OnAutoRefreshToggle(&$event) { $refresh_intervals = $this->Application->ConfigValue('AutoRefreshIntervals'); if (!$refresh_intervals) { return ; } $view_name = $this->Application->RecallVar($event->getPrefixSpecial().'_current_view'); $auto_refresh = $this->Application->RecallPersistentVar($event->getPrefixSpecial().'_auto_refresh.'.$view_name); if ($auto_refresh === false) { $refresh_intervals = explode(',', $refresh_intervals); $this->Application->StorePersistentVar($event->getPrefixSpecial().'_refresh_interval.'.$view_name, $refresh_intervals[0]); } $this->Application->StorePersistentVar($event->getPrefixSpecial().'_auto_refresh.'.$view_name, $auto_refresh ? 0 : 1); } /** * Creates needed sql query to load item, * if no query is defined in config for * special requested, then use default * query * * @param kEvent $event * @access protected */ function ItemPrepareQuery(&$event) { $sqls = $this->Application->getUnitOption($event->Prefix, 'ItemSQLs', Array ()); $special = array_key_exists($event->Special, $sqls) ? $event->Special : ''; if (!array_key_exists($special, $sqls)) { // preferred special not found in ItemSQLs -> use analog from ListSQLs return $this->ListPrepareQuery($event); } return $sqls[$special]; } /** * Creates needed sql query to load list, * if no query is defined in config for * special requested, then use default * query * * @param kEvent $event * @access protected */ function ListPrepareQuery(&$event) { $sqls = $this->Application->getUnitOption($event->Prefix, 'ListSQLs', Array ()); return $sqls[ array_key_exists($event->Special, $sqls) ? $event->Special : '' ]; } /** * Apply custom processing to item * * @param kEvent $event */ function customProcessing(&$event, $type) { } /* Edit Events mostly used in Admin */ /** * Creates new kDBItem * * @param kEvent $event * @access protected */ function OnCreate(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if ($items_info) { list($id,$field_values) = each($items_info); $object->SetFieldsFromHash($field_values); } $this->customProcessing($event,'before'); //look at kDBItem' Create for ForceCreateId description, it's rarely used and is NOT set by default - if( $object->Create($event->getEventParam('ForceCreateId')) ) - { - if( $object->IsTempTable() ) $object->setTempID(); + if ( $object->Create($event->getEventParam('ForceCreateId')) ) { $this->customProcessing($event,'after'); $event->status=erSUCCESS; $event->redirect_params = Array('opener'=>'u'); } - else - { + else { $event->status = erFAIL; $event->redirect = false; $this->Application->SetVar($event->Prefix_Special.'_SaveEvent','OnCreate'); $object->setID($id); } } /** * Updates kDBItem * * @param kEvent $event * @access protected */ function OnUpdate(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $object =& $event->getObject( Array('skip_autoload' => true) ); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if ($items_info) { foreach ($items_info as $id => $field_values) { $object->Load($id); $object->SetFieldsFromHash($field_values); $this->customProcessing($event, 'before'); if ( $object->Update($id) ) { $this->customProcessing($event, 'after'); $event->status = erSUCCESS; } else { $event->status = erFAIL; $event->redirect = false; break; } } } $event->SetRedirectParam('opener', 'u'); } /** * Delete's kDBItem object * * @param kEvent $event * @access protected */ function OnDelete(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); /* @var $temp kTempTablesHandler */ $temp->DeleteItems($event->Prefix, $event->Special, Array($this->getPassedID($event))); } /** * Deletes all records from table * * @param kEvent $event */ function OnDeleteAll(&$event) { $sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName'); $ids = $this->Conn->GetCol($sql); if ($ids) { $temp_handler =& $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler'); /* @var $temp_handler kTempTablesHandler */ $temp_handler->DeleteItems($event->Prefix, $event->Special, $ids); } } /** * Prepares new kDBItem object * * @param kEvent $event * @access protected */ function OnNew(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); /* @var $object kDBItem */ $object->Clear(0); $this->Application->SetVar($event->Prefix_Special.'_SaveEvent', 'OnCreate'); if ($event->getEventParam('top_prefix') != $event->Prefix) { // this is subitem prefix, so use main item special $table_info = $object->getLinkedInfo( $this->getMainSpecial($event) ); } else { $table_info = $object->getLinkedInfo(); } $object->SetDBField($table_info['ForeignKey'], $table_info['ParentId']); $event->redirect = false; } /** * Cancel's kDBItem Editing/Creation * * @param kEvent $event * @access protected */ function OnCancel(&$event) { $object =& $event->getObject(Array('skip_autoload' => true)); $items_info = $this->Application->GetVar($event->getPrefixSpecial(true)); if ($items_info) { $delete_ids = Array(); $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); foreach ($items_info as $id => $field_values) { $object->Load($id); // record created for using with selector (e.g. Reviews->Select User), and not validated => Delete it if ($object->isLoaded() && !$object->Validate() && ($id <= 0) ) { $delete_ids[] = $id; } } if ($delete_ids) { $temp->DeleteItems($event->Prefix, $event->Special, $delete_ids); } } $event->redirect_params = Array('opener'=>'u'); } /** * Deletes all selected items. * Automatically recurse into sub-items using temp handler, and deletes sub-items * by calling its Delete method if sub-item has AutoDelete set to true in its config file * * @param kEvent $event */ function OnMassDelete(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $event->status=erSUCCESS; $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $ids = $this->StoreSelectedIDs($event); $event->setEventParam('ids', $ids); $this->customProcessing($event, 'before'); $ids = $event->getEventParam('ids'); if($ids) { $temp->DeleteItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } /** * Sets window id (of first opened edit window) to temp mark in uls * * @param kEvent $event */ function setTempWindowID(&$event) { $prefixes = Array ($event->Prefix, $event->getPrefixSpecial(true)); foreach ($prefixes as $prefix) { $mode = $this->Application->GetVar($prefix . '_mode'); if ($mode == 't') { $wid = $this->Application->GetVar('m_wid'); $this->Application->SetVar(str_replace('_', '.', $prefix) . '_mode', 't' . $wid); break; } } } /** * Prepare temp tables and populate it * with items selected in the grid * * @param kEvent $event */ function OnEdit(&$event) { $this->setTempWindowID($event); $ids = $this->StoreSelectedIDs($event); $var_name = $event->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid'); $this->Application->RemoveVar($var_name); $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); /* @var $temp kTempTablesHandler */ $temp->PrepareEdit(); $event->SetRedirectParam('m_lang', $this->Application->GetDefaultLanguageId()); $event->SetRedirectParam($event->getPrefixSpecial() . '_id', array_shift($ids)); $event->SetRedirectParam('pass', 'all,' . $event->getPrefixSpecial()); } /** * Saves content of temp table into live and * redirects to event' default redirect (normally grid template) * * @param kEvent $event */ function OnSave(&$event) { $event->CallSubEvent('OnPreSave'); if ($event->status == erSUCCESS) { $skip_master = false; $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $changes_var_name = $this->Prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); if (!$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $live_ids = $temp->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array()); if ($live_ids === false) { // coping from table failed, because we have another coping process to same table, that wasn't finished $event->status = erFAIL; return ; } // Deleteing files scheduled for delete $var_name = $event->getPrefixSpecial().'_file_pending_actions'.$this->Application->GetVar('m_wid'); $schedule = $this->Application->RecallVar($var_name); $schedule = $schedule ? unserialize($schedule) : array(); foreach ($schedule as $data) { if ($data['action'] == 'delete') { unlink($data['file']); } } if ($live_ids) { // ensure, that newly created item ids are avalable as if they were selected from grid // NOTE: only works if main item has subitems !!! $this->StoreSelectedIDs($event, $live_ids); } $this->SaveLoggedChanges($changes_var_name); } else { $this->Application->RemoveVar($changes_var_name); $event->status = erFAIL; } $this->clearSelectedIDs($event); $event->redirect_params = Array('opener' => 'u'); $this->Application->RemoveVar($event->getPrefixSpecial().'_modified'); // all temp tables are deleted here => all after hooks should think, that it's live mode now $this->Application->SetVar($event->Prefix.'_mode', ''); } } function SaveLoggedChanges($changes_var_name) { $ses_log_id = $this->Application->RecallVar('_SessionLogId_'); if (!$ses_log_id) { return ; } $changes = $this->Application->RecallVar($changes_var_name); $changes = $changes ? unserialize($changes) : Array (); if (!$changes) { return ; } $add_fields = Array ( 'PortalUserId' => $this->Application->RecallVar('user_id'), 'SessionLogId' => $ses_log_id, ); $changelog_table = $this->Application->getUnitOption('change-log', 'TableName'); $sessionlog_table = $this->Application->getUnitOption('session-log', 'TableName'); foreach ($changes as $rec) { $this->Conn->doInsert(array_merge($rec, $add_fields), $changelog_table); } $sql = 'UPDATE '.$sessionlog_table.' SET AffectedItems = AffectedItems + '.count($changes).' WHERE SessionLogId = '.$ses_log_id; $this->Conn->Query($sql); $this->Application->RemoveVar($changes_var_name); } /** * Cancels edit * Removes all temp tables and clears selected ids * * @param kEvent $event */ function OnCancelEdit(&$event) { $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $temp->CancelEdit(); $this->clearSelectedIDs($event); $event->redirect_params = Array('opener'=>'u'); $this->Application->RemoveVar($event->getPrefixSpecial().'_modified'); } /** * Allows to determine if we are creating new item or editing already created item * * @param kEvent $event * @return bool */ function isNewItemCreate(&$event) { $object =& $event->getObject( Array ('raise_warnings' => 0) ); return !$object->IsLoaded(); // $item_id = $this->getPassedID($event); // return ($item_id == '') ? true : false; } /** * Saves edited item into temp table * If there is no id, new item is created in temp table * * @param kEvent $event */ function OnPreSave(&$event) { //$event->redirect = false; // if there is no id - it means we need to create an item if (is_object($event->MasterEvent)) { $event->MasterEvent->setEventParam('IsNew',false); } if ($this->isNewItemCreate($event)) { $event->CallSubEvent('OnPreSaveCreated'); if (is_object($event->MasterEvent)) { $event->MasterEvent->setEventParam('IsNew',true); } return; } $object =& $event->getObject( Array('skip_autoload' => true) ); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if ($items_info) { foreach ($items_info as $id => $field_values) { $object->SetDefaultValues(); $object->Load($id); $object->SetFieldsFromHash($field_values); $this->customProcessing($event, 'before'); if( $object->Update($id) ) { $this->customProcessing($event, 'after'); $event->status=erSUCCESS; } else { $event->status = erFAIL; $event->redirect = false; break; } } } } /** * [HOOK] Saves subitem * * @param kEvent $event */ function OnPreSaveSubItem(&$event) { $not_created = $this->isNewItemCreate($event); $event->CallSubEvent($not_created ? 'OnCreate' : 'OnUpdate'); if ($event->status == erSUCCESS) { $object =& $event->getObject(); /* @var $object kDBItem */ $this->Application->SetVar($event->getPrefixSpecial() . '_id', $object->GetID()); } else { $event->MasterEvent->status = $event->status; } $event->SetRedirectParam('opener', 's'); } /** * Saves edited item in temp table and loads * item with passed id in current template * Used in Prev/Next buttons * * @param kEvent $event */ function OnPreSaveAndGo(&$event) { $event->CallSubEvent('OnPreSave'); if ($event->status == erSUCCESS) { $id = $this->Application->GetVar($event->getPrefixSpecial(true) . '_GoId'); $event->SetRedirectParam($event->getPrefixSpecial() . '_id', $id); } } /** * Saves edited item in temp table and goes * to passed tabs, by redirecting to it with OnPreSave event * * @param kEvent $event */ function OnPreSaveAndGoToTab(&$event) { $event->CallSubEvent('OnPreSave'); if ($event->status==erSUCCESS) { $event->redirect=$this->Application->GetVar($event->getPrefixSpecial(true).'_GoTab'); } } /** * Saves editable list and goes to passed tab, * by redirecting to it with empty event * * @param kEvent $event */ function OnUpdateAndGoToTab(&$event) { $event->setPseudoClass('_List'); $event->CallSubEvent('OnUpdate'); if ($event->status==erSUCCESS) { $event->redirect=$this->Application->GetVar($event->getPrefixSpecial(true).'_GoTab'); } } /** * Prepare temp tables for creating new item * but does not create it. Actual create is * done in OnPreSaveCreated * * @param kEvent $event */ function OnPreCreate(&$event) { $this->setTempWindowID($event); $this->clearSelectedIDs($event); $this->Application->SetVar('m_lang', $this->Application->GetDefaultLanguageId()); $object =& $event->getObject( Array('skip_autoload' => true) ); $temp =& $this->Application->recallObject($event->Prefix.'_TempHandler', 'kTempTablesHandler'); $temp->PrepareEdit(); $object->setID(0); $this->Application->SetVar($event->getPrefixSpecial().'_id', 0); $this->Application->SetVar($event->getPrefixSpecial().'_PreCreate', 1); $event->redirect = false; } /** * Creates a new item in temp table and * stores item id in App vars and Session on succsess * * @param kEvent $event */ function OnPreSaveCreated(&$event) { $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) $field_values = array_shift($items_info); $object =& $event->getObject( Array('skip_autoload' => true) ); $object->SetFieldsFromHash($field_values); $this->customProcessing($event, 'before'); if( $object->Create() ) { $this->customProcessing($event, 'after'); $event->redirect_params[$event->getPrefixSpecial(true).'_id'] = $object->GetId(); $event->status=erSUCCESS; } else { $event->status=erFAIL; $event->redirect=false; $object->setID(0); } } function OnReset(&$event) { //do nothing - should reset :) if ($this->isNewItemCreate($event)) { // just reset id to 0 in case it was create $object =& $event->getObject( Array('skip_autoload' => true) ); $object->setID(0); $this->Application->SetVar($event->getPrefixSpecial().'_id',0); } } /** * Apply same processing to each item beeing selected in grid * * @param kEvent $event * @access private */ function iterateItems(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $object =& $event->getObject( Array('skip_autoload' => true) ); $ids = $this->StoreSelectedIDs($event); if ($ids) { $status_field = array_shift( $this->Application->getUnitOption($event->Prefix,'StatusField') ); $order_field = $this->Application->getUnitOption($event->Prefix,'OrderField'); if (!$order_field) { $order_field = 'Priority'; } foreach ($ids as $id) { $object->Load($id); switch ($event->Name) { case 'OnMassApprove': $object->SetDBField($status_field, 1); break; case 'OnMassDecline': $object->SetDBField($status_field, 0); break; case 'OnMassMoveUp': $object->SetDBField($order_field, $object->GetDBField($order_field) + 1); break; case 'OnMassMoveDown': $object->SetDBField($order_field, $object->GetDBField($order_field) - 1); break; } if ($object->Update()) { $event->status = erSUCCESS; } else { $event->status = erFAIL; $event->redirect = false; break; } } } $this->clearSelectedIDs($event); } /** * Enter description here... * * @param kEvent $event */ function OnMassClone(&$event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = erFAIL; return; } $event->status = erSUCCESS; $temp =& $this->Application->recallObject($event->getPrefixSpecial().'_TempHandler', 'kTempTablesHandler'); $ids = $this->StoreSelectedIDs($event); if ($ids) { $temp->CloneItems($event->Prefix, $event->Special, $ids); } $this->clearSelectedIDs($event); } function check_array($records, $field, $value) { foreach ($records as $record) { if ($record[$field] == $value) { return true; } } return false; } function OnPreSavePopup(&$event) { $object =& $event->getObject(); $this->RemoveRequiredFields($object); $event->CallSubEvent('OnPreSave'); $this->finalizePopup($event); } /* End of Edit events */ // III. Events that allow to put some code before and after Update,Load,Create and Delete methods of item /** * Occurse before loading item, 'id' parameter * allows to get id of item beeing loaded * * @param kEvent $event * @access public */ function OnBeforeItemLoad(&$event) { } /** * Occurse after loading item, 'id' parameter * allows to get id of item that was loaded * * @param kEvent $event * @access public */ function OnAfterItemLoad(&$event) { } /** * Occurse before creating item * * @param kEvent $event * @access public */ function OnBeforeItemCreate(&$event) { } /** * Occurse after creating item * * @param kEvent $event * @access public */ function OnAfterItemCreate(&$event) { } /** * Occurse before updating item * * @param kEvent $event * @access public */ function OnBeforeItemUpdate(&$event) { } /** * Occurse after updating item * * @param kEvent $event * @access public */ function OnAfterItemUpdate(&$event) { } /** * Occurse before deleting item, id of item beeing * deleted is stored as 'id' event param * * @param kEvent $event * @access public */ function OnBeforeItemDelete(&$event) { } /** * Occurse after deleting item, id of deleted item * is stored as 'id' param of event * * @param kEvent $event * @access public */ function OnAfterItemDelete(&$event) { } /** * Occurs before validation attempt * * @param kEvent $event */ function OnBeforeItemValidate(&$event) { } /** * Occurs after successful item validation * * @param kEvent $event */ function OnAfterItemValidate(&$event) { } /** * Occures after an item has been copied to temp * Id of copied item is passed as event' 'id' param * * @param kEvent $event */ function OnAfterCopyToTemp(&$event) { } /** * Occures before an item is deleted from live table when copying from temp * (temp handler deleted all items from live and then copy over all items from temp) * Id of item being deleted is passed as event' 'id' param * * @param kEvent $event */ function OnBeforeDeleteFromLive(&$event) { } /** * Occures before an item is copied to live table (after all foreign keys have been updated) * Id of item being copied is passed as event' 'id' param * * @param kEvent $event */ function OnBeforeCopyToLive(&$event) { } /** * !!! NOT FULLY IMPLEMENTED - SEE TEMP HANDLER COMMENTS (search by event name)!!! * Occures after an item has been copied to live table * Id of copied item is passed as event' 'id' param * * @param kEvent $event */ function OnAfterCopyToLive(&$event) { } /** * Occures before an item is cloneded * Id of ORIGINAL item is passed as event' 'id' param * Do not call object' Update method in this event, just set needed fields! * * @param kEvent $event */ function OnBeforeClone(&$event) { } /** * Occures after an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event */ function OnAfterClone(&$event) { } /** * Occures after list is queried * * @param kEvent $event */ function OnAfterListQuery(&$event) { } /** * Ensures that popup will be closed automatically * and parent window will be refreshed with template * passed * * @param kEvent $event * @access public */ function finalizePopup(&$event) { $event->SetRedirectParam('opener', 'u'); } /** * Create search filters based on search query * * @param kEvent $event * @access protected */ function OnSearch(&$event) { $event->setPseudoClass('_List'); $search_helper =& $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $search_helper->performSearch($event); } /** * Clear search keywords * * @param kEvent $event * @access protected */ function OnSearchReset(&$event) { $search_helper =& $this->Application->recallObject('SearchHelper'); /* @var $search_helper kSearchHelper */ $search_helper->resetSearch($event); } /** * Set's new filter value (filter_id meaning from config) * * @param kEvent $event */ function OnSetFilter(&$event) { $filter_id = $this->Application->GetVar('filter_id'); $filter_value = $this->Application->GetVar('filter_value'); $view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter'); $view_filter = $view_filter ? unserialize($view_filter) : Array(); $view_filter[$filter_id] = $filter_value; $this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) ); } function OnSetFilterPattern(&$event) { $filters = $this->Application->GetVar($event->getPrefixSpecial(true).'_filters'); if (!$filters) return ; $view_filter = $this->Application->RecallVar($event->getPrefixSpecial().'_view_filter'); $view_filter = $view_filter ? unserialize($view_filter) : Array(); $filters = explode(',', $filters); foreach ($filters as $a_filter) { list($id, $value) = explode('=', $a_filter); $view_filter[$id] = $value; } $this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) ); $event->redirect = false; } /** * Add/Remove all filters applied to list from "View" menu * * @param kEvent $event */ function FilterAction(&$event) { $view_filter = Array(); $filter_menu = $this->Application->getUnitOption($event->Prefix,'FilterMenu'); switch ($event->Name) { case 'OnRemoveFilters': $filter_value = 1; break; case 'OnApplyFilters': $filter_value = 0; break; } foreach($filter_menu['Filters'] as $filter_key => $filter_params) { if(!$filter_params) continue; $view_filter[$filter_key] = $filter_value; } $this->Application->StoreVar( $event->getPrefixSpecial().'_view_filter', serialize($view_filter) ); } /** * Enter description here... * * @param kEvent $event */ function OnPreSaveAndOpenTranslator(&$event) { $this->Application->SetVar('allow_translation', true); $object =& $event->getObject(); $this->RemoveRequiredFields($object); $event->CallSubEvent('OnPreSave'); if ($event->status == erSUCCESS) { $resource_id = $this->Application->GetVar('translator_resource_id'); if ($resource_id) { $t_prefixes = explode(',', $this->Application->GetVar('translator_prefixes')); $cdata =& $this->Application->recallObject($t_prefixes[1], null, Array('skip_autoload' => true)); $cdata->Load($resource_id, 'ResourceId'); if (!$cdata->isLoaded()) { $cdata->SetDBField('ResourceId', $resource_id); $cdata->Create(); } $this->Application->SetVar($cdata->getPrefixSpecial().'_id', $cdata->GetID()); } $event->redirect = $this->Application->GetVar('translator_t'); $event->redirect_params = Array ( 'pass' => 'all,trans,' . $this->Application->GetVar('translator_prefixes'), 'opener' => 's', $event->getPrefixSpecial(true) . '_id' => $object->GetID(), 'trans_event' => 'OnLoad', 'trans_prefix' => $this->Application->GetVar('translator_prefixes'), 'trans_field' => $this->Application->GetVar('translator_field'), 'trans_multi_line' => $this->Application->GetVar('translator_multi_line'), ); // 1. SAVE LAST TEMPLATE TO SESSION (really needed here, because of tweaky redirect) $last_template = $this->Application->RecallVar('last_template'); preg_match('/index4\.php\|'.$this->Application->GetSID().'-(.*):/U', $last_template, $rets); $this->Application->StoreVar('return_template', $this->Application->GetVar('t')); } } function RemoveRequiredFields(&$object) { // making all field non-required to achieve successful presave foreach($object->Fields as $field => $options) { if(isset($options['required'])) { unset($object->Fields[$field]['required']); } } } /** * Saves selected user in needed field * * @param kEvent $event */ function OnSelectUser(&$event) { $items_info = $this->Application->GetVar('u'); if ($items_info) { $user_id = array_shift( array_keys($items_info) ); $object =& $event->getObject(); $this->RemoveRequiredFields($object); $is_new = !$object->isLoaded(); $is_main = substr($this->Application->GetVar($event->Prefix.'_mode'), 0, 1) == 't'; if ($is_new) { $new_event = $is_main ? 'OnPreCreate' : 'OnNew'; $event->CallSubEvent($new_event); $event->redirect = true; } $object->SetDBField($this->Application->RecallVar('dst_field'), $user_id); if ($is_new) { $object->Create(); - if (!$is_main && $object->IsTempTable()) { - $object->setTempID(); - } } else { $object->Update(); } } $event->SetRedirectParam($event->getPrefixSpecial().'_id', $object->GetID()); $this->finalizePopup($event); } /** EXPORT RELATED **/ /** * Shows export dialog * * @param kEvent $event */ function OnExport(&$event) { $selected_ids = $this->StoreSelectedIDs($event); if (implode(',', $selected_ids) == '') { // K4 fix when no ids found bad selected ids array is formed $selected_ids = false; } $this->Application->StoreVar($event->Prefix.'_export_ids', $selected_ids ? implode(',', $selected_ids) : '' ); $this->Application->LinkVar('export_finish_t'); $this->Application->LinkVar('export_progress_t'); $this->Application->StoreVar('export_oroginal_special', $event->Special); $export_helper =& $this->Application->recallObject('CatItemExportHelper'); /*list ($index_file, $env) = explode('|', $this->Application->RecallVar('last_template')); $finish_url = $this->Application->BaseURL('/admin').$index_file.'?'.ENV_VAR_NAME.'='.$env; $this->Application->StoreVar('export_finish_url', $finish_url);*/ $redirect_params = Array ( $this->Prefix . '.export_event' => 'OnNew', 'pass' => 'all,' . $this->Prefix . '.export' ); $event->setRedirectParams($redirect_params); } /** * Apply some special processing to * object beeing recalled before using * it in other events that call prepareObject * * @param Object $object * @param kEvent $event * @access protected */ function prepareObject(&$object, &$event) { if ($event->Special == 'export' || $event->Special == 'import') { $export_helper =& $this->Application->recallObject('CatItemExportHelper'); /* @var $export_helper kCatDBItemExportHelper */ $export_helper->prepareExportColumns($event); } } /** * Returns specific to each item type columns only * * @param kEvent $event * @return Array */ function getCustomExportColumns(&$event) { return Array(); } /** * Export form validation & processing * * @param kEvent $event */ function OnExportBegin(&$event) { $export_helper =& $this->Application->recallObject('CatItemExportHelper'); /* @var $export_helper kCatDBItemExportHelper */ $export_helper->OnExportBegin($event); } /** * Enter description here... * * @param kEvent $event */ function OnExportCancel(&$event) { $this->OnGoBack($event); } /** * Allows configuring export options * * @param kEvent $event */ function OnBeforeExportBegin(&$event) { } function OnDeleteExportPreset(&$event) { $object =& $event->GetObject(); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) { list($id,$field_values) = each($items_info); $preset_key = $field_values['ExportPresets']; $export_settings = $this->Application->RecallPersistentVar('export_settings'); if (!$export_settings) return ; $export_settings = unserialize($export_settings); if (!isset($export_settings[$event->Prefix])) return ; $to_delete = ''; $export_presets = array(''=>''); foreach ($export_settings[$event->Prefix] as $key => $val) { if (implode('|', $val['ExportColumns']) == $preset_key) { $to_delete = $key; break; } } if ($to_delete) { unset($export_settings[$event->Prefix][$to_delete]); $this->Application->StorePersistentVar('export_settings', serialize($export_settings)); } } } /** * Saves changes & changes language * * @param kEvent $event */ function OnPreSaveAndChangeLanguage(&$event) { $event->CallSubEvent('OnPreSave'); if ($event->status == erSUCCESS) { $this->Application->SetVar('m_lang', $this->Application->GetVar('language')); $pass_vars = Array ('st_id', 'cms_id'); foreach ($pass_vars as $pass_var) { $data = $this->Application->GetVar($pass_var); if ($data) { $event->SetRedirectParam($pass_var, $data); } } } } /** * Used to save files uploaded via swfuploader * * @param kEvent $event */ function OnUploadFile(&$event) { $event->status = erSTOP; define('DBG_SKIP_REPORTING', 1); echo "Flash requires that we output something or it won't fire the uploadSuccess event"; if (!$this->Application->HttpQuery->Post) { // Variables {field, id, flashsid} are always submitted through POST! // When file size is larger, then "upload_max_filesize" (in php.ini), // then theese variables also are not submitted -> handle such case. header('HTTP/1.0 413 File size exceeds allowed limit'); return ; } if (!$this->_checkFlashUploaderPermission($event)) { // 403 Forbidden header('HTTP/1.0 403 You don\'t have permissions to upload'); return ; } $value = $this->Application->GetVar('Filedata'); if (!$value || ($value['error'] != UPLOAD_ERR_OK)) { // 413 Request Entity Too Large (file uploads disabled OR uploaded file was // to large for web server to accept, see "upload_max_filesize" in php.ini) header('HTTP/1.0 413 File size exceeds allowed limit'); return ; } $tmp_path = WRITEABLE . '/tmp/'; $fname = $value['name']; $id = $this->Application->GetVar('id'); if ($id) { $fname = $id.'_'.$fname; } $field_name = $this->Application->GetVar('field'); $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); if (array_key_exists($field_name, $fields)) { $upload_dir = $fields[$field_name]['upload_dir']; } else { $virtual_fields = $this->Application->getUnitOption($event->Prefix, 'VirtualFields'); $upload_dir = $virtual_fields[$field_name]['upload_dir']; } if (!is_writable($tmp_path) || !is_writable(FULL_PATH . $upload_dir)) { // 500 Internal Server Error // check both temp and live upload directory header('HTTP/1.0 500 Write permissions not set on the server'); return ; } move_uploaded_file($value['tmp_name'], $tmp_path.$fname); } /** * Checks, that flash uploader is allowed to perform upload * * @param kEvent $event * @return bool */ function _checkFlashUploaderPermission(&$event) { // Flash uploader does NOT send correct cookies, so we need to make our own check $cookie_name = 'adm_' . $this->Application->ConfigValue('SessionCookieName'); $this->Application->HttpQuery->Cookie['cookies_on'] = 1; $this->Application->HttpQuery->Cookie[$cookie_name] = $this->Application->GetVar('flashsid'); // this prevents session from auto-expiring when KeepSessionOnBrowserClose & FireFox is used $this->Application->HttpQuery->Cookie[$cookie_name . '_live'] = $this->Application->GetVar('flashsid'); $admin_ses =& $this->Application->recallObject('Session.admin'); /* @var $admin_ses Session */ if ($admin_ses->RecallVar('user_id') == -1) { return true; } $backup_user_id = $this->Application->RecallVar('user_id'); // 1. backup user $this->Application->StoreVar('user_id', $admin_ses->RecallVar('user_id')); // 2. fake user_id $check_event = new kEvent($event->getPrefixSpecial() . ':OnProcessSelected'); // 3. event, that have "add|edit" rule $check_event->setEventParam('top_prefix', $this->Application->GetTopmostPrefix($event->Prefix, true)); $allowed_to_upload = $this->CheckPermission($check_event); // 4. check permission $this->Application->StoreVar('user_id', $backup_user_id); // 5. restore user id return $allowed_to_upload; } /** * Enter description here... * * @param kEvent $event */ function OnDeleteFile(&$event) { $event->status = erSTOP; if (strpos($this->Application->GetVar('file'), '../') !== false) { return ; } $object =& $event->getObject( Array ('skip_autoload' => true) ); $options = $object->GetFieldOptions( $this->Application->GetVar('field') ); $var_name = $event->getPrefixSpecial() . '_file_pending_actions' . $this->Application->GetVar('m_wid'); $schedule = $this->Application->RecallVar($var_name); $schedule = $schedule ? unserialize($schedule) : Array (); $schedule[] = Array ('action' => 'delete', 'file' => $path = FULL_PATH . $options['upload_dir'] . $this->Application->GetVar('file')); $this->Application->StoreVar($var_name, serialize($schedule)); } /** * Enter description here... * * @param kEvent $event */ function OnViewFile(&$event) { $file = $this->Application->GetVar('file'); if ((strpos($file, '../') !== false) || (trim($file) !== $file)) { // when relative paths or special chars are found template names from url, then it's hacking attempt return ; } if ($this->Application->GetVar('tmp')) { $path = WRITEABLE . '/tmp/' . $this->Application->GetVar('id') . '_' . $this->Application->GetVar('file'); } else { $object =& $event->getObject(array('skip_autoload'=>true)); $options = $object->GetFieldOptions($this->Application->GetVar('field')); $path = FULL_PATH.$options['upload_dir'].$file; } $path = str_replace('/', DIRECTORY_SEPARATOR, $path); if (!file_exists($path)) { exit; } $type = mime_content_type($path); header('Content-Length: '.filesize($path)); header('Content-Type: '.$type); safeDefine('DBG_SKIP_REPORTING',1); readfile($path); exit(); } /** * Validates MInput control fields * * @param kEvent $event */ function OnValidateMInputFields(&$event) { $minput_helper =& $this->Application->recallObject('MInputHelper'); /* @var $minput_helper MInputHelper */ $minput_helper->OnValidateMInputFields($event); } /** * Returns auto-complete values for ajax-dropdown * * @param kEvent $event */ function OnSuggestValues(&$event) { if (!$this->Application->isAdminUser) { // very careful here, because this event allows to // view every object field -> limit only to logged-in admins return ; } $event->status = erSTOP; $field = $this->Application->GetVar('field'); $cur_value = $this->Application->GetVar('cur_value'); $object =& $event->getObject(); if (!$field || !$cur_value || !array_key_exists($field, $object->Fields)) { return ; } $limit = $this->Application->GetVar('limit'); if (!$limit) { $limit = 20; } $sql = 'SELECT DISTINCT '.$field.' FROM '.$object->TableName.' WHERE '.$field.' LIKE '.$this->Conn->qstr($cur_value.'%').' ORDER BY '.$field.' LIMIT 0,' . $limit; $data = $this->Conn->GetCol($sql); $this->Application->XMLHeader(); echo '<suggestions>'; foreach ($data as $item) { echo '<item>' . htmlspecialchars($item) . '</item>'; } echo '</suggestions>'; } /** * Enter description here... * * @param kEvent $event */ function OnSaveWidths(&$event) { $event->status = erSTOP; $lang =& $this->Application->recallObject('lang.current'); // header('Content-type: text/xml; charset='.$lang->GetDBField('Charset')); $picker_helper =& $this->Application->RecallObject('ColumnPickerHelper'); /* @var $picker_helper kColumnPickerHelper */ $picker_helper->PreparePicker($event->getPrefixSpecial(), $this->Application->GetVar('grid_name')); $picker_helper->SaveWidths($event->getPrefixSpecial(), $this->Application->GetVar('widths')); echo 'OK'; } /** * Called from CSV import script after item fields * are set and validated, but before actual item create/update. * If event status is erSUCCESS, line will be imported, * else it will not be imported but added to skipped lines * and displayed in the end of import. * Event status is preset from import script. * * @param kEvent $event */ function OnBeforeCSVLineImport(&$event) { // abstract, for hooking } /** * [HOOK] Allows to add cloned subitem to given prefix * * @param kEvent $event */ function OnCloneSubItem(&$event) { $clones = $this->Application->getUnitOption($event->MasterEvent->Prefix, 'Clones'); $subitem_prefix = $event->Prefix . '-' . preg_replace('/^#/', '', $event->MasterEvent->Prefix); $clones[$subitem_prefix] = Array ('ParentPrefix' => $event->Prefix); $this->Application->setUnitOption($event->MasterEvent->Prefix, 'Clones', $clones); } } \ No newline at end of file Index: branches/5.0.x/core/kernel/db/dbitem.php =================================================================== --- branches/5.0.x/core/kernel/db/dbitem.php (revision 13602) +++ branches/5.0.x/core/kernel/db/dbitem.php (revision 13603) @@ -1,1314 +1,1312 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); /** * DBItem * * Desciption * @package kernel4 */ class kDBItem extends kDBBase { /** * Description * * @var array Associative array of current item' field values * @access public */ var $FieldValues; /** * Unformatted field values, before parse * * @var Array * @access private */ var $DirtyFieldValues = Array(); /** * Holds item values after loading (not affected by submit) * * @var Array * @access private */ var $OriginalFieldValues = Array (); var $FieldErrors; var $ErrorMsgs = Array(); /** * If set to true, Update will skip Validation before running * * @var array Associative array of current item' field values * @access public */ var $IgnoreValidation = false; var $Loaded = false; /** * Holds item' primary key value * * @var int Value of primary key field for current item * @access public */ var $ID; /** * This object is used in cloning operations * * @var bool */ var $inCloning = false; function kDBItem() { parent::kDBBase(); $this->ErrorMsgs['required'] = '!la_err_required!'; //'Field is required'; $this->ErrorMsgs['unique'] = '!la_err_unique!'; //'Field value must be unique'; $this->ErrorMsgs['value_out_of_range'] = '!la_err_value_out_of_range!'; //'Field is out of range, possible values from %s to %s'; $this->ErrorMsgs['length_out_of_range'] = '!la_err_length_out_of_range!'; //'Field is out of range'; $this->ErrorMsgs['bad_type'] = '!la_err_bad_type!'; //'Incorrect data format, please use %s'; $this->ErrorMsgs['invalid_format'] = '!la_err_invalid_format!'; //'Incorrect data format, please use %s'; $this->ErrorMsgs['bad_date_format'] = '!la_err_bad_date_format!'; //'Incorrect date format, please use (%s) ex. (%s)'; $this->ErrorMsgs['primary_lang_required'] = '!la_err_primary_lang_required!'; } function SetDirtyField($field_name, $field_value) { $this->DirtyFieldValues[$field_name] = $field_value; } function GetDirtyField($field_name) { return $this->DirtyFieldValues[$field_name]; } function GetOriginalField($field_name, $formatted = false, $format=null) { if (array_key_exists($field_name, $this->OriginalFieldValues)) { // item was loaded before $value = $this->OriginalFieldValues[$field_name]; } else { // no original fields -> use default field value $value = $this->Fields[$field_name]['default']; } if (!$formatted) { return $value; } $options = $this->GetFieldOptions($field_name); $res = $value; if (array_key_exists('formatter', $options)) { $formatter =& $this->Application->recallObject($options['formatter']); /* @var $formatter kFormatter */ $res = $formatter->Format($value, $field_name, $this, $format); } return $res; } /** * Sets original field value (useful for custom virtual fields) * * @param string $field_name */ function SetOriginalField($field_name, $field_value) { $this->OriginalFieldValues[$field_name] = $field_value; } /** * Set's default values for all fields * * @param bool $populate_ml_fields create all ml fields from db in config or not * * @access public */ function SetDefaultValues($populate_ml_fields = false) { parent::SetDefaultValues($populate_ml_fields); if ($populate_ml_fields) { $this->PopulateMultiLangFields(); } foreach ($this->Fields as $field => $params) { if ( isset($params['default']) ) { $this->SetDBField($field, $params['default']); } else { $this->SetDBField($field, NULL); } } } /** * Sets current item field value * (applies formatting) * * @access public * @param string $name Name of the field * @param mixed $value Value to set the field to * @return void */ function SetField($name,$value) { $options = $this->GetFieldOptions($name); $parsed = $value; if ($value == '') { $parsed = NULL; } // kFormatter is always used, to make sure, that numeric value is converted to normal representation // according to regional format, even when formatter is not set (try seting format to 1.234,56 to understand why) $formatter =& $this->Application->recallObject(isset($options['formatter']) ? $options['formatter'] : 'kFormatter'); $parsed = $formatter->Parse($value, $name, $this); $this->SetDBField($name,$parsed); } /** * Sets current item field value * (doesn't apply formatting) * * @access public * @param string $name Name of the field * @param mixed $value Value to set the field to * @return void */ function SetDBField($name,$value) { $this->FieldValues[$name] = $value; /*if (isset($this->Fields[$name]['formatter'])) { $formatter =& $this->Application->recallObject($this->Fields[$name]['formatter']); $formatter->UpdateSubFields($name, $value, $this->Fields[$name], $this); }*/ } /** * Set's field error, if pseudo passed not found then create it with message text supplied. * Don't owerrite existing pseudo translation. * * @param string $field * @param string $pseudo * @param string $error_label * * @return bool */ function SetError($field, $pseudo, $error_label = null, $error_params = null) { $error_field = isset($this->Fields[$field]['error_field']) ? $this->Fields[$field]['error_field'] : $field; if (isset($this->FieldErrors[$error_field]['pseudo'])) { // don't set more then one error on field return false; } $this->FieldErrors[$error_field]['pseudo'] = $pseudo; if (isset($error_params)) { if (array_key_exists('value', $error_params)) { $this->FieldErrors[$error_field]['value'] = $error_params['value']; unset($error_params['value']); } // additional params, that helps to determine error sources $this->FieldErrors[$error_field]['params'] = $error_params; } if (isset($error_label) && !isset($this->ErrorMsgs[$pseudo])) { // label for error (only when not already set) $this->ErrorMsgs[$pseudo] = (substr($error_label, 0, 1) == '+') ? substr($error_label, 1) : '!'.$error_label.'!'; } return true; } /** * Return current item' field value by field name * (doesn't apply formatter) * * @access public * @param string $name field name to return * @return mixed */ function GetDBField($name) { /*if (!array_key_exists($name, $this->FieldValues) && defined('DEBUG_MODE') && DEBUG_MODE) { $this->Application->Debugger->appendTrace(); }*/ return $this->FieldValues[$name]; } function HasField($name) { return isset($this->FieldValues[$name]); } function GetFieldValues() { return $this->FieldValues; } /** * Sets item' fields corresponding to elements in passed $hash values. * * The function sets current item fields to values passed in $hash, by matching $hash keys with field names * of current item. If current item' fields are unknown {@link kDBItem::PrepareFields()} is called before acutally setting the fields * * @access public * @param Array $hash * @param Array $set_fields Optional param, field names in target object to set, other fields will be skipped * @return void */ function SetFieldsFromHash($hash, $set_fields = null) { // used in formatter which work with multiple fields together foreach($hash as $field_name => $field_value) { if (is_numeric($field_name) || !array_key_exists($field_name, $this->Fields)) { continue; } if (is_array($set_fields) && !in_array($field_name, $set_fields)) { continue; } $this->SetDirtyField($field_name, $field_value); } // formats all fields using associated formatters foreach ($hash as $field_name => $field_value) { if (is_numeric($field_name) || !array_key_exists($field_name, $this->Fields)) { continue; } if (is_array($set_fields) && !in_array($field_name, $set_fields)) { continue; } $this->SetField($field_name,$field_value); } } function SetDBFieldsFromHash($hash, $set_fields = null) { foreach ($hash as $field_name => $field_value) { if (is_numeric($field_name) || !array_key_exists($field_name, $this->Fields)) { continue; } if (is_array($set_fields) && !in_array($field_name, $set_fields)) { continue; } $this->SetDBField($field_name, $field_value); } } /** * Returns part of SQL WHERE clause identifing the record, ex. id = 25 * * @access public * @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method * @param Array $keys_hash alternative, then item id, keys hash to load item by * @return void * @see kDBItem::Load() * @see kDBItem::Update() * @see kDBItem::Delete() */ function GetKeyClause($method=null, $keys_hash = null) { if (!isset($keys_hash)) { $keys_hash = Array ($this->IDField => $this->ID); } $ret = ''; foreach ($keys_hash as $field => $value) { if (!preg_match('/\./', $field)) { $ret .= '(`' . $this->TableName . '`.' . $field . ' = ' . $this->Conn->qstr($value) . ') AND '; } else { $ret .= '(' . $field . ' = ' . $this->Conn->qstr($value) . ') AND '; } } return substr($ret, 0, -5); } /** * Loads item from the database by given id * * @access public * @param mixed $id item id of keys->values hash to load item by * @param string $id_field_name Optional parameter to load item by given Id field * @return bool True if item has been loaded, false otherwise */ function Load($id, $id_field_name = null) { if ( isset($id_field_name) ) { $this->SetIDField($id_field_name); // set new IDField } $keys_sql = ''; if (is_array($id)) { $keys_sql = $this->GetKeyClause('load', $id); } else { $this->setID($id); $keys_sql = $this->GetKeyClause('load'); } if ( isset($id_field_name) ) { // restore original IDField from unit config $this->setIDField( $this->Application->getUnitOption($this->Prefix, 'IDField') ); } if (($id === false) || !$keys_sql) { return $this->Clear(); } if (!$this->raiseEvent('OnBeforeItemLoad', $id)) { return false; } $q = $this->GetSelectSQL() . ' WHERE ' . $keys_sql; $field_values = $this->Conn->GetRow($q); if ($field_values) { $this->FieldValues = array_merge_recursive2($this->FieldValues, $field_values); $this->OriginalFieldValues = $this->FieldValues; } else { return $this->Clear(); } if (is_array($id) || isset($id_field_name)) { $this->setID($this->FieldValues[$this->IDField]); } $this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example) $this->raiseEvent('OnAfterItemLoad', $this->GetID()); $this->Loaded = true; return true; } /** * Builds select sql, SELECT ... FROM parts only * * @access public * @return string */ function GetSelectSQL() { $sql = $this->addCalculatedFields($this->SelectClause); return parent::GetSelectSQL($sql); } function UpdateFormattersMasterFields() { foreach ($this->Fields as $field => $options) { if (isset($options['formatter'])) { $formatter =& $this->Application->recallObject($options['formatter']); $formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this); } } } /** * Allows to skip certain fields from getting into sql queries * * @param string $field_name * @param mixed $force_id * @return bool */ function skipField($field_name, $force_id = false) { $skip = false; // 1. skipping 'virtual' field $skip = $skip || array_key_exists($field_name, $this->VirtualFields); // 2. don't write empty field value to db, when "skip_empty" option is set $field_value = array_key_exists($field_name, $this->FieldValues) ? $this->FieldValues[$field_name] : false; if (array_key_exists($field_name, $this->Fields)) { $skip_empty = array_key_exists('skip_empty', $this->Fields[$field_name]) ? $this->Fields[$field_name]['skip_empty'] : false; } else { // field found in database, but not declared in unit config $skip_empty = false; } $skip = $skip || (!$field_value && $skip_empty); // 3. skipping field not in Fields (nor virtual, nor real) $skip = $skip || !array_key_exists($field_name, $this->Fields); return $skip; } /** * Updates previously loaded record with current item' values * * @access public * @param int Primery Key Id to update * @return bool */ function Update($id = null, $system_update = false) { if (isset($id)) { $this->setID($id); } if (!$this->raiseEvent('OnBeforeItemUpdate')) { return false; } if (!isset($this->ID)) { // ID could be set inside OnBeforeItemUpdate event, so don't combine this check with previous one return false; } // validate before updating if (!$this->Validate()) { return false; } if (!$this->FieldValues) { // nothing to update return true; } $sql = ''; foreach ($this->FieldValues as $field_name => $field_value) { if ($this->skipField($field_name)) { continue; } if ( is_null($field_value) ) { if (array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null']) { // "kFormatter::Parse" methods converts empty values to NULL and for // not-null fields they are replaced with default value here $field_value = $this->Fields[$field_name]['default']; } } $sql .= '`' . $field_name . '` = ' . $this->Conn->qstr($field_value) . ', '; } $sql = 'UPDATE ' . $this->TableName . ' SET ' . substr($sql, 0, -2) . ' WHERE ' . $this->GetKeyClause('update'); if ($this->Conn->ChangeQuery($sql) === false) { // there was and sql error return false; } $affected_rows = $this->Conn->getAffectedRows(); if (!$system_update && ($affected_rows > 0)) { $this->setModifiedFlag(clUPDATE); } $this->saveCustomFields(); if ($affected_rows > 0) { $this->raiseEvent('OnAfterItemUpdate'); } $this->OriginalFieldValues = $this->FieldValues; $this->Loaded = true; if ($this->mode != 't') { $this->Application->resetCounters($this->TableName); } return true; } function ValidateField($field) { $options = $this->Fields[$field]; /*if (isset($options['formatter'])) { $formatter =& $this->Application->recallObject($options['formatter']); $formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this); }*/ $error_field = isset($options['error_field']) ? $options['error_field'] : $field; $res = !isset($this->FieldErrors[$error_field]['pseudo']) || !$this->FieldErrors[$error_field]['pseudo']; $res = $res && $this->ValidateRequired($field, $options); $res = $res && $this->ValidateType($field, $options); $res = $res && $this->ValidateRange($field, $options); $res = $res && $this->ValidateUnique($field, $options); $res = $res && $this->CustomValidation($field, $options); return $res; } /** * Validate all item fields based on * constraints set in each field options * in config * * @return bool * @access private */ function Validate() { $this->UpdateFormattersMasterFields(); //order is critical - should be called BEFORE checking errors if ($this->IgnoreValidation) { return true; } // will apply any custom validation to the item $this->raiseEvent('OnBeforeItemValidate'); $global_res = true; foreach ($this->Fields as $field => $params) { $res = $this->ValidateField($field); $global_res = $global_res && $res; } if (!$global_res && $this->Application->isDebugMode()) { $error_msg = ' Validation failed in prefix <strong>'.$this->Prefix.'</strong>, FieldErrors follow (look at items with <strong>"pseudo"</strong> key set)<br /> You may ignore this notice if submitted data really has a validation error'; trigger_error(trim($error_msg), E_USER_NOTICE); $this->Application->Debugger->dumpVars($this->FieldErrors); } if ($global_res) { // no validation errors $this->raiseEvent('OnAfterItemValidate'); } return $global_res; } /** * Check field value by user-defined alghoritm * * @param string $field field name * @param Array $params field options from config * @return bool */ function CustomValidation($field, $params) { return true; } /** * Check if item has errors * * @param Array $skip_fields fields to skip during error checking * @return bool */ function HasErrors($skip_fields) { $global_res = false; foreach ($this->Fields as $field => $field_params) { // If Formatter has set some error messages during values parsing if ( !( in_array($field, $skip_fields) ) && isset($this->FieldErrors[$field]['pseudo']) && $this->FieldErrors[$field] != '') { $global_res = true; } } return $global_res; } /** * Check if value in field matches field type specified in config * * @param string $field field name * @param Array $params field options from config * @return bool */ function ValidateType($field, $params) { $res = true; $val = $this->FieldValues[$field]; if ( $val != '' && isset($params['type']) && preg_match("#int|integer|double|float|real|numeric|string#", $params['type']) ) { if ($params['type'] == 'numeric') { trigger_error('Invalid field type <strong>'.$params['type'].'</strong> (in ValidateType method), please use <strong>float</strong> instead', E_USER_NOTICE); $params['type'] = 'float'; } $res = is_numeric($val); if ($params['type']=='string' || $res) { $f = 'is_'.$params['type']; settype($val, $params['type']); $res = $f($val) && ($val == $this->FieldValues[$field]); } if (!$res) { $this->SetError($field, 'bad_type', null, $params['type']); } } return $res; } /** * Check if value is set for required field * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ function ValidateRequired($field, $params) { $res = true; if (isset($params['required']) && $params['required']) { $check_value = $this->FieldValues[$field]; if ($this->Application->ConfigValue('TrimRequiredFields')) { $check_value = trim($check_value); } $res = ((string)$check_value != ''); } if (!$res) { $this->SetError($field, 'required'); } return $res; } /** * Validates that current record has unique field combination among other table records * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ function ValidateUnique($field, $params) { $res = true; $unique_fields = getArrayValue($params,'unique'); if($unique_fields !== false) { $where = Array(); array_push($unique_fields,$field); foreach($unique_fields as $unique_field) { // if field is not empty or if it is required - we add where condition if ((string)$this->GetDBField($unique_field) != '' || (isset($this->Fields[$unique_field]['required']) && $this->Fields[$unique_field]['required'])) { $where[] = '`'.$unique_field.'` = '.$this->Conn->qstr( $this->GetDBField($unique_field) ); } else { // not good if we check by less fields than indicated return true; } } // This can ONLY happen if all unique fields are empty and not required. // In such case we return true, because if unique field is not required there may be numerous empty values // if (!$where) return true; $sql = 'SELECT COUNT(*) FROM %s WHERE ('.implode(') AND (',$where).') AND ('.$this->IDField.' <> '.(int)$this->ID.')'; $res_temp = $this->Conn->GetOne( str_replace('%s', $this->TableName, $sql) ); $current_table_only = getArrayValue($params, 'current_table_only'); // check unique record only in current table $res_live = $current_table_only ? 0 : $this->Conn->GetOne( str_replace('%s', $this->Application->GetLiveName($this->TableName), $sql) ); $res = ($res_temp == 0) && ($res_live == 0); if (!$res) { $this->SetError($field, 'unique'); } } return $res; } /** * Check if field value is in range specified in config * * @param string $field field name * @param Array $params field options from config * @return bool * @access private */ function ValidateRange($field, $params) { $res = true; $val = $this->FieldValues[$field]; if ( isset($params['type']) && preg_match("#int|integer|double|float|real#", $params['type']) && strlen($val) > 0 ) { if ( isset($params['max_value_inc'])) { $res = $res && $val <= $params['max_value_inc']; $max_val = $params['max_value_inc'].' (inclusive)'; } if ( isset($params['min_value_inc'])) { $res = $res && $val >= $params['min_value_inc']; $min_val = $params['min_value_inc'].' (inclusive)'; } if ( isset($params['max_value_exc'])) { $res = $res && $val < $params['max_value_exc']; $max_val = $params['max_value_exc'].' (exclusive)'; } if ( isset($params['min_value_exc'])) { $res = $res && $val > $params['min_value_exc']; $min_val = $params['min_value_exc'].' (exclusive)'; } } if (!$res) { if ( !isset($min_val) ) $min_val = '-∞'; if ( !isset($max_val) ) $max_val = '∞'; $this->SetError($field, 'value_out_of_range', null, Array ($min_val, $max_val)); return $res; } if ( isset($params['max_len'])) { $res = $res && mb_strlen($val) <= $params['max_len']; } if ( isset($params['min_len'])) { $res = $res && mb_strlen($val) >= $params['min_len']; } if (!$res) { $error_params = Array (getArrayValue($params, 'min_len'), getArrayValue($params, 'max_len')); $this->SetError($field, 'length_out_of_range', null, $error_params); return $res; } return $res; } /** * Return error message for field * * @param string $field * @return string * @access public */ function GetErrorMsg($field, $force_escape = null) { if( !isset($this->FieldErrors[$field]) ) return ''; $err = getArrayValue($this->FieldErrors[$field], 'pseudo'); if (!$err) return ''; // if special error msg defined in config if( isset($this->Fields[$field]['error_msgs'][$err]) ) { $msg = $this->Fields[$field]['error_msgs'][$err]; } else //fall back to defaults { if( !isset($this->ErrorMsgs[$err]) ) { trigger_error('No user message is defined for pseudo error <b>'.$err.'</b><br>', E_USER_WARNING); return $err; //return the pseudo itself } $msg = $this->ErrorMsgs[$err]; } $msg = $this->Application->ReplaceLanguageTags($msg, $force_escape); if ( isset($this->FieldErrors[$field]['params']) ) { return vsprintf($msg, $this->FieldErrors[$field]['params']); } return $msg; } /** * Creates a record in the database table with current item' values * * @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE! * @access public * @return bool */ function Create($force_id = false, $system_create = false) { if (!$this->raiseEvent('OnBeforeItemCreate')) { return false; } // Validating fields before attempting to create record if (!$this->Validate()) { return false; } if (is_int($force_id)) { $this->FieldValues[$this->IDField] = $force_id; } elseif (!$force_id || !is_bool($force_id)) { $this->FieldValues[$this->IDField] = $this->generateID(); } $fields_sql = ''; $values_sql = ''; foreach ($this->FieldValues as $field_name => $field_value) { if ($this->skipField($field_name, $force_id)) { continue; } if (is_null($field_value)) { if (array_key_exists('not_null', $this->Fields[$field_name]) && $this->Fields[$field_name]['not_null']) { // "kFormatter::Parse" methods converts empty values to NULL and for // not-null fields they are replaced with default value here $values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default']); } else { $values_sql .= $this->Conn->qstr($field_value); } } else { if (($field_name == $this->IDField) && ($field_value == 0)) { // don't skip IDField in INSERT statement, just use DEFAULT keyword as it's value $values_sql .= 'DEFAULT'; } else { $values_sql .= $this->Conn->qstr($field_value); } } $fields_sql .= '`' . $field_name . '`, '; //Adding field name to fields block of Insert statement $values_sql .= ', '; } $sql = 'INSERT INTO ' . $this->TableName . ' (' . substr($fields_sql, 0, -2) . ') VALUES (' . substr($values_sql, 0, -2) . ')'; //Executing the query and checking the result if ($this->Conn->ChangeQuery($sql) === false) { return false; } $this->OriginalFieldValues = $this->FieldValues; $insert_id = $this->Conn->getInsertID(); if ($insert_id == 0) { // insert into temp table (id is not auto-increment field) $insert_id = $this->FieldValues[$this->IDField]; } $this->setID($insert_id); if (!$system_create){ $this->setModifiedFlag(clCREATE); } $this->saveCustomFields(); if ($this->mode != 't') { $this->Application->resetCounters($this->TableName); } + + if ($this->IsTempTable() && ($this->Application->GetTopmostPrefix($this->Prefix) != $this->Prefix)) { + // temp table + subitem = set negative id + $this->setTempID(); + } + $this->raiseEvent('OnAfterItemCreate'); $this->Loaded = true; return true; } /** * Deletes the record from databse * * @access public * @return bool */ function Delete($id = null) { if (isset($id)) { $this->setID($id); } if (!$this->raiseEvent('OnBeforeItemDelete')) { return false; } $sql = 'DELETE FROM ' . $this->TableName . ' WHERE ' . $this->GetKeyClause('Delete'); $ret = $this->Conn->ChangeQuery($sql); $affected_rows = $this->Conn->getAffectedRows(); $this->setModifiedFlag(clDELETE); // will change affected rows, so get it before this line if ($affected_rows > 0) { // something was actually deleted $this->raiseEvent('OnAfterItemDelete'); } if ($this->mode != 't') { $this->Application->resetCounters($this->TableName); } return $ret; } function PopulateMultiLangFields() { foreach ($this->Fields as $field => $options) { // master field is set only for CURRENT language $formatter = array_key_exists('formatter', $options) ? $options['formatter'] : false; if (($formatter == 'kMultiLanguage') && array_key_exists('master_field', $options) && array_key_exists('error_field', $options)) { // MuliLanguage formatter sets error_field to master_field, but in PopulateMlFields mode, // we display ML fields directly so we set it back to itself, otherwise error will not be displayed unset($this->Fields[$field]['error_field']); } } } /** * Sets new name for item in case if it is beeing copied * in same table * * @param array $master Table data from TempHandler * @param int $foreign_key ForeignKey value to filter name check query by * @param string $title_field FieldName to alter, by default - TitleField of the prefix * @param string $format sprintf-style format of renaming pattern, by default Copy %1$s of %2$s which makes it Copy [Number] of Original Name * @access private */ function NameCopy($master=null, $foreign_key=null, $title_field=null, $format='Copy %1$s of %2$s') { if (!isset($title_field)) { $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return; } $new_name = $this->GetDBField($title_field); $original_checked = false; do { if ( preg_match('/'.sprintf($format, '([0-9]*) *', '(.*)').'/', $new_name, $regs) ) { $new_name = sprintf($format, ($regs[1]+1), $regs[2]); } elseif ($original_checked) { $new_name = sprintf($format, '', $new_name); } // if we are cloning in temp table this will look for names in temp table, // since object' TableName contains correct TableName (for temp also!) // if we are cloning live - look in live $query = 'SELECT '.$title_field.' FROM '.$this->TableName.' WHERE '.$title_field.' = '.$this->Conn->qstr($new_name); $foreign_key_field = getArrayValue($master, 'ForeignKey'); $foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field; if ($foreign_key_field && isset($foreign_key)) { $query .= ' AND '.$foreign_key_field.' = '.$foreign_key; } $res = $this->Conn->GetOne($query); /*// if not found in live table, check in temp table if applicable if ($res === false && $object->Special == 'temp') { $query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).' WHERE '.$name_field.' = '.$this->Conn->qstr($new_name); $res = $this->Conn->GetOne($query); }*/ $original_checked = true; } while ($res !== false); $this->SetDBField($title_field, $new_name); } function raiseEvent($name, $id = null, $additional_params = Array()) { if( !isset($id) ) $id = $this->GetID(); $event = new kEvent( Array('name'=>$name,'prefix'=>$this->Prefix,'special'=>$this->Special) ); $event->setEventParam('id', $id); if ($additional_params) { foreach ($additional_params as $ap_name => $ap_value) { $event->setEventParam($ap_name, $ap_value); } } $this->Application->HandleEvent($event); return $event->status == erSUCCESS ? true : false; } /** * Set's new ID for item * * @param int $new_id * @access public */ function setID($new_id) { $this->ID = $new_id; $this->SetDBField($this->IDField, $new_id); } /** * Generate and set new temporary id * * @access private */ function setTempID() { $new_id = (int)$this->Conn->GetOne('SELECT MIN('.$this->IDField.') FROM '.$this->TableName); if($new_id > 0) $new_id = 0; --$new_id; $this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID()); if ($this->ShouldLogChanges()) { // Updating TempId in ChangesLog, if changes are disabled $ses_var_name = $this->Application->GetTopmostPrefix($this->Prefix).'_changes_'.$this->Application->GetTopmostWid($this->Prefix); $changes = $this->Application->RecallVar($ses_var_name); $changes = $changes ? unserialize($changes) : Array (); if ($changes) { foreach ($changes as $key => $rec) { if ($rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID()) { $changes[$key]['ItemId'] = $new_id; } } } $this->Application->StoreVar($ses_var_name, serialize($changes)); } $this->SetID($new_id); } /** * Set's modification flag for main prefix of current prefix to true * * @access private * @author Alexey */ function setModifiedFlag($mode = null) { $main_prefix = $this->Application->GetTopmostPrefix($this->Prefix); $this->Application->StoreVar($main_prefix.'_modified', '1', !$this->Application->isAdmin); if ($this->ShouldLogChanges()) { $this->LogChanges($main_prefix, $mode); if (!$this->IsTempTable()) { $handler =& $this->Application->recallObject($this->Prefix.'_EventHandler'); $ses_var_name = $main_prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); $handler->SaveLoggedChanges($ses_var_name); } } } /** * Determines, that changes made to this item should be written to change log * * @return bool */ function ShouldLogChanges() { $log_changes = $this->Application->getUnitOption($this->Prefix, 'LogChanges') || $this->Application->ConfigValue('UseChangeLog'); return $log_changes && !$this->Application->getUnitOption($this->Prefix, 'ForceDontLogChanges'); } function LogChanges($main_prefix, $mode) { if (!$mode) { return ; } $ses_var_name = $main_prefix.'_changes_'.$this->Application->GetTopmostWid($this->Prefix); $changes = $this->Application->RecallVar($ses_var_name); $changes = $changes ? unserialize($changes) : array(); $general = array( 'Prefix' => $this->Prefix, 'ItemId' => $this->GetID(), 'OccuredOn' => adodb_mktime(), 'MasterPrefix' => $main_prefix, 'MasterId' => $this->Prefix == $main_prefix ? $this->GetID() : $this->Application->GetVar($main_prefix.'_id'), // is that correct (Kostja)?? 'Action' => $mode, ); switch ($mode) { case clUPDATE: $changes[] = array_merge($general, Array( 'Changes' => serialize(array_merge($this->GetTitleField(), $this->GetChangedFields())), )); break; case clCREATE: $changes[] = array_merge($general, Array( 'Changes' => serialize($this->GetTitleField()), )); break; case clDELETE: $changes[] = array_merge($general, Array( 'Changes' => serialize(array_merge($this->GetTitleField(), $this->GetRealFields())), )); } $this->Application->StoreVar($ses_var_name, serialize($changes)); } function GetTitleField() { $title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField'); if ($title_field && $this->GetField($title_field)) { return Array($title_field => $this->GetField($title_field)); } } function GetRealFields() { if (function_exists('array_diff_key')) { $db_fields = array_diff_key($this->FieldValues, $this->VirtualFields, $this->CalculatedFields); } else { $db_fields = array(); foreach ($this->FieldValues as $key => $value) { if (array_key_exists($key, $this->VirtualFields) || array_key_exists($key, $this->CalculatedFields)) continue; $db_fields[$key] = $value; } } return $db_fields; } function GetChangedFields() { $changes = array(); $diff = array_diff_assoc($this->GetRealFields(), $this->OriginalFieldValues); foreach ($diff as $field => $new_value) { $changes[$field] = array('old' => $this->GetOriginalField($field, true), 'new' => $this->GetField($field)); } return $changes; } /** * Returns ID of currently processed record * * @return int * @access public */ function GetID() { return $this->ID; } /** * Generates ID for new items before inserting into database * * @return int * @access private */ function generateID() { return 0; } /** * Returns true if item was loaded successfully by Load method * * @return bool */ function isLoaded() { return $this->Loaded; } /** * Checks if field is required * * @param string $field * @return bool */ function isRequired($field) { return getArrayValue( $this->Fields[$field], 'required' ); } /** * Sets new required flag to field * * @param string $field * @param bool $is_required */ function setRequired($field, $is_required = true) { $this->Fields[$field]['required'] = $is_required; } function Clear($new_id = null) { $this->Loaded = false; $this->FieldValues = Array(); $this->OriginalFieldValues = Array (); $this->SetDefaultValues(); // will wear off kDBItem::setID effect, so set it later $this->FieldErrors = Array(); $this->setID($new_id); return $this->Loaded; } function Query($force = false) { if ($this->Application->isDebugMode()) { $this->Application->Debugger->appendTrace(); } trigger_error('<b>Query</b> method is called in class <b>'.get_class($this).'</b> for prefix <b>'.$this->getPrefixSpecial().'</b>', E_USER_ERROR); } function saveCustomFields() { if (!$this->customFields || $this->inCloning) { return true; } $cdata_key = rtrim($this->Prefix.'-cdata.'.$this->Special, '.'); $cdata =& $this->Application->recallObject($cdata_key, null, Array('skip_autoload' => true, 'populate_ml_fields' => true)); $resource_id = $this->GetDBField('ResourceId'); $cdata->Load($resource_id, 'ResourceId'); $cdata->SetDBField('ResourceId', $resource_id); $ml_formatter =& $this->Application->recallObject('kMultiLanguage'); /* @var $ml_formatter kMultiLanguage */ foreach ($this->customFields as $custom_id => $custom_name) { $force_primary = isset($cdata->Fields['cust_'.$custom_id]['force_primary']) && $cdata->Fields['cust_'.$custom_id]['force_primary']; $cdata->SetDBField($ml_formatter->LangFieldName('cust_'.$custom_id, $force_primary), $this->GetDBField('cust_'.$custom_name)); } - if ($cdata->isLoaded()) { - $ret = $cdata->Update(); - } - else { - $ret = $cdata->Create(); - if ($cdata->mode == 't') $cdata->setTempID(); - } - - return $ret; + return $cdata->isLoaded() ? $cdata->Update() : $cdata->Create(); } /** * Returns specified field value from all selected rows. * Don't affect current record index * * @param string $field * @return Array */ function GetCol($field) { return Array (0 => $this->GetDBField($field)); } } \ No newline at end of file Index: branches/5.0.x/core/units/helpers/controls/edit_picker_helper.php =================================================================== --- branches/5.0.x/core/units/helpers/controls/edit_picker_helper.php (revision 13602) +++ branches/5.0.x/core/units/helpers/controls/edit_picker_helper.php (revision 13603) @@ -1,181 +1,177 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class EditPickerHelper extends kHelper { function getTable($prefix, $temp=false) { $table_name = $this->Application->getUnitOption($prefix, 'TableName'); return $temp ? $this->Application->GetTempName($table_name, 'prefix:'.$prefix) : $table_name; } /** * Applies filter for multiple lists in inp_edit_picker control. * Called from SetCustomQuery of prefix, that contains all available items. * * @param kEvent $event * @param string $storage_field main item's field name, where values are located */ function applyFilter(&$event, $storage_field) { if ($event->Special != 'selected' && $event->Special != 'available') { return ; } if ($storage_field != $event->getEventParam('link_to_field')) { return ; } $object =& $event->getObject(); /* @var $object kDBList */ $main_object =& $this->Application->recallObject($event->getEventParam('link_to_prefix')); /* @var $main_object kDBItem */ $selected_items = $main_object->GetDBField($storage_field); if ($selected_items) { $filter_type = $event->Special == 'selected' ? 'IN' : 'NOT IN'; $selected_items = explode('|', substr($selected_items, 1, -1)); $filter_clause = '%1$s.' . $object->IDField.' '.$filter_type.' ('.implode(',', $selected_items).')'; } else { $filter_clause = ($event->Special == 'selected') ? 'FALSE' : 'TRUE'; } $constrain = $this->_getConstrain($main_object, $storage_field, 'filter'); if ($constrain) { $filter_clause .= ' AND (' . $constrain . ')'; } $object->addFilter('edit_picker_filter', $filter_clause); } /** * Loads selected values from sub_prefix to main item virtual field. * Called from OnAfterItemLoad of main prefix. * * @param kEvent $event * @param string $store_field main item's field name, to store values into * @param string $source_field prefix and it's field used to store info about selected items (format: prefix.field) */ function LoadValues(&$event, $store_field, $source_field) { $object =& $event->getObject(); /* @var $object kDBItem */ list ($sub_prefix, $sub_prefix_field) = explode('.', $source_field); $foreign_key = $this->Application->getUnitOption($sub_prefix, 'ForeignKey'); $sql = 'SELECT '.$sub_prefix_field.' FROM '.$this->getTable($sub_prefix, $object->IsTempTable()).' WHERE '.$foreign_key.' = '.$object->GetID(); $constrain = $this->_getConstrain($object, $store_field, 'load'); if ($constrain) { $sql .= ' AND (' . $sub_prefix_field . ' IN (' . $constrain . '))'; } $selected_items = array_unique($this->Conn->GetCol($sql)); $object->SetDBField($store_field, $selected_items ? '|'.implode('|', $selected_items).'|' : ''); } /** * Saves value to subitem's table * * @param kEvent $sub_event * @param string $store_field main item's field name, to get values from * @param string $sub_prefix_field check already existing records by this field */ function SaveValues(&$sub_event, $store_field, $sub_prefix_field) { $main_object =& $sub_event->MasterEvent->getObject(); /* @var $main_object kDBItem */ $affected_field = $main_object->GetDBField($store_field); $object =& $this->Application->recallObject($sub_event->getPrefixSpecial(), null, Array('skip_autoload' => true)); /* @var $object kDBItem */ $sub_table = $object->TableName; $foreign_key = $this->Application->getUnitOption($sub_event->Prefix, 'ForeignKey'); // 1. get previous values from db $sql = 'SELECT ' . $sub_prefix_field . ' FROM ' . $sub_table . ' WHERE '.$foreign_key.' = '.$main_object->GetID(); $constrain = $this->_getConstrain($main_object, $store_field, 'save'); if ($constrain) { $sql .= ' AND (' . $sub_prefix_field . ' IN (' . $constrain . '))'; } $old_values = $this->Conn->GetCol($sql); // 2. get new values from form $new_values = $affected_field ? explode('|', substr($affected_field, 1, -1)) : Array (); $records_to_add = array_diff($new_values, $old_values); $records_to_delete = array_diff($old_values, $new_values); if ($records_to_delete && $main_object->Loaded) { $where_clause = Array ( $foreign_key . ' = ' . $main_object->GetID(), $sub_prefix_field . ' IN (' . implode(',', $records_to_delete) . ')', ); $sql = 'SELECT ' . $object->IDField . ' FROM ' . $sub_table . ' WHERE (' . implode(') AND (', $where_clause) . ')'; $delete_ids = $this->Conn->GetCol($sql); foreach ($delete_ids as $delete_id) { $object->Delete($delete_id); } } if ($records_to_add) { $main_id = $main_object->GetID(); foreach ($records_to_add as $add_id) { $object->Clear(); $object->SetDBField($foreign_key, $main_id); $object->SetDBField($sub_prefix_field, $add_id); $object->Create(); - - if ($object->IsTempTable()) { - $object->setTempID(); - } } } } function _getConstrain(&$object, $store_field, $mode = 'filter') { $field_options = $object->GetFieldOptions($store_field); $constrain = array_key_exists('option_constrain', $field_options) ? $field_options['option_constrain'] : false; if ($mode == 'filter') { // filter on edit form return $constrain; } elseif ($constrain) { // load or save return sprintf($field_options['options_sql'], $field_options['option_key_field']); } return false; } } \ No newline at end of file Index: branches/5.0.x/core/units/helpers/brackets_helper.php =================================================================== --- branches/5.0.x/core/units/helpers/brackets_helper.php (revision 13602) +++ branches/5.0.x/core/units/helpers/brackets_helper.php (revision 13603) @@ -1,470 +1,468 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class kBracketsHelper extends kHelper { /** * Field name holding minimal amount * * @var string */ var $min_field = ''; /** * Field name holding maximal amount * * @var string */ var $max_field = ''; /** * Default values to be set to automtically created price brackets * * @var Array */ var $default_values = Array(); var $defaultStartValue = 1; /** * Decimal separator * * @var string */ var $_decimalSeparator = ''; /** * Thousands separator * * @var string */ var $_thousandsSeparator = ''; /** * Current language * * @var LanguagesItem */ var $_language = null; function kBracketsHelper() { parent::kHelper(); $this->_language =& $this->Application->recallObject('lang.current'); /* @var $lang kDBItem */ $this->_decimalSeparator = $this->_language->GetDBField('DecimalPoint'); $this->_thousandsSeparator = $this->_language->GetDBField('ThousandSep'); } function InitHelper($min_field, $max_field, $default_values, $default_start_value = null) { $this->min_field = $min_field; $this->max_field = $max_field; $this->default_values = $default_values; if (isset($default_start_value)) { $this->defaultStartValue = $default_start_value; } } /** * Converts number to operatable form * * @param string $value * @return float */ function _parseNumber($value) { $value = str_replace($this->_thousandsSeparator, '', $value); $value = str_replace($this->_decimalSeparator, '.', $value); return $value; } /** * Returns brackets from form with all numbers parsed * * @param kEvent $event * @return Array */ function getBrackets(&$event) { $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); return $this->parseBrackets($items_info); } function parseBrackets($brackets) { if (!$brackets) { return $brackets; } foreach ($brackets as $id => $field_values) { if (strlen($brackets[$id][$this->min_field])) { $brackets[$id][$this->min_field] = (float)$this->_parseNumber($brackets[$id][$this->min_field]); } if (strlen($brackets[$id][$this->max_field])) { $brackets[$id][$this->max_field] = (float)$this->_parseNumber($brackets[$id][$this->max_field]); } } return $brackets; } /** * Formats given brackets and sets them back to request * * @param kEvent $event * @param Array $brackets */ function setBrackets(&$event, $brackets) { $brackets = $this->formatBrackets($brackets); $this->Application->SetVar($event->getPrefixSpecial(true), $brackets); } function formatBrackets($brackets) { if (!$brackets) { return $brackets; } foreach ($brackets as $id => $field_values) { if (strlen($brackets[$id][$this->min_field])) { $brackets[$id][$this->min_field] = $this->_language->formatNumber($brackets[$id][$this->min_field]); } if (strlen($brackets[$id][$this->max_field])) { $brackets[$id][$this->max_field] = $this->_language->formatNumber($brackets[$id][$this->max_field]); } } return $brackets; } /** * Adds 5 more empty brackets to brackets * * @param kEvent $event */ function OnMoreBrackets(&$event) { $field_values = $this->getBrackets($event); $object =& $event->getObject(); foreach($field_values as $id => $record) { if($record[$this->max_field] == '∞') $field_values[$id][$this->max_field] = -1; } $new_id = (int)$this->Conn->GetOne('SELECT MIN('.$object->IDField.') FROM '.$object->TableName); if($new_id > 0) $new_id = 0; do { $new_id--; }while( $this->arraySearch($field_values, $object->IDField, $new_id) ); $last_max_qty = $this->Conn->GetOne('SELECT MAX('.$this->max_field.') FROM '.$object->TableName); $min_qty = $this->Conn->GetOne('SELECT MIN('.$this->max_field.') FROM '.$object->TableName); if($min_qty == -1) $last_max_qty = -1; if(!$last_max_qty) $last_max_qty = $this->defaultStartValue; for($i = $new_id; $i > $new_id - 5; $i--) { $field_values[$i][$object->IDField] = $i; $field_values[$i][$this->min_field] = ($i == $new_id-4 && $last_max_qty != -1) ? $last_max_qty : ''; $field_values[$i][$this->max_field] = ($i == $new_id-4 && $last_max_qty != -1) ? -1 : ''; $field_values[$i] = array_merge_recursive2($field_values[$i], $this->default_values); } $event->CallSubEvent('OnPreSaveBrackets'); $this->setBrackets($event, $field_values); } /** * Adds infinity bracket * * @param kEvent $event */ function OnInfinity(&$event) { $object =& $event->getObject(); $infinite_exists = $this->Conn->GetOne('SELECT COUNT(*) FROM '.$object->TableName.' WHERE '.$this->max_field.' = -1'); $field_values = $this->getBrackets($event); /*if(is_array($field_values)) { foreach($field_values as $values) { $infinite_exists = $infinite_exists || ($values[$this->max_field] == -1); } }*/ if ($infinite_exists == 0) { reset($field_values); $last_bracket = end($field_values); $new_id = (int)$this->Conn->GetOne('SELECT MIN('.$object->IDField.') FROM '.$object->TableName); $brackets_exist = (int)$this->Conn->GetOne('SELECT COUNT(*) FROM '.$object->TableName); if($new_id > 0) $new_id = 0; do { $new_id--; }while( $this->arraySearch($field_values, $object->IDField, $new_id) ); $infinite_bracket[$object->IDField] = $new_id; $infinite_bracket[$this->min_field] = ($brackets_exist > 0) ? $last_bracket[$this->max_field] : $this->defaultStartValue; $infinite_bracket[$this->max_field] = '-1'; $infinite_bracket = array_merge_recursive2($infinite_bracket, $this->default_values); $field_values[$new_id] = $infinite_bracket; reset($field_values); $this->setBrackets($event, $field_values); } } /** * Saves brackets to database * * @param kEvent $event */ function OnPreSaveBrackets(&$event) { $items_info = $this->getBrackets($event); if ($items_info) { $object =& $event->getObject(); $linked_info = $object->getLinkedInfo(); $stored_ids = $this->Conn->GetCol('SELECT '.$object->IDField.' FROM '.$object->TableName.' WHERE '.$linked_info['ForeignKey'].' = '.$linked_info['ParentId']); uasort($items_info, Array(&$this, 'compareBrackets') ); foreach ($items_info as $item_id => $values) { if (in_array($item_id, $stored_ids)) { //if it's already exist $object->SetDefaultValues(); $object->Load($item_id); $object->SetFieldsFromHash($values); if (!$object->Validate()) { unset($stored_ids[array_search($item_id, $stored_ids)]); $event->redirect = false; continue; } if( $object->Update($item_id) ) { $event->status = erSUCCESS; } else { $event->status = erFAIL; $event->redirect = false; break; } unset( $stored_ids[ array_search($item_id, $stored_ids) ] ); } else { $object->SetDefaultValues(); $object->SetFieldsFromHash($values); $object->SetDBField($linked_info['ForeignKey'], $linked_info['ParentId']); - if( $object->Create() ) - { - $object->setTempID(); + if ($object->Create()) { $event->status = erSUCCESS; } } } // delete foreach ($stored_ids as $stored_id) { $this->Conn->Query('DELETE FROM '.$object->TableName.' WHERE '.$object->IDField.' = '.$stored_id); } } } function arrangeBrackets(&$event) { $object =& $event->getObject(); $temp = $this->getBrackets($event); foreach($temp as $id => $record) { if( $record[$this->max_field] == '∞' ) { $temp[$id][$this->max_field] = -1; } } $temp_orig = $temp; reset($temp); if( is_array($temp) ) { // array to store max values (2nd column) $end_values = Array(); // get minimal value of Min $first_elem = current($temp); $start = $first_elem[$this->min_field]; if (!strlen($start)) { $start = $this->defaultStartValue; } foreach($temp as $id => $record) { if( // MAX is less than start ($record[$this->max_field] <= $start && $record[$this->max_field] != -1) || // Max is empty !strlen($record[$this->max_field]) || // Max already defined in $end_values (array_search($record[$this->max_field], $end_values) !== false) ) { // then delete from brackets list unset($temp[$id]); } else { // this is when ok - add to end_values list $end_values[] = $record[$this->max_field]; } } // sort brackets by 2nd column (Max values) uasort($temp, Array(&$this, 'compareBrackets') ); reset($temp); $first_item = each($temp); $first_item_key = $first_item['key']; $linked_info = $object->getLinkedInfo(); $sql = 'SELECT %s FROM %s WHERE %s = %s'; $ids = $this->Conn->GetCol( sprintf($sql, $object->IDField, $object->TableName, $linked_info['ForeignKey'], $linked_info['ParentId']) ); if( is_array($ids) ) { usort($ids, Array(&$this, 'sortBracketIDs') ); } // $min_id = min( min($ids) - 1, -1 ); foreach($temp as $key => $record) { $temp[$key][$this->min_field] = $start; $start = $temp[$key][$this->max_field]; } } $this->setBrackets($event, $temp); return $temp; } function compareBrackets($bracket1, $bracket2) // ap_bracket_comp { $bracket1_min = $bracket1[$this->min_field]; $bracket1_max = $bracket1[$this->max_field]; $bracket2_min = $bracket2[$this->min_field]; $bracket2_max = $bracket2[$this->max_field]; // limits if( ($bracket1_min != '') && ($bracket1_max == '') && ($bracket2_min != '') && ($bracket2_max != '') ) return 1; if( ($bracket1_min != '') && ($bracket1_max == '') && ($bracket2_min == '') && ($bracket2_max == '') ) return -1; if( ($bracket1_max == '') && ($bracket2_max != '') ) return 1; if( ($bracket1_max != '') && ($bracket2_max == '') ) return -1; if( ( ($bracket1_max > $bracket2_max) && ($bracket2_max != -1) ) || ( ($bracket1_max == -1) && ($bracket2_max != -1) ) ) { return 1; } elseif( ($bracket1_max < $bracket2_max) || ( ($bracket2_max == -1) && ($bracket1_max != -1) ) ) { return -1; } else { return 0; } } function sortBracketIDs($first_id, $second_id) // pr_bracket_id_sort { $first_abs = abs($first_id); $second_abs = abs($second_id); $first_sign = ($first_id == 0) ? 0 : $first_id / $first_abs; $second_sign = ($second_id == 0) ? 0 : $second_id / $second_abs; if($first_sign != $second_sign) { if($first_id > $second_id) { $bigger =& $first_abs; $smaller =& $second_abs; } else { $bigger =& $second_abs; $smaller =& $first_abs; } $smaller = $bigger + $smaller; } return ($first_abs > $second_abs) ? 1 : ($first_abs < $second_abs ? -1 : 0); } /** * Searches through submitted grid data to find record with specific value in specific field * * @param Array $records // grid data from REQUEST * @param string $field * @param string $value * @return bool */ function arraySearch($records, $field, $value) // check_array { foreach ($records as $record) { if ($record[$field] == $value) { return true; } } return false; } /** * Replate infinity mark with -1 before saving to db * * @param kEvent $event */ function replaceInfinity(&$event) { $object =& $event->getObject(); if($object->GetDBField($this->max_field) == '∞') $object->SetDBField($this->max_field, -1); } } \ No newline at end of file Index: branches/5.0.x/core/units/selectors/selectors_event_handler.php =================================================================== --- branches/5.0.x/core/units/selectors/selectors_event_handler.php (revision 13602) +++ branches/5.0.x/core/units/selectors/selectors_event_handler.php (revision 13603) @@ -1,380 +1,378 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); safeDefine('STYLE_BASE', 1); safeDefine('STYLE_BLOCK', 2); class SelectorsEventHandler extends kDBEventHandler { /** * Allows to override standart permission mapping * */ function mapPermissions() { parent::mapPermissions(); $permissions = Array( 'OnResetToBase' => Array('subitem' => 'add|edit'), 'OnMassResetToBase' => Array('subitem' => 'add|edit'), 'OnOpenStyleEditor' => Array('subitem' => 'add|edit'), 'OnSaveStyle' => Array('subitem' => 'add|edit'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Occures before an item has been cloned * Id of newly created item is passed as event' 'id' param * * @param kEvent $event */ function OnBeforeClone(&$event) { $event->Init($event->Prefix, '-item'); $object =& $event->getObject(); $title_field = 'SelectorName'; $new_name = $object->GetDBField($title_field); $original_checked = false; $foreign_key = $event->getEventParam('foreign_key'); // in case if whole stylesheet is cloned if($foreign_key === false) $foreign_key = $object->GetDBField('StylesheetId'); // in case if selector is copied ifself do { if ( preg_match('/(.*)-([\d]+)/', $new_name, $regs) ) { $new_name = $regs[1].'-'.($regs[2]+1); } elseif ($original_checked) { $new_name = $new_name.'-1'; } // if we are cloning in temp table this will look for names in temp table, // since object' TableName contains correct TableName (for temp also!) // if we are cloning live - look in live $query = ' SELECT '.$title_field.' FROM '.$object->TableName.' WHERE '.$title_field.' = '.$this->Conn->qstr($new_name).' AND StylesheetId = '.$foreign_key; $res = $this->Conn->GetOne($query); /*// if not found in live table, check in temp table if applicable if ($res === false && $object->Special == 'temp') { $query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).' WHERE '.$name_field.' = '.$this->Conn->qstr($new_name); $res = $this->Conn->GetOne($query); }*/ $original_checked = true; } while ($res !== false); $object->SetDBField($title_field, $new_name); } /** * Show base styles or block styles * * @param kEvent $event */ function SetCustomQuery(&$event) { $object =& $event->getObject(); switch ($event->Special) { case 'base': $object->addFilter('type_filter', '%1$s.Type = 1'); break; case 'block': $object->addFilter('type_filter', '%1$s.Type = 2'); break; } } /** * Enter description here... * * @param kEvent $event */ function OnBeforeItemUpdate(&$event) { $this->SerializeSelectorData($event); } /** * Enter description here... * * @param kEvent $event */ function OnBeforeItemCreate(&$event) { $this->SerializeSelectorData($event); } /** * Enter description here... * * @param kEvent $event */ function OnAfterItemUpdate(&$event) { $this->UnserializeSelectorData($event); } /** * Enter description here... * * @param kEvent $event */ function OnAfterItemCreate(&$event) { $this->UnserializeSelectorData($event); } /** * Get's special of main item for linking with subitem * * @param kEvent $event * @return string */ function getMainSpecial(&$event) { return ''; } /** * Save css-style name & description before opening css editor * * @param kEvent $event */ function OnOpenStyleEditor(&$event) { $this->SaveChanges($event); $event->redirect = false; } /** * Saves Changes to Item * * @param kEvent $event */ function SaveChanges(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) { list($id,$field_values) = each($items_info); - if($id == 0) - { + if ($id == 0) { $parent_id = getArrayValue($field_values,'ParentId'); if($parent_id) $object->Load($parent_id); $object->SetFieldsFromHash($field_values); $object->Create(); - if( $object->IsTempTable() ) $object->setTempID(); + $this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetID() ); } - else - { + else { $object->Load($id); $object->SetFieldsFromHash($field_values); $object->Update(); } } } /** * Save style changes from style editor * * @param kEvent $event */ function OnSaveStyle(&$event) { $this->SaveChanges($event); $object =& $event->getObject(); $this->Application->SetVar($event->getPrefixSpecial().'_id', $object->GetId() ); $this->finalizePopup($event); } /** * Extract styles * * @param kEvent $event */ function OnAfterItemLoad(&$event) { $object =& $event->getObject(); $selector_data = $object->GetDBField('SelectorData'); if($selector_data) { $selector_data = unserialize($selector_data); $object->SetDBField('SelectorData', $selector_data); } else { $selector_data = Array(); } $this->AddParentProperties($event, $selector_data); } /** * Serialize item before saving to db * * @param kEvent $event */ function SerializeSelectorData(&$event) { $object =& $event->getObject(); $selector_data = $object->GetDBField('SelectorData'); if(!$selector_data) $selector_data = Array(); $selector_data = $this->RemoveParentProperties($event, $selector_data); if( !IsSerialized($selector_data) ) $selector_data = serialize($selector_data); $object->SetDBField('SelectorData', $selector_data); } /** * Unserialize data back when update was made * * @param kEvent $event */ function UnserializeSelectorData(&$event) { $object =& $event->getObject(); $selector_data = $object->GetDBField('SelectorData'); if(!$selector_data) $selector_data = Array(); if( IsSerialized($selector_data) ) $selector_data = unserialize($selector_data); $selector_data = $this->AddParentProperties($event, $selector_data); $object->SetDBField('SelectorData', $selector_data); } /** * Populate options based on temporary table :) * * @param kEvent $event */ function OnPrepareBaseStyles(&$event) { $object =& $event->getObject(); $parent_info = $object->getLinkedInfo(); $title_field = $this->Application->getUnitOption($event->Prefix,'TitleField'); $sql = 'SELECT '.$title_field.', '.$object->IDField.' FROM '.$object->TableName.' WHERE Type = 1 AND StylesheetId = '.$parent_info['ParentId'].' ORDER BY '.$title_field; $object->Fields['ParentId']['options'] = $this->Conn->GetCol($sql,$object->IDField); } /** * Remove properties of parent style that match by value from style * * @param kEvent $event */ function RemoveParentProperties(&$event, $selector_data) { $object =& $event->getObject(); $parent_id = $object->GetDBField('ParentId'); if($parent_id) { $sql = 'SELECT SelectorData FROM '.$object->TableName.' WHERE '.$object->IDField.' = '.$parent_id; $base_selector_data = $this->Conn->GetOne($sql); if( IsSerialized($base_selector_data) ) $base_selector_data = unserialize($base_selector_data); foreach($selector_data as $prop_name => $prop_value) { if( !$prop_value || getArrayValue($base_selector_data,$prop_name) == $prop_value ) { unset($selector_data[$prop_name]); } } $object->SetDBField('SelectorData', $selector_data); return $selector_data; } else { foreach($selector_data as $prop_name => $prop_value) { if(!$prop_value) unset($selector_data[$prop_name]); } $object->SetDBField('SelectorData', $selector_data); return $selector_data; } } /** * Add back properties from parent style, that match this style property values * * @param kEvent $event */ function AddParentProperties(&$event, $selector_data) { $object =& $event->getObject(); $parent_id = $object->GetDBField('ParentId'); if($parent_id) { $sql = 'SELECT SelectorData FROM '.$object->TableName.' WHERE '.$object->IDField.' = '.$parent_id; $base_selector_data = $this->Conn->GetOne($sql); if( IsSerialized($base_selector_data) ) $base_selector_data = unserialize($base_selector_data); $selector_data = array_merge_recursive2($base_selector_data,$selector_data); $object->SetDBField('SelectorData', $selector_data); return $selector_data; } return $selector_data; } /** * Reset Style definition to base style -> no customizations * * @param kEvent $event */ function OnResetToBase(&$event) { $object =& $event->getObject(); $object->SetFieldsFromHash( $this->getSubmittedFields($event) ); $object->ResetStyle(); $event->redirect_params['pass'] = 'all,'.$event->getPrefixSpecial(); } /** * Resets selected styles properties to values of their base classes * * @param kEvent $event */ function OnMassResetToBase(&$event) { $object =& $event->getObject( Array('skip_autoload' => true) ); $items_info = $this->Application->GetVar( $event->getPrefixSpecial(true) ); if($items_info) { foreach($items_info as $id => $field_values) { $object->Load($id); $object->ResetStyle(); } } } } \ No newline at end of file