Page MenuHomeIn-Portal Phabricator

in-portal
No OneTemporary

File Metadata

Created
Tue, Feb 25, 5:42 AM

in-portal

Index: branches/5.3.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.3.x/core/kernel/utility/temp_handler.php (revision 15730)
+++ branches/5.3.x/core/kernel/utility/temp_handler.php (revision 15731)
@@ -1,1063 +1,1586 @@
<?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!');
+/**
+ * Create a public interface to temp table structure of one unit and it's sub-items
+ *
+ * Pattern: Facade
+ */
class kTempTablesHandler extends kBase {
- var $Tables = Array();
-
/**
- * Master table name for temp handler
+ * Tables
*
- * @var string
- * @access private
+ * @var kTempHandlerTopTable
*/
- var $MasterTable = '';
+ protected $_tables;
/**
- * IDs from master table
+ * Event, that was used to create this object
*
- * @var Array
- * @access private
+ * @var kEvent
+ * @access protected
*/
- var $MasterIDs = Array();
-
- var $AlreadyProcessed = Array();
-
- var $DroppedTables = Array();
-
- var $FinalRefs = Array();
-
- var $TableIdCounter = 0;
-
- var $CopiedTables = Array();
+ protected $parentEvent = null;
/**
- * Foreign key cache
+ * Sets new parent event to the object
*
- * @var Array
+ * @param kEvent $event
+ * @return void
+ * @access public
*/
- var $FKeysCache = Array ();
+ public function setParentEvent($event)
+ {
+ $this->parentEvent = $event;
- /**
- * IDs of newly cloned items (key - prefix.special, value - array of ids)
- *
- * @var Array
- */
- var $savedIDs = Array();
+ if ( is_object($this->_tables) ) {
+ $this->_tables->setParentEvent($event);
+ }
+ }
/**
- * Window ID of current window
+ * Scans table structure of given unit
*
- * @var mixed
+ * @param string $prefix
+ * @param Array $ids
+ * @return void
+ * @access public
*/
- var $WindowID = '';
+ public function BuildTables($prefix, $ids)
+ {
+ $this->_tables = new kTempHandlerTopTable($prefix, $ids);
+ $this->_tables->setParentEvent($this->parentEvent);
+ }
/**
- * Event, that was used to create this object
+ * Create temp table for editing db record from live table. If none ids are given, then just empty tables are created.
*
- * @var kEvent
- * @access protected
+ * @return void
+ * @access public
*/
- protected $parentEvent = null;
+ public function PrepareEdit()
+ {
+ $this->_tables->doCopyLiveToTemp();
+ $this->_tables->checkSimultaneousEdit();
+ }
/**
- * Sets new parent event to the object
+ * Deletes temp tables without copying their data back to live tables
*
- * @param kEvent $event
* @return void
* @access public
*/
- public function setParentEvent($event)
+ public function CancelEdit()
{
- $this->parentEvent = $event;
+ $this->_tables->deleteAll();
}
- function SetTables($tables)
+ /**
+ * Saves changes made in temp tables to live tables
+ *
+ * @param Array $master_ids
+ * @return bool
+ * @access public
+ */
+ public function SaveEdit($master_ids = Array())
{
- // set table name as key for tables array
- $this->Tables = $tables;
- $this->MasterTable = $tables['TableName'];
- }
+ // SessionKey field is required for deleting records from expired sessions
+ $sleep_count = 0;
+ $conn = $this->_getSeparateConnection();
- 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;
+ do {
+ // acquire lock
+ $conn->ChangeQuery('LOCK TABLES ' . TABLE_PREFIX . 'Semaphores WRITE');
+
+ $sql = 'SELECT SessionKey
+ FROM ' . TABLE_PREFIX . 'Semaphores
+ WHERE (MainPrefix = ' . $conn->qstr($this->_tables->getPrefix()) . ')';
+ $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_TemporaryTableCopyingFailed');
+ $this->Application->SetVar('_temp_table_message', $error_message);
+
+ return false;
}
- else {
- $this->savedIDs[$prefix.($special ? '.' : '').$special][] = $id;
- }
+
+ // 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->getPrefix(),
+ );
+
+ $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->_tables->doCopyTempToLive($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;
}
/**
- * Get temp table name
+ * Deletes unit data for given items along with related sub-items
*
- * @param string $table
- * @return string
+ * @param string $prefix
+ * @param string $special
+ * @param Array $ids
+ * @throws InvalidArgumentException
*/
- function GetTempName($table)
+ function DeleteItems($prefix, $special, $ids)
{
- return $this->Application->GetTempName($table, $this->WindowID);
- }
+ if ( strpos($prefix, '.') !== false ) {
+ throw new InvalidArgumentException("Pass prefix and special as separate arguments");
+ }
- function GetTempTablePrefix()
- {
- return $this->Application->GetTempTablePrefix($this->WindowID);
+ if ( !is_array($ids) ) {
+ throw new InvalidArgumentException('Incorrect ids format');
+ }
+
+ $this->_tables->doDeleteItems(rtrim($prefix . '.' . $special, '.'), $ids);
}
/**
- * Return live table name based on temp table name
+ * Clones given ids
*
- * @param string $temp_table
- * @return string
+ * @param string $prefix
+ * @param string $special
+ * @param Array $ids
+ * @param Array $master
+ * @param int $foreign_key
+ * @param string $parent_prefix
+ * @param bool $skip_filenames
+ * @return Array
*/
- function GetLiveName($temp_table)
+ function CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false)
{
- return $this->Application->GetLiveName($temp_table);
+ return $this->_tables->doCloneItems($prefix . '.' . $special, $ids, $foreign_key, $skip_filenames);
}
- function IsTempTable($table)
+ /**
+ * Create separate connection for locking purposes
+ *
+ * @return kDBConnection
+ * @access protected
+ */
+ protected function _getSeparateConnection()
{
- return $this->Application->IsTempTable($table);
+ static $connection = null;
+
+ if (!isset($connection)) {
+ $connection = $this->Application->makeClass( 'kDBConnection', Array (SQL_TYPE, Array ($this->Application, 'handleSQLError')) );
+ /* @var $connection kDBConnection */
+
+ $connection->debugMode = $this->Application->isDebugMode();
+ $connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB, true);
+ }
+
+ return $connection;
}
+}
+
+/**
+ * Base class, that represents one table
+ *
+ * Pattern: Composite
+ */
+abstract class kTempHandlerTable extends kBase {
+
+ /**
+ * Temp table was created from live table OR it was copied back to live table
+ */
+ const STATE_COPIED = 1;
+
+ /**
+ * Temp table was deleted
+ */
+ const STATE_DELETED = 2;
/**
- * Return temporary table name for master table
+ * Reference to parent table
*
- * @return string
- * @access public
+ * @var kTempHandlerTable
+ * @access protected
*/
- function GetMasterTempName()
- {
- return $this->GetTempName($this->MasterTable);
- }
+ protected $_parent;
- function CreateTempTable($table)
- {
- $sql = 'CREATE TABLE ' . $this->GetTempName($table) . '
- SELECT *
- FROM ' . $table . '
- WHERE 0';
+ /**
+ * Field in this db table, that holds ID from it's parent table
+ *
+ * @var string
+ * @access protected
+ */
+ protected $_foreignKey = '';
- $this->Conn->Query($sql);
- }
+ /**
+ * This table is connected to multiple parent tables
+ *
+ * @var bool
+ * @access protected
+ */
+ protected $_multipleParents = false;
- function BuildTables($prefix, $ids)
- {
- $this->TableIdCounter = 0;
- $this->WindowID = $this->Application->GetVar('m_wid');
+ /**
+ * Foreign key cache
+ *
+ * @var Array
+ * @access protected
+ */
+ protected $_foreignKeyCache = Array ();
- $config = $this->Application->getUnitConfig($prefix);
+ /**
+ * Field in parent db table from where foreign key field value originates
+ *
+ * @var string
+ * @access protected
+ */
+ protected $_parentTableKey = '';
- $tables = Array(
- 'TableName' => $config->getTableName(),
- 'IdField' => $config->getIDField(),
- 'IDs' => $ids,
- 'Prefix' => $prefix,
- 'TableId' => $this->TableIdCounter++,
- );
+ /**
+ * Additional WHERE filter, that determines what records needs to be processed
+ *
+ * @var string
+ * @access protected
+ */
+ protected $_constrain = '';
- /*$config = $this->Application->getUnitConfig($prefix);
- $parent_prefix = $config->getParentPrefix();
+ /**
+ * Automatically clone records from this table when parent table record is cloned
+ *
+ * @var bool
+ * @access protected
+ */
+ protected $_autoClone = true;
- if ( $parent_prefix ) {
- $tables['ForeignKey'] = $config->getForeignKey();
- $tables['ParentPrefix'] = $parent_prefix;
- $tables['ParentTableKey'] = $config->getParentTableKey();
- }*/
+ /**
+ * Automatically delete records from this table when parent table record is deleted
+ *
+ * @var bool
+ * @access protected
+ */
+ protected $_autoDelete = true;
- $this->FinalRefs[ $tables['TableName'] ] = $tables['TableId']; // don't forget to add main table to FinalRefs too
+ /**
+ * List of sub-tables
+ *
+ * @var Array
+ * @access protected
+ */
+ protected $_subTables = Array ();
- $sub_items = $this->Application->getUnitConfig($prefix)->getSubItems(Array ());
+ /**
+ * Window ID of current window
+ *
+ * @var int
+ * @access protected
+ */
+ protected $_windowID = '';
- if ( is_array($sub_items) ) {
- foreach ($sub_items as $prefix) {
- $this->AddTables($prefix, $tables);
- }
+ /**
+ * Unit prefix
+ *
+ * @var string
+ * @access protected
+ */
+ protected $_prefix = '';
+
+ /**
+ * IDs, that needs to be processed
+ *
+ * @var Array
+ * @access protected
+ */
+ protected $_ids = Array ();
+
+ /**
+ * Table name-based 2-level array of cloned ids
+ *
+ * @static
+ * @var array
+ * @access protected
+ */
+ static protected $_clonedIds = Array ();
+
+ /**
+ * IDs of newly cloned items (key - special, value - array of ids)
+ *
+ * @var Array
+ * @access protected
+ */
+ protected $_savedIds = Array ();
+
+ /**
+ * ID field of associated db table
+ *
+ * @var string
+ * @access protected
+ */
+ protected $_idField = '';
+
+ /**
+ * Name of associated db table
+ *
+ * @var string
+ * @access protected
+ */
+ protected $_tableName = '';
+
+ /**
+ * State of the table
+ *
+ * @var int
+ * @access protected
+ */
+ protected $_state = 0;
+
+ /**
+ * Tells that this is last usage of this table
+ *
+ * @var bool
+ * @access protected
+ */
+ protected $_lastUsage = false;
+
+ /**
+ * Event, that was used to create this object
+ *
+ * @var kEvent
+ * @access protected
+ */
+ protected $_parentEvent = null;
+
+ /**
+ * Creates table object
+ *
+ * @param string $prefix
+ * @param Array $ids
+ */
+ public function __construct($prefix, $ids = Array ())
+ {
+ parent::__construct();
+
+ $this->_windowID = $this->Application->GetVar('m_wid');
+
+ $this->_prefix = $prefix;
+ $this->_ids = $ids;
+
+ if ( !$this->unitRegistered() ) {
+ return;
}
- $this->SetTables($tables);
+ $this->_collectTableInfo();
}
/**
- * Searches through TempHandler tables info for required prefix
+ * Creates temp tables (recursively) and optionally fills them with data from live table
*
- * @param string $prefix
- * @param Array $master
- * @return mixed
+ * @param Array $foreign_keys
+ * @return void
+ * @access public
*/
- function SearchTable($prefix, $master = null)
+ public function doCopyLiveToTemp($foreign_keys = Array ())
{
- if (is_null($master)) {
- $master = $this->Tables;
+ $parsed_prefix = $this->Application->processPrefix($this->_prefix);
+ $foreign_key_field = $this->_foreignKey ? $this->_foreignKey : $this->_idField;
+
+ if ( !is_numeric($parsed_prefix['special']) ) {
+ // TODO: find out what numeric specials are used for
+ if ( $this->_delete() ) {
+ $this->_create();
+ }
}
- if ($master['Prefix'] == $prefix) {
- return $master;
+ $foreign_keys = $this->_parseLiveIds($foreign_keys);
+
+ if ( $foreign_keys != '' && !$this->_inState(self::STATE_COPIED) ) {
+ // 1. copy data from live table into temp table
+ $sql = 'INSERT INTO ' . $this->_getTempTableName() . '
+ SELECT *
+ FROM ' . $this->_tableName . '
+ WHERE ' . $foreign_key_field . ' IN (' . $foreign_keys . ')';
+ $this->Conn->Query($this->_addConstrain($sql));
+
+ $this->_setAsCopied();
+
+ // 2. get ids, that were actually copied into temp table
+ $sql = 'SELECT ' . $this->_idField . '
+ FROM ' . $this->_tableName . '
+ WHERE ' . $foreign_key_field . ' IN (' . $foreign_keys . ')';
+ $copied_ids = $this->Conn->GetCol($this->_addConstrain($sql));
+
+ $this->_raiseEvent('OnAfterCopyToTemp', '', $copied_ids);
}
- if (isset($master['SubTables'])) {
- foreach ($master['SubTables'] as $sub_table) {
- $found = $this->SearchTable($prefix, $sub_table);
- if ($found !== false) {
- return $found;
- }
+ /* @var $sub_table kTempHandlerSubTable */
+ foreach ($this->_subTables as $sub_table) {
+ if ( !$sub_table->_parentTableKey ) {
+ continue;
+ }
+
+ if ( $foreign_keys != '' && $sub_table->_parentTableKey != $foreign_key_field ) {
+ // if sub-table isn't connected this this table by id field, then get foreign keys
+ $sql = 'SELECT ' . $sub_table->_parentTableKey . '
+ FROM ' . $this->_tableName . '
+ WHERE ' . $foreign_key_field . ' IN (' . $foreign_keys . ')';
+ $sub_foreign_keys = implode(',', $this->Conn->GetCol($sql));
+ }
+ else {
+ $sub_foreign_keys = $foreign_keys;
}
- }
- return false;
+ $sub_table->doCopyLiveToTemp($sub_foreign_keys);
+ }
}
- function AddTables($prefix, &$tables)
+ /**
+ * Ensures, that ids are always a comma-separated string, that is ready to be used in SQLs
+ *
+ * @param Array|string $ids
+ * @return string
+ * @access protected
+ */
+ protected function _parseLiveIds($ids)
{
- if ( !$this->Application->prefixRegistred($prefix) ) {
- // allows to skip subitem processing if subitem module not enabled/installed
- return ;
+ if ( !$ids ) {
+ $ids = $this->_ids;
}
- $config = $this->Application->getUnitConfig($prefix);
+ if ( is_array($ids) ) {
+ $ids = implode(',', $ids);
+ }
- $tmp = Array(
- 'TableName' => $config->getTableName(),
- 'IdField' => $config->getIDField(),
- 'ForeignKey' => $config->getForeignKey(),
- 'ParentPrefix' => $config->getParentPrefix(),
- 'ParentTableKey' => $config->getParentTableKey(),
- 'Prefix' => $prefix,
- 'AutoClone' => $config->getAutoClone(),
- 'AutoDelete' => $config->getAutoDelete(),
- 'TableId' => $this->TableIdCounter++,
- );
+ return $ids;
+ }
+
+ /**
+ * Copies data from temp to live table and returns IDs of copied records
+ *
+ * @param Array $current_ids
+ * @return Array
+ * @access public
+ */
+ public function doCopyTempToLive($current_ids = Array())
+ {
+ $current_ids = $this->_parseTempIds($current_ids);
- $this->FinalRefs[ $tmp['TableName'] ] = $tmp['TableId'];
+ if ( $current_ids ) {
+ $this->_deleteFromLive($current_ids);
- $constrain = $config->getConstrain();
+ if ( $this->_subTables ) {
+ if ( $this->_inState(self::STATE_COPIED) || !$this->_lastUsage ) {
+ return Array ();
+ }
- if ( $constrain ) {
- $tmp['Constrain'] = $constrain;
- $this->FinalRefs[ $tmp['TableName'] . $tmp['Constrain'] ] = $tmp['TableId'];
- }
+ $this->_copyTempToLiveWithSubTables($current_ids);
+ }
+ elseif ( !$this->_inState(self::STATE_COPIED) && $this->_lastUsage ) {
+ // If current master doesn't have sub-tables - we could use mass operations
+ // We don't need to delete items from live here, as it get deleted in the beginning of the
+ // method for MasterTable or in parent table processing for sub-tables
- $sub_items = $config->getSubItems(Array ());
+ $this->_copyTempToLiveWithoutSubTables($current_ids);
- if ( is_array($sub_items) ) {
- foreach ($sub_items as $prefix) {
- $this->AddTables($prefix, $tmp);
+ // no need to clear temp table - it will be dropped by next statement
}
}
- if ( !is_array(getArrayValue($tables, 'SubTables')) ) {
- $tables['SubTables'] = Array ();
+ if ( !$this->_lastUsage ) {
+ return Array ();
}
- $tables['SubTables'][] = $tmp;
+ /*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->_delete();
+ $this->Application->resetCounters($this->_tableName);
+
+ return isset($this->_savedIds['']) ? $this->_savedIds[''] : Array ();
}
- function CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false)
+ /**
+ * Deletes unit db records along with related sub-items by id field
+ *
+ * @param string $prefix_special
+ * @param Array $ids
+ * @return void
+ * @access public
+ */
+ public function doDeleteItems($prefix_special, $ids)
{
- 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';
+ if ( !$ids ) {
+ return;
}
- $object = $this->Application->recallObject($prefix.'.'.$special, $prefix, Array('skip_autoload' => true, 'parent_event' => $this->parentEvent));
- /* @var $object kCatDBItem */
+ $object = $this->_getItem($prefix_special);
+ $parsed_prefix = $this->Application->processPrefix($prefix_special);
+ foreach ($ids as $id) {
+ $object->Load($id);
+ $original_values = $object->GetFieldValues();
+
+ if ( !$object->Delete($id) ) {
+ continue;
+ }
+
+ /* @var $sub_table kTempHandlerSubTable */
+ foreach ($this->_subTables as $sub_table) {
+ $sub_table->subDeleteItems($object, $parsed_prefix['special'], $original_values);
+ }
+ }
+ }
+
+ /**
+ * Clones item by id and it's sub-items by foreign key
+ *
+ * @param string $prefix_special
+ * @param Array $ids
+ * @param string $foreign_key
+ * @param bool $skip_filenames
+ * @return Array
+ * @access public
+ */
+ public function doCloneItems($prefix_special, $ids, $foreign_key = null, $skip_filenames = false)
+ {
+ $object = $this->_getItem($prefix_special);
$object->PopulateMultiLangFields();
foreach ($ids as $id) {
$mode = 'create';
- $cloned_ids = getArrayValue($this->AlreadyProcessed, $master['TableName']);
+ $cloned_ids = getArrayValue(self::$_clonedIds, $this->_tableName);
if ( $cloned_ids ) {
// if we have already cloned the id, replace it with cloned id and set mode to update
// update mode is needed to update second ForeignKey for items cloned by first ForeignKey
if ( getArrayValue($cloned_ids, $id) ) {
$id = $cloned_ids[$id];
$mode = 'update';
}
}
$object->Load($id);
$original_values = $object->GetFieldValues();
- if (!$skip_filenames) {
+ if ( !$skip_filenames ) {
+ $master = Array ('ForeignKey' => $this->_foreignKey, 'TableName' => $this->_tableName);
$object->NameCopy($master, $foreign_key);
}
- elseif ($master['TableName'] == $this->MasterTable) {
- // kCatDBItem class only has this attribute
+ elseif ( $object instanceof kCatDBItem ) {
$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 ( isset($foreign_key) ) {
+ $object->SetDBField($this->_foreignKey, $foreign_key);
}
- if ($mode == 'create') {
- $this->RaiseEvent('OnBeforeClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key);
+ if ( $mode == 'create' ) {
+ $this->_raiseEvent('OnBeforeClone', $object->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')) ) {
+ if ( $res ) {
+ if ( $mode == 'create' && $this->_multipleParents ) {
// remember original => clone mapping for dual ForeignKey updating
- $this->AlreadyProcessed[$master['TableName']][$id] = $object->GetId();
+ self::$_clonedIds[$this->_tableName][$id] = $object->GetID();
}
- if ($mode == 'create') {
- $this->RaiseEvent('OnAfterClone', $master['Prefix'], $special, Array($object->GetId()), $foreign_key, array('original_id' => $id) );
- $this->saveID($master['Prefix'], $special, $object->GetID());
+ if ( $mode == 'create' ) {
+ $this->_raiseEvent('OnAfterClone', $object->Special, Array ($object->GetID()), $foreign_key, Array ('original_id' => $id));
+ $this->_saveId($object->Special, $object->GetID());
}
- if ( is_array(getArrayValue($master, 'SubTables')) ) {
- foreach($master['SubTables'] as $sub_table) {
- if (!getArrayValue($sub_table, 'AutoClone')) continue;
- $sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
-
- $foreign_key_field = is_array($sub_table['ForeignKey']) ? $sub_table['ForeignKey'][$master['Prefix']] : $sub_table['ForeignKey'];
- $parent_key_field = is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$master['Prefix']] : $sub_table['ParentTableKey'];
-
- if (!$foreign_key_field || !$parent_key_field) continue;
-
- $query = 'SELECT '.$sub_table['IdField'].' FROM '.$sub_TableName.'
- WHERE '.$foreign_key_field.' = '.$original_values[$parent_key_field];
- if (isset($sub_table['Constrain'])) $query .= ' AND '.$sub_table['Constrain'];
-
- $sub_ids = $this->Conn->GetCol($query);
-
- if ( is_array(getArrayValue($sub_table, 'ForeignKey')) ) {
- // $sub_ids could containt newly cloned items, we need to remove it here
- // to escape double cloning
-
- $cloned_ids = getArrayValue($this->AlreadyProcessed, $sub_table['TableName']);
- if ( !$cloned_ids ) $cloned_ids = Array();
- $new_ids = array_values($cloned_ids);
- $sub_ids = array_diff($sub_ids, $new_ids);
- }
-
- $parent_key = $object->GetDBField($parent_key_field);
-
- $this->CloneItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key, $master['Prefix']);
- }
+ /* @var $sub_table kTempHandlerSubTable */
+ foreach ($this->_subTables as $sub_table) {
+ $sub_table->subCloneItems($object, $original_values);
}
}
}
- if (!$ids) {
- $this->savedIDs[$prefix.($special ? '.' : '').$special] = Array();
- }
-
- return $this->savedIDs[$prefix.($special ? '.' : '').$special];
+ return isset($this->_savedIds[$object->Special]) ? $this->_savedIds[$object->Special] : Array ();
}
- function DeleteItems($prefix, $special, $ids, $master=null, $foreign_key=null)
+ /**
+ * Returns item, associated with this table
+ *
+ * @param string $prefix_special
+ * @return kDBItem
+ * @access protected
+ */
+ protected function _getItem($prefix_special)
{
- if ( !$ids ) {
- return;
- }
-
- if ( !isset($master) ) {
- $master = $this->Tables;
- }
-
- if ( strpos($prefix, '.') !== false ) {
- list($prefix, $special) = explode('.', $prefix, 2);
- }
-
- $prefix_special = rtrim($prefix . '.' . $special, '.');
+ // recalling by different name, because we may get kDBList, if we recall just by prefix
+ $parsed_prefix = $this->Application->processPrefix($prefix_special);
+ $recall_prefix = $parsed_prefix['prefix'] . '.' . preg_replace('/-item$/', '', $parsed_prefix['special']) . '-item';
- //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, 'parent_event' => $this->parentEvent));
+ $object = $this->Application->recallObject($recall_prefix, null, Array ('skip_autoload' => true, 'parent_event' => $this->_parentEvent));
/* @var $object kDBItem */
- foreach ($ids as $id) {
- $object->Load($id);
- $original_values = $object->GetFieldValues();
-
- if ( !$object->Delete($id) ) {
- continue;
- }
+ return $object;
+ }
- if ( is_array(getArrayValue($master, 'SubTables')) ) {
- foreach ($master['SubTables'] as $sub_table) {
- if ( !getArrayValue($sub_table, 'AutoDelete') ) {
- continue;
- }
+ /**
+ * Copies data from temp table that has sub-tables one-by-one record
+ *
+ * @param $temp_ids
+ * @return void
+ * @access protected
+ */
+ protected function _copyTempToLiveWithSubTables($temp_ids)
+ {
+ /* @var $sub_table kTempHandlerSubTable */
- $sub_TableName = $object->IsTempTable() ? $this->GetTempName($sub_table['TableName']) : $sub_table['TableName'];
+ $live_ids = Array ();
- $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'];
+ foreach ($temp_ids as $index => $temp_id) {
+ $this->_raiseEvent('OnBeforeCopyToLive', '', Array ($temp_id));
- if ( !$foreign_key_field || !$parent_key_field ) {
- continue;
- }
+ list ($new_temp_id, $live_id) = $this->_copyOneTempID($temp_id);
+ $live_ids[$index] = $live_id;
- $sql = 'SELECT ' . $sub_table['IdField'] . '
- FROM ' . $sub_TableName . '
- WHERE ' . $foreign_key_field . ' = ' . $original_values[$parent_key_field];
- $sub_ids = $this->Conn->GetCol($sql);
+ $this->_saveId('', Array ($temp_id => $live_id));
+ $this->_raiseEvent('OnAfterCopyToLive', '', Array ($temp_id => $live_id));
- $parent_key = $object->GetDBField(is_array($sub_table['ParentTableKey']) ? $sub_table['ParentTableKey'][$prefix] : $sub_table['ParentTableKey']);
+ $this->_updateChangeLogForeignKeys($live_id, $temp_id);
- $this->DeleteItems($sub_table['Prefix'], $special, $sub_ids, $sub_table, $parent_key);
- }
+ foreach ($this->_subTables as $sub_table) {
+ $sub_table->subUpdateForeignKeys($live_id, $temp_id);
}
+ // delete only after sub-table foreign key update !
+ $this->_deleteOneTempID($new_temp_id);
}
- }
- 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']);
- }
- }
+ $this->_setAsCopied();
- if (is_array($ids)) {
- $ids = join(',', $ids);
+ // when all of ids in current master has been processed, copy all sub-tables data
+ foreach ($this->_subTables as $sub_table) {
+ $sub_table->subCopyToLive($live_ids, $temp_ids);
}
+ }
- $table_sig = $master['TableName'].(isset($master['Constrain']) ? $master['Constrain'] : '');
+ /**
+ * Copies data from temp table that has no sub-tables all records together
+ *
+ * @param $temp_ids
+ * @return void
+ * @access protected
+ */
+ protected function _copyTempToLiveWithoutSubTables($temp_ids)
+ {
+ $live_ids = Array ();
+ $this->_raiseEvent('OnBeforeCopyToLive', '', $temp_ids);
- 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'];
+ foreach ($temp_ids as $temp_id) {
+ if ( $temp_id > 0 ) {
+ $live_ids[$temp_id] = $temp_id;
+ // positive ids (already live) will be copied together below
+ continue;
}
- $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);
+ // copy negative IDs (exists only in temp) one-by-one
+ list ($new_temp_id, $live_id) = $this->_copyOneTempID($temp_id);
+ $live_ids[$temp_id] = $live_id;
- $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) );
+ $this->_updateChangeLogForeignKeys($live_ids[$temp_id], $temp_id);
+ $this->_deleteOneTempID($new_temp_id);
}
- 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;
+ // copy ALL records with positive ids (since negative ids were processed above) to live table
+ $sql = 'INSERT INTO ' . $this->_tableName . '
+ SELECT *
+ FROM ' . $this->_getTempTableName();
+ $this->Conn->Query($this->_addConstrain($sql));
- 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']);
- }
- }
+ $this->_saveId('', $live_ids);
+ $this->_raiseEvent('OnAfterCopyToLive', '', $live_ids);
+ $this->_setAsCopied();
}
- function GetForeignKeys($master, $sub_table, $live_id, $temp_id=null)
+ /**
+ * Copies one record with 0/negative ID from temp to live table to obtain it's live auto-increment id
+ *
+ * @param int $temp_id
+ * @return Array Pair of temp id and live id
+ * @access protected
+ */
+ protected function _copyOneTempID($temp_id)
{
- $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);
+ $copy_id = $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 ( $temp_id < 0 ) {
+ $sql = 'UPDATE ' . $this->_getTempTableName() . '
+ SET ' . $this->_idField . ' = 0
+ WHERE ' . $this->_idField . ' = ' . $temp_id;
+ $this->Conn->Query($this->_addConstrain($sql));
+ $copy_id = 0;
}
- $cached = getArrayValue($this->FKeysCache, $master['TableName'].'.'.$parent_key_field);
-
- if ( $cached ) {
- if ( array_key_exists(serialize($live_id), $cached) ) {
- list($live_foreign_key, $temp_foreign_key) = $cached[serialize($live_id)];
- if ($mode == 1) {
- return $live_foreign_key;
- }
- else {
- return Array($live_foreign_key[0], $temp_foreign_key[0]);
- }
- }
- }
+ $sql = 'INSERT INTO ' . $this->_tableName . '
+ SELECT *
+ FROM ' . $this->_getTempTableName() . '
+ WHERE ' . $this->_idField . ' = ' . $copy_id;
+ $this->Conn->Query($sql);
- 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);
+ return Array ($copy_id, $copy_id == 0 ? $this->Conn->getInsertID() : $copy_id);
+ }
- 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;
- }
+ /**
+ * Delete already copied record from master temp table
+ *
+ * @param int $temp_id
+ * @return void
+ * @access protected
+ */
+ protected function _deleteOneTempID($temp_id)
+ {
+ $sql = 'DELETE FROM ' . $this->_getTempTableName() . '
+ WHERE ' . $this->_idField . ' = ' . $temp_id;
+ $this->Conn->Query($this->_addConstrain($sql));
+ }
- $this->FKeysCache[$master['TableName'].'.'.$parent_key_field][serialize($live_id)] = Array($live_foreign_key, $temp_foreign_key);
+ /**
+ * Deletes records from live table
+ *
+ * @param $ids
+ * @return void
+ * @access protected
+ */
+ abstract protected function _deleteFromLive($ids);
- if ($mode == 1) {
- return $live_foreign_key;
- }
- else {
- return Array($live_foreign_key[0], $temp_foreign_key[0]);
+ /**
+ * Ensures, that ids are always an array
+ *
+ * @param Array $ids
+ * @return Array
+ * @access protected
+ */
+ protected function _parseTempIds($ids)
+ {
+ if ( !$ids ) {
+ $sql = 'SELECT ' . $this->_idField . '
+ FROM ' . $this->_getTempTableName();
+ $ids = $this->Conn->GetCol($this->_addConstrain($sql));
}
+
+ return $ids;
}
/**
- * Copies data from temp to live table and returns IDs of copied records
+ * Sets new parent event to the object
*
- * @param Array $master
- * @param string $parent_prefix
- * @param Array $current_ids
- * @return Array
+ * @param kEvent $event
+ * @return void
* @access public
*/
- public function DoCopyTempToOriginal($master, $parent_prefix = null, $current_ids = Array())
+ public function setParentEvent(kEvent $event)
{
- if ( !$current_ids ) {
- $query = 'SELECT ' . $master['IdField'] . ' FROM ' . $this->GetTempName($master['TableName']);
+ $this->_parentEvent = $event;
+ $this->_top()->_drillDown($this, 'setParentEvent');
+ }
- if ( isset($master['Constrain']) ) {
- $query .= ' WHERE ' . $master['Constrain'];
- }
+ /**
+ * Collects information about table
+ *
+ * @return void
+ * @access protected
+ */
+ protected function _collectTableInfo()
+ {
+ $config = $this->Application->getUnitConfig($this->_prefix);
- $current_ids = $this->Conn->GetCol($query);
- }
+ $this->_idField = $config->getIDField();
+ $this->_tableName = $config->getTableName();
- $table_sig = $master['TableName'] . (isset($master['Constrain']) ? $master['Constrain'] : '');
+ $this->_foreignKey = $config->getForeignKey();
+ $this->_parentTableKey = $config->getParentTableKey();
+ $this->_constrain = $config->getConstrain('');
- 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);
+ $this->_autoClone = $config->getAutoClone();
+ $this->_autoDelete = $config->getAutoDelete();
+ }
- $query = 'DELETE FROM ' . $master['TableName'] . ' WHERE ' . $master['IdField'] . ' IN (' . join(',', $current_ids) . ')';
- $this->Conn->Query($query);
- }
+ /**
+ * Discovers and adds sub-tables to this table
+ *
+ * @return void
+ * @access protected
+ * @throws InvalidArgumentException
+ */
+ protected function _addSubTables()
+ {
+ $sub_items = $this->Application->getUnitConfig($this->_prefix)->getSubItems(Array ());
- if ( getArrayValue($master, 'SubTables') ) {
- if ( in_array($table_sig, $this->CopiedTables) || $this->FinalRefs[$table_sig] != $master['TableId'] ) {
- return Array ();
- }
+ if ( !is_array($sub_items) ) {
+ throw new InvalidArgumentException('TempHandler: SubItems property in unit config must be an array');
+ }
- foreach ($current_ids AS $id) {
- $this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', Array ($id));
+ foreach ($sub_items as $sub_item_prefix) {
+ $this->add(new kTempHandlerSubTable($sub_item_prefix));
+ }
+ }
- //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'];
- }
+ /**
+ * Adds new sub-table
+ *
+ * @param kTempHandlerSubTable $table
+ * @return void
+ * @access public
+ */
+ public function add(kTempHandlerSubTable $table)
+ {
+ if ( !$table->unitRegistered() ) {
+ trigger_error('TempHandler: unit "' . $table->_prefix . '" not registered', E_USER_WARNING);
- $this->Conn->Query($query);
- $id_to_copy = 0;
- }
- else {
- $id_to_copy = $id;
- }
+ return ;
+ }
- //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);
+ $this->_subTables[] = $table;
+ $table->setParent($this);
+ }
- $insert_id = $id_to_copy == 0 ? $this->Conn->getInsertID() : $id_to_copy;
+ /**
+ * Sets parent table
+ *
+ * @param kTempHandlerTable $parent
+ * @return void
+ * @access public
+ */
+ public function setParent(kTempHandlerTable $parent)
+ {
+ $this->_parent = $parent;
- $this->saveID($master['Prefix'], '', array ($id => $insert_id));
- $this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', Array ($insert_id), null, Array ('temp_id' => $id));
+ if ( is_array($this->_foreignKey) ) {
+ $this->_multipleParents = true;
+ $this->_foreignKey = $this->_foreignKey[$parent->_prefix];
+ }
- $this->UpdateForeignKeys($master, $insert_id, $id);
+ if ( is_array($this->_parentTableKey) ) {
+ $this->_parentTableKey = $this->_parentTableKey[$parent->_prefix];
+ }
- //delete already copied record from master temp table
- $query = ' DELETE FROM ' . $this->GetTempName($master['TableName']) . '
- WHERE ' . $master['IdField'] . ' = ' . $id_to_copy;
+ $this->_setAsLastUsed();
+ $this->_addSubTables();
+ }
- if ( isset($master['Constrain']) ) {
- $query .= ' AND ' . $master['Constrain'];
- }
+ /**
+ * Returns unit prefix
+ *
+ * @return string
+ * @access public
+ */
+ public function getPrefix()
+ {
+ return $this->_prefix;
+ }
- $this->Conn->Query($query);
- }
+ /**
+ * Determines if unit used to create table exists
+ *
+ * @return bool
+ * @access public
+ */
+ public function unitRegistered()
+ {
+ return $this->Application->prefixRegistred($this->_prefix);
+ }
- $this->CopiedTables[] = $table_sig;
+ /**
+ * Returns topmost table
+ *
+ * @return kTempHandlerTopTable
+ * @access protected
+ */
+ protected function _top()
+ {
+ $top = $this;
- // when all of ids in current master has been processed, copy all sub-tables data
- $this->CopySubTablesToLive($master, $current_ids);
- }
- elseif ( !in_array($table_sig, $this->CopiedTables) && ($this->FinalRefs[$table_sig] == $master['TableId']) ) { //If current master doesn't have sub-tables - we could use mass operations
- // We don't need to delete items from live here, as it get deleted in the beginning of the method for MasterTable
- // or in parent table processing for sub-tables
- $live_ids = Array ();
- $this->RaiseEvent('OnBeforeCopyToLive', $master['Prefix'], '', $current_ids);
-
- foreach ($current_ids as $an_id) {
- if ( $an_id > 0 ) {
- $live_ids[$an_id] = $an_id;
- // positive (already live) IDs will be copied in on query all togather below,
- // so we just store it here
- continue;
- }
- else { // zero or negative ids should be copied one by one to get their InsertId
- // resetting to 0 so it get inserted into live table with autoincrement
- $query = ' UPDATE ' . $this->GetTempName($master['TableName']) . '
- SET ' . $master['IdField'] . ' = 0
- WHERE ' . $master['IdField'] . ' = ' . $an_id;
- // constrain is not needed here because ID is already unique
- $this->Conn->Query($query);
-
- // copying
- $query = ' INSERT INTO ' . $master['TableName'] . '
- SELECT * FROM ' . $this->GetTempName($master['TableName']) . '
- WHERE ' . $master['IdField'] . ' = 0';
- $this->Conn->Query($query);
-
- $live_ids[$an_id] = $this->Conn->getInsertID(); //storing newly created live id
-
- //delete already copied record from master temp table
- $query = ' DELETE FROM ' . $this->GetTempName($master['TableName']) . '
- WHERE ' . $master['IdField'] . ' = 0';
- $this->Conn->Query($query);
+ while ( is_object($top->_parent) ) {
+ $top = $top->_parent;
+ }
- $this->UpdateChangeLogForeignKeys($master, $live_ids[$an_id], $an_id);
- }
- }
+ return $top;
+ }
- // copy ALL records to live table
- $query = ' INSERT INTO ' . $master['TableName'] . '
- SELECT * FROM ' . $this->GetTempName($master['TableName']);
+ /**
+ * Performs given operation on current table and all it's sub-tables
+ *
+ * @param kTempHandlerTable $table
+ * @param string $operation
+ * @param bool $same_table
+ * @param bool $same_constrain
+ * @return void
+ * @access protected
+ */
+ protected function _drillDown(kTempHandlerTable $table, $operation, $same_table = false, $same_constrain = false)
+ {
+ $table_match = $same_table ? $this->_tableName == $table->_tableName : true;
+ $constrain_match = $same_constrain ? $this->_constrain == $table->_constrain : true;
- if ( isset($master['Constrain']) ) {
- $query .= ' WHERE ' . $master['Constrain'];
- }
+ if ( $table_match && $constrain_match ) {
+ switch ( $operation ) {
+ case 'state:copied':
+ $this->_addState(self::STATE_COPIED);
+ break;
- $this->Conn->Query($query);
+ case 'state:deleted':
+ $this->_addState(self::STATE_DELETED);
+ break;
- $this->CopiedTables[] = $table_sig;
- $this->RaiseEvent('OnAfterCopyToLive', $master['Prefix'], '', $live_ids);
+ case 'setParentEvent':
+ $this->_parentEvent = $table->_parentEvent;
+ break;
- $this->saveID($master['Prefix'], '', $live_ids);
- // no need to clear temp table - it will be dropped by next statement
+ case 'resetLastUsed':
+ $this->_lastUsage = false;
+ break;
}
}
- if ( $this->FinalRefs[ $master['TableName'] ] != $master['TableId'] ) {
- return Array ();
+ /* @var $sub_table kTempHandlerSubTable */
+ foreach ($this->_subTables as $sub_table) {
+ $sub_table->_drillDown($table, $operation, $same_table, $same_constrain);
}
-
- /*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
+ * Marks this instance of a table as it's last usage
*
- * @return kDBConnection
+ * @return void
+ * @access protected
*/
- function &_getSeparateConnection()
+ protected function _setAsLastUsed()
{
- static $connection = null;
-
- if (!isset($connection)) {
- $connection = $this->Application->makeClass( 'kDBConnection', Array (SQL_TYPE, Array ($this->Application, 'handleSQLError')) );
- /* @var $connection kDBConnection */
-
- $connection->debugMode = $this->Application->isDebugMode();
- $connection->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB, true);
- }
+ $this->_top()->_drillDown($this, 'resetLastUsed', true, true);
+ $this->_lastUsage = true;
+ }
- return $connection;
+ /**
+ * Marks table and all it's clones as copied
+ *
+ * @return void
+ * @access protected
+ */
+ protected function _setAsCopied()
+ {
+ $this->_top()->_drillDown($this, 'state:copied', true, true);
}
- function UpdateChangeLogForeignKeys($master, $live_id, $temp_id)
+ /**
+ * Update foreign key columns after new ids were assigned instead of temporary ids in change log
+ *
+ * @param int $live_id
+ * @param int $temp_id
+ */
+ function _updateChangeLogForeignKeys($live_id, $temp_id)
{
- if ($live_id == $temp_id) {
- return ;
+ if ( $live_id == $temp_id ) {
+ return;
}
- $prefix = $master['Prefix'];
- $main_prefix = $this->Application->GetTopmostPrefix($prefix);
- $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
+ $main_prefix = $this->Application->GetTopmostPrefix($this->_prefix);
+ $ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->_prefix);
$changes = $this->Application->RecallVar($ses_var_name);
$changes = $changes ? unserialize($changes) : Array ();
foreach ($changes as $key => $rec) {
- if ($rec['Prefix'] == $prefix && $rec['ItemId'] == $temp_id) {
+ if ( $rec['Prefix'] == $this->_prefix && $rec['ItemId'] == $temp_id ) {
// main item change log record
$changes[$key]['ItemId'] = $live_id;
}
- if ($rec['MasterPrefix'] == $prefix && $rec['MasterId'] == $temp_id) {
+ if ( $rec['MasterPrefix'] == $this->_prefix && $rec['MasterId'] == $temp_id ) {
// sub item change log record
$changes[$key]['MasterId'] = $live_id;
}
- if (in_array($prefix, $rec['ParentPrefix']) && $rec['ParentId'][$prefix] == $temp_id) {
+ if ( in_array($this->_prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->_prefix] == $temp_id ) {
// parent item change log record
- $changes[$key]['ParentId'][$prefix] = $live_id;
+ $changes[$key]['ParentId'][$this->_prefix] = $live_id;
- if (array_key_exists('DependentFields', $rec)) {
+ if ( array_key_exists('DependentFields', $rec) ) {
// these are fields from table of $rec['Prefix'] table!
// when one of dependent fields goes into idfield of it's parent item, that was changed
$config = $this->Application->getUnitConfig($rec['Prefix']);
- $parent_table_key = $config->getParentTableKey($prefix);
+ $parent_table_key = $config->getParentTableKey($this->_prefix);
- if ($parent_table_key == $master['IdField']) {
- $foreign_key = $config->getForeignKey($prefix);
+ if ( $parent_table_key == $this->_idField ) {
+ $foreign_key = $config->getForeignKey($this->_prefix);
$changes[$key]['DependentFields'][$foreign_key] = $live_id;
}
}
}
}
$this->Application->StoreVar($ses_var_name, serialize($changes));
}
- function UpdateForeignKeys($master, $live_id, $temp_id)
+ /**
+ * Returns foreign key pairs for given ids and $sub_table
+ *
+ * USE: MainTable
+ *
+ * @param kTempHandlerSubTable $sub_table
+ * @param int|Array $live_id
+ * @param int|Array $temp_id
+ * @return Array
+ * @access protected
+ */
+ protected function _getForeignKeys(kTempHandlerSubTable $sub_table, $live_id, $temp_id = null)
{
- $this->UpdateChangeLogForeignKeys($master, $live_id, $temp_id);
+ $single_mode = false;
- foreach ($master['SubTables'] as $sub_table) {
- $foreign_key_field = is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
+ if ( !is_array($live_id) ) {
+ $single_mode = true;
+ $live_id = Array ($live_id);
+ }
- if (!$foreign_key_field) {
- continue;
+ if ( isset($temp_id) && !is_array($temp_id) ) {
+ $temp_id = Array ($temp_id);
+ }
+
+ $cache_key = serialize($live_id);
+ $parent_key_field = $sub_table->_parentTableKey ? $sub_table->_parentTableKey : $this->_idField;
+ $cached = getArrayValue($this->_foreignKeyCache, $parent_key_field);
+
+ if ( $cached ) {
+ if ( array_key_exists($cache_key, $cached) ) {
+ list($live_foreign_key, $temp_foreign_key) = $cached[$cache_key];
+
+ return $single_mode ? Array ($live_foreign_key[0], $temp_foreign_key[0]) : $live_foreign_key;
}
+ }
+
+ if ( $parent_key_field != $this->_idField ) {
+ $sql = 'SELECT ' . $parent_key_field . '
+ FROM ' . $this->_tableName . '
+ WHERE ' . $this->_idField . ' IN (' . implode(',', $live_id) . ')';
+ $live_foreign_key = $this->Conn->GetCol($sql);
- list ($live_foreign_key, $temp_foreign_key) = $this->GetForeignKeys($master, $sub_table, $live_id, $temp_id);
+ if ( isset($temp_id) ) {
+ // because doCopyTempToLive resets negative IDs to 0 in temp table (one by one) before copying to live
+ $temp_key = $temp_id < 0 ? 0 : $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);
+ $sql = 'SELECT ' . $parent_key_field . '
+ FROM ' . $this->_getTempTableName() . '
+ WHERE ' . $this->_idField . ' IN (' . implode(',', $temp_key) . ')';
+ $temp_foreign_key = $this->Conn->GetCol($sql);
+ }
+ else {
+ $temp_foreign_key = Array ();
}
}
+ else {
+ $live_foreign_key = $live_id;
+ $temp_foreign_key = $temp_id;
+ }
+
+ $this->_foreignKeyCache[$parent_key_field][$cache_key] = Array ($live_foreign_key, $temp_foreign_key);
+
+ if ( $single_mode ) {
+ return Array ($live_foreign_key[0], $temp_foreign_key[0]);
+ }
+
+ return $live_foreign_key;
}
- 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);
- }
- }
+ /**
+ * Adds constrain to given sql
+ *
+ * @param $sql
+ * @return string
+ * @access protected
+ */
+ protected function _addConstrain($sql)
+ {
+ if ( $this->_constrain ) {
+ $sql .= ' AND ' . $this->_constrain;
+ }
+
+ return $sql;
+ }
+
+ /**
+ * Creates temp table
+ * Don't use CREATE TABLE ... LIKE because it also copies indexes
+ *
+ * @return void
+ * @access protected
+ */
+ protected function _create()
+ {
+ $sql = 'CREATE TABLE ' . $this->_getTempTableName() . '
+ SELECT *
+ FROM ' . $this->_tableName . '
+ WHERE 0';
+ $this->Conn->Query($sql);
+ }
+
+ /**
+ * Deletes temp table
+ *
+ * @return bool
+ * @access protected
+ */
+ protected function _delete()
+ {
+ if ( $this->_inState(self::STATE_DELETED) ) {
+ return false;
+ }
+
+ $sql = 'DROP TABLE IF EXISTS ' . $this->_getTempTableName();
+ $this->Conn->Query($sql);
+
+ $this->_top()->_drillDown($this, 'state:deleted', true);
+
+ return true;
+ }
+
+ /**
+ * Deletes table and all it's sub-tables
+ *
+ * @return void
+ * @access public
+ */
+ public function deleteAll()
+ {
+ $this->_delete();
+
+ /* @var $sub_table kTempHandlerSubTable */
+ foreach ($this->_subTables as $sub_table) {
+ $sub_table->deleteAll();
+ }
+ }
+
+ /**
+ * Returns temp table name for current table
+ *
+ * @return string
+ * @access protected
+ */
+ protected function _getTempTableName()
+ {
+ return $this->Application->GetTempName($this->_tableName, $this->_windowID);
+ }
+
+ /**
+ * Adds table state
+ *
+ * @param int $state
+ * @return kTempHandlerTable
+ * @access protected
+ */
+ protected function _addState($state)
+ {
+ $this->_state |= $state;
+
+ return $this;
+ }
+
+ /**
+ * Removes table state
+ *
+ * @param int $state
+ * @return kTempHandlerTable
+ * @access protected
+ */
+ protected function _removeState($state)
+ {
+ $this->_state = $this->_state &~ $state;
+
+ return $this;
+ }
+
+ /**
+ * Checks that table has given state
+ *
+ * @param int $state
+ * @return bool
+ * @access protected
+ */
+ protected function _inState($state)
+ {
+ return ($this->_state & $state) == $state;
+ }
+
+ /**
+ * Saves id for later usage
+ *
+ * @param string $special
+ * @param int|Array $id
+ * @return void
+ * @access protected
+ */
+ protected function _saveId($special = '', $id = null)
+ {
+ if ( !isset($this->_savedIds[$special]) ) {
+ $this->_savedIds[$special] = Array ();
+ }
+
+ if ( is_array($id) ) {
+ foreach ($id as $tmp_id => $live_id) {
+ $this->_savedIds[$special][$tmp_id] = $live_id;
}
- //sub_table passed here becomes master in the method, and recursively updated and copy its sub tables
- $this->DoCopyTempToOriginal($sub_table, $master['Prefix']);
+ }
+ else {
+ $this->_savedIds[$special][] = $id;
}
}
/**
* Raises event using IDs, that are currently being processed in temp handler
*
* @param string $name
- * @param string $prefix
* @param string $special
* @param Array $ids
* @param string $foreign_key
* @param Array $add_params
* @return bool
* @access protected
*/
- protected function RaiseEvent($name, $prefix, $special, $ids, $foreign_key = null, $add_params = null)
+ protected function _raiseEvent($name, $special, $ids, $foreign_key = null, $add_params = null)
{
if ( !is_array($ids) ) {
return true;
}
- $event_key = $prefix . ($special ? '.' : '') . $special . ':' . $name;
- $event = new kEvent($event_key);
- $event->MasterEvent = $this->parentEvent;
+ $event = new kEvent($this->_prefix . ($special ? '.' : '') . $special . ':' . $name);
+ $event->MasterEvent = $this->_parentEvent;
if ( isset($foreign_key) ) {
$event->setEventParam('foreign_key', $foreign_key);
}
$set_temp_id = ($name == 'OnAfterCopyToLive') && (!is_array($add_params) || !array_key_exists('temp_id', $add_params));
foreach ($ids as $index => $id) {
$event->setEventParam('id', $id);
if ( $set_temp_id ) {
$event->setEventParam('temp_id', $index);
}
if ( is_array($add_params) ) {
foreach ($add_params as $name => $val) {
$event->setEventParam($name, $val);
}
}
$this->Application->HandleEvent($event);
}
return $event->status == kEvent::erSUCCESS;
}
+}
- function DropTempTable($table)
- {
- if ( in_array($table, $this->DroppedTables) ) {
- return false;
- }
- $query = 'DROP TABLE IF EXISTS ' . $this->GetTempName($table);
+/**
+ * Represents topmost table, that has related tables inside it
+ *
+ * Pattern: Composite
+ */
+class kTempHandlerTopTable extends kTempHandlerTable {
- array_push($this->DroppedTables, $table);
- $this->DroppedTables = array_unique($this->DroppedTables);
- $this->Conn->Query($query);
+ /**
+ * Creates table object
+ *
+ * @param string $prefix
+ * @param Array $ids
+ */
+ public function __construct($prefix, $ids = Array ())
+ {
+ parent::__construct($prefix, $ids);
- return true;
+ $this->_setAsLastUsed();
+ $this->_addSubTables();
}
- function PrepareEdit()
+ /**
+ * Checks, that someone is editing selected records and returns true, when no one.
+ *
+ * @param Array $ids
+ * @return bool
+ * @access public
+ */
+ public function checkSimultaneousEdit($ids = null)
{
- $this->DoCopyLiveToTemp($this->Tables, $this->Tables['IDs']);
+ if ( !$this->Application->getUnitConfig($this->_prefix)->getCheckSimulatniousEdit() ) {
+ return true;
+ }
+
+ $tables = $this->Conn->GetCol('SHOW TABLES');
+ $mask_edit_table = '/' . TABLE_PREFIX . 'ses_(.*)_edit_' . $this->_tableName . '$/';
- if ($this->Application->getUnitConfig($this->Tables['Prefix'])->getCheckSimulatniousEdit()) {
- $this->CheckSimultaniousEdit();
+ $my_sid = $this->Application->GetSID();
+ $my_wid = $this->Application->GetVar('m_wid');
+ $ids = implode(',', isset($ids) ? $ids : $this->_ids);
+
+ $sids = Array ();
+
+ if ( !$ids ) {
+ return true;
}
- }
- function SaveEdit($master_ids = Array())
- {
- // SessionKey field is required for deleting records from expired sessions
- $conn =& $this->_getSeparateConnection();
+ foreach ($tables as $table) {
+ if ( !preg_match($mask_edit_table, $table, $regs) ) {
+ continue;
+ }
- $sleep_count = 0;
- do {
- // acquire lock
- $conn->ChangeQuery('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
+ // remove popup's wid from sid
+ $sid = preg_replace('/(.*)_(.*)/', '\\1', $regs[1]);
- $sql = 'SELECT SessionKey
- FROM ' . TABLE_PREFIX . 'Semaphores
- WHERE (MainPrefix = ' . $conn->qstr($this->Tables['Prefix']) . ')';
- $another_coping_active = $conn->GetOne($sql);
+ if ( $sid == $my_sid ) {
+ if ( $my_wid ) {
+ // using popups for editing
+ if ( preg_replace('/(.*)_(.*)/', '\\2', $regs[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;
+ }
+ }
- 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);
+ $sql = 'SELECT COUNT(' . $this->_idField . ')
+ FROM ' . $table . '
+ WHERE ' . $this->_idField . ' IN (' . $ids . ')';
+ $found = $this->Conn->GetOne($sql);
+
+ if ( !$found || in_array($sid, $sids) ) {
+ continue;
}
- } while ($another_coping_active && ($sleep_count <= 30));
- if ($sleep_count > 30) {
- // another coping process failed to finished in 30 seconds
- $error_message = $this->Application->Phrase('la_error_TemporaryTableCopyingFailed');
- $this->Application->SetVar('_temp_table_message', $error_message);
+ $sids[] = $sid;
+ }
- return false;
+ if ( !$sids ) {
+ return true;
}
- // 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'],
- );
+ // detect who is it
+ $sql = 'SELECT CONCAT(
+ (CASE s.PortalUserId WHEN ' . USER_ROOT . ' THEN "root" WHEN ' . USER_GUEST . ' THEN "Guest" ELSE CONCAT(u.FirstName, " ", u.LastName, " (", u.Username, ")") END),
+ " IP: ", s.IpAddress
+ )
+ FROM ' . TABLE_PREFIX . 'UserSessions AS s
+ LEFT JOIN ' . TABLE_PREFIX . 'Users AS u ON u.PortalUserId = s.PortalUserId
+ WHERE s.SessionKey IN (' . implode(',', $sids) . ')';
+ $users = $this->Conn->GetCol($sql);
- $conn->doInsert($fields_hash, TABLE_PREFIX.'Semaphores');
- $semaphore_id = $conn->getInsertID();
+ if ( $users ) {
+ $this->Application->SetVar('_Simultaneous_edit_message', sprintf($this->Application->Phrase('la_record_being_edited_by'), implode(",\n", $users)));
- // unlock table now to prevent permanent lock in case, when coping will end with SQL error in the middle
- $conn->ChangeQuery('UNLOCK TABLES');
+ return false;
+ }
- $ids = $this->DoCopyTempToOriginal($this->Tables, null, $master_ids);
+ return true;
+ }
- // remove mark, that we are coping from temp to live
- $conn->Query('LOCK TABLES '.TABLE_PREFIX.'Semaphores WRITE');
+ /**
+ * Deletes records from live table
+ *
+ * @param $ids
+ * @return void
+ * @access protected
+ */
+ protected function _deleteFromLive($ids)
+ {
+ if ( !$this->_raiseEvent('OnBeforeDeleteFromLive', '', $ids) ) {
+ return;
+ }
- $sql = 'DELETE FROM ' . TABLE_PREFIX . 'Semaphores
- WHERE SemaphoreId = ' . $semaphore_id;
- $conn->ChangeQuery($sql);
+ $sql = 'DELETE FROM ' . $this->_tableName . '
+ WHERE ' . $this->_idField . ' IN (' . implode(',', $ids) . ')';
+ $this->Conn->Query($sql);
+ }
+}
- $conn->ChangeQuery('UNLOCK TABLES');
- return $ids;
+/**
+ * Represents sub table, that has related tables inside it
+ *
+ * Pattern: Composite
+ */
+class kTempHandlerSubTable extends kTempHandlerTable {
+
+ /**
+ * Deletes records from live table
+ *
+ * @param $ids
+ * @return void
+ * @access protected
+ */
+ protected function _deleteFromLive($ids)
+ {
+ // for sub-tables records get deleted in "subCopyToLive" method !BY Foreign Key!
}
- function CancelEdit($master=null)
+ /**
+ * Copies sub-table contents to live
+ *
+ * @param Array $live_ids
+ * @param Array $temp_ids
+ * @return void
+ * @access public
+ */
+ public function subCopyToLive($live_ids, $temp_ids)
{
- if (!isset($master)) $master = $this->Tables;
- $this->DropTempTable($master['TableName']);
- if ( getArrayValue($master, 'SubTables') ) {
- foreach ($master['SubTables'] as $sub_table) {
- $this->CancelEdit($sub_table);
+ // delete records from live table by foreign key, so that records deleted from temp table
+ // get deleted from live
+ if ( $temp_ids && !$this->_inState(self::STATE_COPIED) ) {
+ if ( !$this->_foreignKey ) {
+ return;
+ }
+
+ $foreign_keys = $this->_parent->_getForeignKeys($this, $live_ids, $temp_ids);
+
+ if ( count($foreign_keys) > 0 ) {
+ $sql = 'SELECT ' . $this->_idField . '
+ FROM ' . $this->_tableName . '
+ WHERE ' . $this->_foreignKey . ' IN (' . implode(',', $foreign_keys) . ')';
+ $ids = $this->Conn->GetCol($this->_addConstrain($sql));
+
+ if ( $this->_raiseEvent('OnBeforeDeleteFromLive', '', $ids, $foreign_keys) ) {
+ $sql = 'DELETE FROM ' . $this->_tableName . '
+ WHERE ' . $this->_foreignKey . ' IN (' . implode(',', $foreign_keys) . ')';
+ $this->Conn->Query($this->_addConstrain($sql));
+ }
}
}
+
+ // sub_table passed here becomes master in the method, and recursively updated and copy its sub tables
+ $this->doCopyTempToLive();
}
/**
- * Checks, that someone is editing selected records and returns true, when no one.
- *
- * @param Array $ids
+ * Deletes unit db records and it's sub-items by foreign key
*
- * @return bool
+ * @param kDBItem $object
+ * @param string $special
+ * @param Array $original_values
+ * @return void
+ * @access public
*/
- function CheckSimultaniousEdit($ids = null)
+ public function subDeleteItems(kDBItem $object, $special, $original_values)
{
- $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;
+ if ( !$this->_autoDelete || !$this->_foreignKey || !$this->_parentTableKey ) {
+ return;
}
- 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;
- }
- }
+ $table_name = $object->IsTempTable() ? $this->_getTempTableName() : $this->_tableName;
- $sql = 'SELECT COUNT(' . $this->Tables['IdField'] . ')
- FROM ' . $table . '
- WHERE ' . $this->Tables['IdField'] . ' IN (' . $ids . ')';
- $found = $this->Conn->GetOne($sql);
+ $sql = 'SELECT ' . $this->_idField . '
+ FROM ' . $table_name . '
+ WHERE ' . $this->_foreignKey . ' = ' . $original_values[$this->_parentTableKey];
+ $sub_ids = $this->Conn->GetCol($sql);
- if (!$found || in_array($sid, $sids)) {
- continue;
- }
+ $this->doDeleteItems($this->_prefix .'.' . $special, $sub_ids);
+ }
- $sids[] = $sid;
- }
+ /**
+ * Clones unit db records and it's sub-items by foreign key
+ *
+ * @param kDBItem $object
+ * @param Array $original_values
+ * @return void
+ * @access public
+ */
+ public function subCloneItems(kDBItem $object, $original_values)
+ {
+ if ( !$this->_autoClone || !$this->_foreignKey || !$this->_parentTableKey ) {
+ return;
}
- if ($sids) {
- // detect who is it
- $sql = 'SELECT
- CONCAT(IF (s.PortalUserId = ' . USER_ROOT . ', \'root\',
- IF (s.PortalUserId = ' . USER_GUEST . ', \'Guest\',
- CONCAT(u.FirstName, \' \', u.LastName, \' (\', u.Username, \')\')
- )
- ), \' IP: \', s.IpAddress, \'\') FROM ' . TABLE_PREFIX . 'UserSessions AS s
- LEFT JOIN ' . TABLE_PREFIX . 'Users AS u
- ON u.PortalUserId = s.PortalUserId
- WHERE s.SessionKey IN (' . implode(',', $sids) . ')';
- $users = $this->Conn->GetCol($sql);
-
- if ($users) {
- $this->Application->SetVar('_simultanious_edit_message',
- sprintf($this->Application->Phrase('la_record_being_edited_by'), join(",\n", $users))
- );
+ $table_name = $object->IsTempTable() ? $this->_getTempTableName() : $this->_tableName;
+
+ $sql = 'SELECT ' . $this->_idField . '
+ FROM ' . $table_name . '
+ WHERE ' . $this->_foreignKey . ' = ' . $original_values[$this->_parentTableKey];
+ $sub_ids = $this->Conn->GetCol($this->_addConstrain($sql));
- return false;
+ if ( $this->_multipleParents ) {
+ // $sub_ids could contain newly cloned items, we need to remove it here to escape double cloning
+ $cloned_ids = getArrayValue(self::$_clonedIds, $this->_tableName);
+
+ if ( !$cloned_ids ) {
+ $cloned_ids = Array ();
}
+
+ $sub_ids = array_diff($sub_ids, array_values($cloned_ids));
}
- return true;
+ $parent_key = $object->GetDBField($this->_parentTableKey);
+
+ $this->doCloneItems($this->_prefix . '.' . $object->Special, $sub_ids, $parent_key);
}
+ /**
+ * Update foreign key columns after new ids were assigned instead of temporary ids in db
+ *
+ * @param int $live_id
+ * @param int $temp_id
+ * @return void
+ * @access public
+ */
+ public function subUpdateForeignKeys($live_id, $temp_id)
+ {
+ if ( !$this->_foreignKey ) {
+ return;
+ }
+
+ list ($live_foreign_key, $temp_foreign_key) = $this->_parent->_getForeignKeys($this, $live_id, $temp_id);
+
+ // update ForeignKey in temporary sub-table
+ if ( $live_foreign_key == $temp_foreign_key ) {
+ return;
+ }
+
+ $sql = 'UPDATE ' . $this->_getTempTableName() . '
+ SET ' . $this->_foreignKey . ' = ' . $live_foreign_key . '
+ WHERE ' . $this->_foreignKey . ' = ' . $temp_foreign_key;
+ $this->Conn->Query($this->_addConstrain($sql));
+ }
}
\ No newline at end of file

Event Timeline