Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F800265
in-portal
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sat, Feb 22, 12:04 AM
Size
40 KB
Mime Type
text/x-diff
Expires
Mon, Feb 24, 12:04 AM (7 h, 34 m)
Engine
blob
Format
Raw Data
Handle
573523
Attached To
rINP In-Portal
in-portal
View Options
Index: branches/5.3.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.3.x/core/kernel/utility/temp_handler.php (revision 15946)
+++ branches/5.3.x/core/kernel/utility/temp_handler.php (revision 15947)
@@ -1,1628 +1,1638 @@
<?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 {
/**
* Tables
*
* @var kTempHandlerTopTable
*/
protected $_tables;
/**
* Event, that was used to create this object
*
* @var kEvent
* @access protected
*/
protected $parentEvent = null;
/**
* Sets new parent event to the object
*
* @param kEvent $event
* @return void
* @access public
*/
public function setParentEvent($event)
{
$this->parentEvent = $event;
if ( is_object($this->_tables) ) {
$this->_tables->setParentEvent($event);
}
}
/**
* Scans table structure of given unit
*
* @param string $prefix
* @param Array $ids
* @return void
* @access public
*/
public function BuildTables($prefix, $ids)
{
$this->_tables = new kTempHandlerTopTable($prefix, $ids);
if ( is_object($this->parentEvent) ) {
$this->_tables->setParentEvent($this->parentEvent);
}
}
/**
* Returns reference to top table.
*
* @return kTempHandlerTopTable
*/
public function getTopTable()
{
return $this->_tables;
}
/**
* Create temp table for editing db record from live table. If none ids are given, then just empty tables are created.
*
* @return void
* @access public
*/
public function PrepareEdit()
{
$this->_tables->doCopyLiveToTemp();
$this->_tables->checkSimultaneousEdit();
}
/**
* Deletes temp tables without copying their data back to live tables
*
* @return void
* @access public
*/
public function CancelEdit()
{
$this->_tables->deleteAll();
}
/**
* Saves changes made in temp tables to live tables
*
* @param Array $master_ids
* @return bool
* @access public
*/
public function SaveEdit($master_ids = Array())
{
// SessionKey field is required for deleting records from expired sessions
$sleep_count = 0;
$conn = $this->_getSeparateConnection();
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;
}
// 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' => time(),
'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;
}
/**
* Deletes unit data for given items along with related sub-items
*
* @param string $prefix
* @param string $special
* @param Array $ids
* @throws InvalidArgumentException
*/
function DeleteItems($prefix, $special, $ids)
{
if ( strpos($prefix, '.') !== false ) {
throw new InvalidArgumentException("Pass prefix and special as separate arguments");
}
if ( !is_array($ids) ) {
throw new InvalidArgumentException('Incorrect ids format');
}
$this->_tables->doDeleteItems(rtrim($prefix . '.' . $special, '.'), $ids);
}
/**
* Clones given ids
*
* @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 CloneItems($prefix, $special, $ids, $master = null, $foreign_key = null, $parent_prefix = null, $skip_filenames = false)
{
return $this->_tables->doCloneItems($prefix . '.' . $special, $ids, $foreign_key, $skip_filenames);
}
/**
* Create separate connection for locking purposes
*
* @return kDBConnection
* @access protected
*/
protected function _getSeparateConnection()
{
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);
}
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;
/**
* Reference to parent table
*
* @var kTempHandlerTable
* @access protected
*/
protected $_parent;
/**
* Field in this db table, that holds ID from it's parent table
*
* @var string
* @access protected
*/
protected $_foreignKey = '';
/**
* This table is connected to multiple parent tables
*
* @var bool
* @access protected
*/
protected $_multipleParents = false;
/**
* Foreign key cache
*
* @var Array
* @access protected
*/
protected $_foreignKeyCache = Array ();
/**
* Field in parent db table from where foreign key field value originates
*
* @var string
* @access protected
*/
protected $_parentTableKey = '';
/**
* Additional WHERE filter, that determines what records needs to be processed
*
* @var string
* @access protected
*/
protected $_constrain = '';
/**
* Automatically clone records from this table when parent table record is cloned
*
* @var bool
* @access protected
*/
protected $_autoClone = true;
/**
* Automatically delete records from this table when parent table record is deleted
*
* @var bool
* @access protected
*/
protected $_autoDelete = true;
/**
* List of sub-tables
*
* @var kTempHandlerSubTable[]
* @access protected
*/
protected $_subTables = Array ();
/**
* Window ID of current window
*
* @var int
* @access protected
*/
protected $_windowID = '';
/**
* 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->_collectTableInfo();
}
/**
* Creates temp tables (recursively) and optionally fills them with data from live table
*
* @param Array $foreign_keys
* @return void
* @access public
*/
public function doCopyLiveToTemp($foreign_keys = Array ())
{
$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();
}
}
$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);
}
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;
}
$sub_table->doCopyLiveToTemp($sub_foreign_keys);
}
}
/**
* 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 ( !$ids ) {
$ids = $this->_ids;
}
if ( is_array($ids) ) {
$ids = implode(',', $ids);
}
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);
if ( $current_ids ) {
$this->_deleteFromLive($current_ids);
if ( $this->_subTables ) {
if ( $this->_inState(self::STATE_COPIED) || !$this->_lastUsage ) {
return Array ();
}
$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
$this->_copyTempToLiveWithoutSubTables($current_ids);
// no need to clear temp table - it will be dropped by next statement
}
}
if ( !$this->_lastUsage ) {
return Array ();
}
/*if ( is_array(getArrayValue($master, 'ForeignKey')) ) { //if multiple ForeignKeys
if ( $master['ForeignKey'][$parent_prefix] != end($master['ForeignKey']) ) {
return; // Do not delete temp table if not all ForeignKeys have been processed (current is not the last)
}
}*/
$this->_delete();
$this->Application->resetCounters($this->_tableName);
return $this->getSavedIds();
}
/**
* 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 ( !$ids ) {
return;
}
$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;
}
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(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 ) {
$master = Array ('ForeignKey' => $this->_foreignKey, 'TableName' => $this->_tableName);
$object->NameCopy($master, $foreign_key);
}
elseif ( $object instanceof kCatDBItem ) {
$object->useFilenames = false;
}
if ( isset($foreign_key) ) {
$object->SetDBField($this->_foreignKey, $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' && $this->_multipleParents ) {
// remember original => clone mapping for dual ForeignKey updating
self::$_clonedIds[$this->_tableName][$id] = $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());
}
foreach ($this->_subTables as $sub_table) {
$sub_table->subCloneItems($object, $original_values);
}
}
}
return $this->getSavedIds($object->Special);
}
/**
* Returns item, associated with this table
*
* @param string $prefix_special
* @return kDBItem
* @access protected
*/
protected function _getItem($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';
$object = $this->Application->recallObject($recall_prefix, null, Array ('skip_autoload' => true, 'parent_event' => $this->_parentEvent));
/* @var $object kDBItem */
return $object;
}
/**
* 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)
{
$live_ids = Array ();
foreach ($temp_ids as $index => $temp_id) {
$this->_raiseEvent('OnBeforeCopyToLive', '', Array ($temp_id));
list ($new_temp_id, $live_id) = $this->_copyOneTempID($temp_id);
$live_ids[$index] = $live_id;
$this->_saveId('', Array ($temp_id => $live_id));
$this->_raiseEvent('OnAfterCopyToLive', '', Array ($temp_id => $live_id));
$this->_updateChangeLogForeignKeys($live_id, $temp_id);
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);
}
$this->_setAsCopied();
// 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);
}
}
/**
* 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);
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;
}
// 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->_updateChangeLogForeignKeys($live_ids[$temp_id], $temp_id);
$this->_deleteOneTempID($new_temp_id);
}
// 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));
$this->_saveId('', $live_ids);
$this->_raiseEvent('OnAfterCopyToLive', '', $live_ids);
$this->_setAsCopied();
}
/**
* 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)
{
$copy_id = $temp_id;
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;
}
$sql = 'INSERT INTO ' . $this->_tableName . '
SELECT *
FROM ' . $this->_getTempTableName() . '
WHERE ' . $this->_idField . ' = ' . $copy_id;
$this->Conn->Query($sql);
return Array ($copy_id, $copy_id == 0 ? $this->Conn->getInsertID() : $copy_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));
}
/**
* Deletes records from live table
*
* @param $ids
* @return void
* @access protected
*/
abstract protected function _deleteFromLive($ids);
/**
* 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;
}
/**
* Sets new parent event to the object
*
* @param kEvent $event
* @return void
* @access public
*/
public function setParentEvent(kEvent $event)
{
$this->_parentEvent = $event;
$this->_top()->_drillDown($this, 'setParentEvent');
}
/**
* Collects information about table
*
* @return void
* @access protected
*/
protected function _collectTableInfo()
{
$config = $this->Application->getUnitConfig($this->_prefix);
$this->_idField = $config->getIDField();
$this->_tableName = $config->getTableName();
$this->_foreignKey = $config->getForeignKey();
$this->_parentTableKey = $config->getParentTableKey();
$this->_constrain = $config->getConstrain('');
$this->_autoClone = $config->getAutoClone();
$this->_autoDelete = $config->getAutoDelete();
}
/**
* 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 ( !is_array($sub_items) ) {
throw new InvalidArgumentException('TempHandler: SubItems property in unit config must be an array');
}
foreach ($sub_items as $sub_item_prefix) {
$this->add(new kTempHandlerSubTable($sub_item_prefix));
}
}
/**
* 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);
return ;
}
$this->_subTables[] = $table;
$table->setParent($this);
}
/**
* Finds sub-table by prefix.
*
* @param string $prefix Unit config prefix.
*
* @return kTempHandlerSubTable
*/
public function get($prefix)
{
if ( $this->_prefix == $prefix ) {
return $this;
}
foreach ( $this->_subTables as $sub_table ) {
$found_table = $sub_table->get($prefix);
if ( $found_table !== null ) {
return $found_table;
}
}
return null;
}
/**
* Sets parent table
*
* @param kTempHandlerTable $parent
* @return void
* @access public
*/
public function setParent(kTempHandlerTable $parent)
{
$this->_parent = $parent;
if ( is_array($this->_foreignKey) ) {
$this->_multipleParents = true;
$this->_foreignKey = $this->_foreignKey[$parent->_prefix];
}
if ( is_array($this->_parentTableKey) ) {
$this->_parentTableKey = $this->_parentTableKey[$parent->_prefix];
}
$this->_setAsLastUsed();
$this->_addSubTables();
}
/**
* Returns unit prefix
*
* @return string
* @access public
*/
public function getPrefix()
{
return $this->_prefix;
}
/**
* Determines if unit used to create table exists
*
* @return bool
* @access public
*/
public function unitRegistered()
{
return $this->Application->prefixRegistred($this->_prefix);
}
/**
* Returns topmost table
*
* @return kTempHandlerTopTable
* @access protected
*/
protected function _top()
{
$top = $this;
while ( is_object($top->_parent) ) {
$top = $top->_parent;
}
return $top;
}
/**
* 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 ( $table_match && $constrain_match ) {
switch ( $operation ) {
case 'state:copied':
$this->_addState(self::STATE_COPIED);
break;
case 'state:deleted':
$this->_addState(self::STATE_DELETED);
break;
case 'setParentEvent':
$this->_parentEvent = $table->_parentEvent;
break;
case 'resetLastUsed':
$this->_lastUsage = false;
break;
}
}
foreach ($this->_subTables as $sub_table) {
$sub_table->_drillDown($table, $operation, $same_table, $same_constrain);
}
}
/**
* Marks this instance of a table as it's last usage
*
* @return void
* @access protected
*/
protected function _setAsLastUsed()
{
$this->_top()->_drillDown($this, 'resetLastUsed', true, true);
$this->_lastUsage = true;
}
/**
* Marks table and all it's clones as copied
*
* @return void
* @access protected
*/
protected function _setAsCopied()
{
$this->_top()->_drillDown($this, 'state:copied', true, true);
}
/**
* 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;
}
$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'] == $this->_prefix && $rec['ItemId'] == $temp_id ) {
// main item change log record
$changes[$key]['ItemId'] = $live_id;
}
if ( $rec['MasterPrefix'] == $this->_prefix && $rec['MasterId'] == $temp_id ) {
// sub item change log record
$changes[$key]['MasterId'] = $live_id;
}
if ( in_array($this->_prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->_prefix] == $temp_id ) {
// parent item change log record
$changes[$key]['ParentId'][$this->_prefix] = $live_id;
if ( array_key_exists('DependentFields', $rec) ) {
// these are fields from table of $rec['Prefix'] table!
// when one of dependent fields goes into idfield of it's parent item, that was changed
$config = $this->Application->getUnitConfig($rec['Prefix']);
$parent_table_key = $config->getParentTableKey($this->_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));
}
/**
* 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)
{
$single_mode = false;
if ( !is_array($live_id) ) {
$single_mode = true;
$live_id = Array ($live_id);
}
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);
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;
$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;
}
/**
* 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();
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;
}
}
else {
$this->_savedIds[$special][] = $id;
}
}
/**
* Returns saved ids for given special.
*
* @param string $special Special.
*
* @return array
*/
public function getSavedIds($special = '')
{
return isset($this->_savedIds[$special]) ? $this->_savedIds[$special] : Array ();
}
/**
* Raises event using IDs, that are currently being processed in temp handler
*
* @param string $name
* @param string $special
* @param Array $ids
* @param string $foreign_key
* @param Array $add_params
* @return bool
* @access protected
*/
protected function _raiseEvent($name, $special, $ids, $foreign_key = null, $add_params = null)
{
if ( !is_array($ids) ) {
return true;
}
$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;
}
}
/**
* Represents topmost table, that has related tables inside it
*
* Pattern: Composite
*/
class kTempHandlerTopTable extends kTempHandlerTable {
/**
* Creates table object
*
* @param string $prefix
* @param Array $ids
*/
public function __construct($prefix, $ids = Array ())
{
parent::__construct($prefix, $ids);
+ // editing sub-item, linked to multiple parents directly
+ if ( is_array($this->_foreignKey) ) {
+ $this->_multipleParents = true;
+ $this->_foreignKey = reset($this->_foreignKey);
+ }
+
+ if ( is_array($this->_parentTableKey) ) {
+ $this->_parentTableKey = reset($this->_parentTableKey);
+ }
+
$this->_setAsLastUsed();
$this->_addSubTables();
}
/**
* 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)
{
if ( !$this->Application->getUnitConfig($this->_prefix)->getCheckSimulatniousEdit() ) {
return true;
}
$tables = $this->Conn->GetCol('SHOW TABLES');
$mask_edit_table = '/' . TABLE_PREFIX . 'ses_(.*)_edit_' . $this->_tableName . '$/';
$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;
}
foreach ($tables as $table) {
if ( !preg_match($mask_edit_table, $table, $regs) ) {
continue;
}
// remove popup's wid from sid
$sid = preg_replace('/(.*)_(.*)/', '\\1', $regs[1]);
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;
}
}
$sql = 'SELECT COUNT(' . $this->_idField . ')
FROM ' . $table . '
WHERE ' . $this->_idField . ' IN (' . $ids . ')';
$found = $this->Conn->GetOne($sql);
if ( !$found || in_array($sid, $sids) ) {
continue;
}
$sids[] = $sid;
}
if ( !$sids ) {
return true;
}
// 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);
if ( $users ) {
$this->Application->SetVar('_simultaneous_edit_message', sprintf($this->Application->Phrase('la_record_being_edited_by'), implode(",\n", $users)));
return false;
}
return true;
}
/**
* Deletes records from live table
*
* @param $ids
* @return void
* @access protected
*/
protected function _deleteFromLive($ids)
{
if ( !$this->_raiseEvent('OnBeforeDeleteFromLive', '', $ids) ) {
return;
}
$sql = 'DELETE FROM ' . $this->_tableName . '
WHERE ' . $this->_idField . ' IN (' . implode(',', $ids) . ')';
$this->Conn->Query($sql);
}
}
/**
* 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!
}
/**
* 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)
{
// 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();
}
/**
* Deletes unit db records and it's sub-items by foreign key
*
* @param kDBItem $object
* @param string $special
* @param Array $original_values
* @return void
* @access public
*/
public function subDeleteItems(kDBItem $object, $special, $original_values)
{
if ( !$this->_autoDelete || !$this->_foreignKey || !$this->_parentTableKey ) {
return;
}
$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($sql);
$this->doDeleteItems($this->_prefix .'.' . $special, $sub_ids);
}
/**
* 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;
}
$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));
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));
}
$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
Log In to Comment