Page MenuHomeIn-Portal Phabricator

D413.id.diff
No OneTemporary

File Metadata

Created
Mon, Jan 6, 6:46 AM

D413.id.diff

Index: core/admin_templates/config/custom_variables.tpl
===================================================================
--- core/admin_templates/config/custom_variables.tpl
+++ core/admin_templates/config/custom_variables.tpl
@@ -81,6 +81,10 @@
- <inp2:m_RenderElement name="cf_default_value" pass_params="1"/>
</inp2:m_DefineElement>
+<inp2:m_DefineElement name="cf_EditingWindowManagerPingInterval_value">
+ <inp2:m_RenderElement name="cf_default_value" pass_params="1"/> seconds
+</inp2:m_DefineElement>
+
<inp2:m_DefineElement name="cf_HardMaintenanceTemplate_value">
<inp2:m_RenderElement name="cf_default_value" pass_params="1"/>
@@ -143,4 +147,4 @@
<inp2:m_DefineElement name="cf_AllowAdminConsoleInterfaceChange_value">
<inp2:m_RenderElement name="cf_default_value" pass_params="1"/> <label for="_cb_<inp2:InputName name='VariableValue'/>"><inp2:m_Phrase name="la_AllowChangingAdminConsoleInterface"/></label>
-</inp2:m_DefineElement>
\ No newline at end of file
+</inp2:m_DefineElement>
Index: core/admin_templates/incs/form_blocks.tpl
===================================================================
--- core/admin_templates/incs/form_blocks.tpl
+++ core/admin_templates/incs/form_blocks.tpl
@@ -41,6 +41,32 @@
</tr>
</table>
</inp2:m_if>
+
+ <inp2:m_if check="{$prefix}_TrackEditingWindow" equals_to="tracked">
+ <script type="text/javascript">
+ $(document).ready(function () {
+ function schedule_ping() {
+ var $ping_interval = Number('<inp2:m_GetConfig name="EditingWindowManagerPingInterval" js_escape="1"/>') * 1000;
+
+ setTimeout(
+ function () {
+ $.get(
+ '<inp2:m_Link pass="m,$prefix" {$prefix}_event="OnTrackEditingWindowAJAX" skip_session_refresh="1" no_amp="1" js_escape="1"/>',
+ function ($response) {
+ if ( $response === 'tracked' ) {
+ schedule_ping();
+ }
+ }
+ );
+ },
+ $ping_interval
+ );
+ }
+
+ schedule_ping();
+ });
+ </script>
+ </inp2:m_if>
</inp2:m_if>
<inp2:$prefix_ModifyUnitConfig pass_params="1"/>
Index: core/install/english.lang
===================================================================
--- core/install/english.lang
+++ core/install/english.lang
@@ -166,6 +166,7 @@
<PHRASE Label="la_config_DefaultGridPerPage" Module="Core" Type="1">RGVmYXVsdCAiUGVyIFBhZ2UiIHNldHRpbmcgaW4gR3JpZHM=</PHRASE>
<PHRASE Label="la_config_DefaultRegistrationCountry" Module="Core" Type="1">RGVmYXVsdCBSZWdpc3RyYXRpb24gQ291bnRyeQ==</PHRASE>
<PHRASE Label="la_config_DefaultTrackingCode" Module="Core" Type="1">RGVmYXVsdCBBbmFseXRpY3MgVHJhY2tpbmcgQ29kZQ==</PHRASE>
+ <PHRASE Label="la_config_EditingWindowManagerPingInterval" Module="Core" Type="1">RWRpdGluZyBXaW5kb3cgTWFuYWdlciBQaW5nIEludGVydmFs</PHRASE>
<PHRASE Label="la_config_EmailDelivery" Module="Core" Type="1">RW1haWwgRGVsaXZlcnk=</PHRASE>
<PHRASE Label="la_config_EmailLogRotationInterval" Module="Core" Type="1" Hint="VGhpcyBzZXR0aW5nIGFsbG93cyB5b3UgdG8gY29udHJvbCBmb3IgaG93IGxvbmcgIkUtbWFpbCBMb2ciIG1lc3NhZ2VzIHdpbGwgYmUgc3RvcmVkIGluIHRoZSBsb2cgYW5kIHRoZW4gYXV0b21hdGljYWxseSBkZWxldGVkLiBVc2Ugb3B0aW9uICJGb3JldmVyIiB3aXRoIGNhdXRpb24gc2luY2UgaXQgd2lsbCBjb21wbGV0ZWx5IGRpc2FibGUgYXV0b21hdGljIGxvZyBjbGVhbnVwIGFuZCBjYW4gbGVhZCB0byBsYXJnZSBzaXplIG9mIGRhdGFiYXNlIHRhYmxlIHRoYXQgc3RvcmVzIGUtbWFpbCBtZXNzYWdlcy4=">S2VlcCAiRS1tYWlsIExvZyIgZm9y</PHRASE>
<PHRASE Label="la_config_EnableEmailLog" Module="Core" Type="1" Hint="IkUtbWFpbCBMb2ciIHN0b3JlcyB0aGUgZXhhY3QgY29weSBvZiBhbGwgZW1haWxzIHRoYXQgYmVlbiBzZW50IG91dCBieSB5b3VyIHdlYnNpdGUuIFRoZSBmb2xsb3dpbmcgaW5mb3JtYXRpb24gaXMgc3RvcmVkOiBUaW1lLCBTZW5kZXIsIFJlY2lwaWVudHMsIFN1YmplY3QsIEUtbWFpbCBFdmVudCBuYW1lLCBhbmQgRW1haWwgQm9keS4=">RW5hYmxlICJFLW1haWwgTG9nIg==</PHRASE>
Index: core/install/install_data.sql
===================================================================
--- core/install/install_data.sql
+++ core/install/install_data.sql
@@ -134,6 +134,7 @@
INSERT INTO SystemSettings VALUES(DEFAULT, 'User_Default_Registration_Country', '', 'In-Portal:Users', 'in-portal:configure_users', 'la_title_General', 'la_config_DefaultRegistrationCountry', 'select', NULL, '=+||<SQL+>SELECT l%3$s_Name AS OptionName, CountryStateId AS OptionValue FROM <PREFIX>CountryStates WHERE Type = 1 ORDER BY OptionName</SQL>', 10.13, 0, 0, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'AllowSelectGroupOnFront', '0', 'In-Portal:Users', 'in-portal:configure_users', 'la_title_General', 'la_config_AllowSelectGroupOnFront', 'checkbox', NULL, NULL, 10.14, 0, 0, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'DefaultSettingsUserId', '-1', 'In-Portal:Users', 'in-portal:configure_users', 'la_title_General', 'la_prompt_DefaultUserId', 'text', NULL, NULL, 10.15, 0, 0, NULL);
+INSERT INTO SystemSettings VALUES(DEFAULT, 'EditingWindowManagerPingInterval', '30', 'In-Portal:Users', 'in-portal:configure_users', 'la_title_General', 'la_config_EditingWindowManagerPingInterval', 'text', '', 'style=\"width: 50px;\"', 10.16, 0, 1, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'u_MaxImageCount', '5', 'In-Portal:Users', 'in-portal:configure_users', 'la_section_ImageSettings', 'la_config_MaxImageCount', 'text', '', '', 30.01, 0, 0, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'u_ThumbnailImageWidth', '120', 'In-Portal:Users', 'in-portal:configure_users', 'la_section_ImageSettings', 'la_config_ThumbnailImageWidth', 'text', '', '', 30.02, 0, 0, NULL);
INSERT INTO SystemSettings VALUES(DEFAULT, 'u_ThumbnailImageHeight', '120', 'In-Portal:Users', 'in-portal:configure_users', 'la_section_ImageSettings', 'la_config_ThumbnailImageHeight', 'text', '', '', 30.03, 0, 0, NULL);
Index: core/install/upgrades.sql
===================================================================
--- core/install/upgrades.sql
+++ core/install/upgrades.sql
@@ -2956,3 +2956,4 @@
# ===== v 5.2.2-B3 =====
INSERT INTO SearchConfig VALUES ('Categories', 'PageContent', 1, 1, 'lu_fielddesc_category_PageContent', 'lc_field_PageContent', 'In-Portal', 'la_text_category', 22, DEFAULT, 1, 'text', 'MULTI:PageRevisions.PageContent', '{ForeignTable}.PageId = {LocalTable}.CategoryId AND {ForeignTable}.RevisionNumber = {LocalTable}.LiveRevisionNumber', NULL, NULL, NULL, NULL, NULL);
ALTER TABLE Semaphores CHANGE MainIDs MainIDs TEXT NULL;
+INSERT INTO SystemSettings VALUES(DEFAULT, 'EditingWindowManagerPingInterval', '30', 'In-Portal:Users', 'in-portal:configure_users', 'la_title_General', 'la_config_EditingWindowManagerPingInterval', 'text', '', 'style=\"width: 50px;\"', 10.16, 0, 1, NULL);
Index: core/kernel/db/db_event_handler.php
===================================================================
--- core/kernel/db/db_event_handler.php
+++ core/kernel/db/db_event_handler.php
@@ -167,6 +167,7 @@
'OnViewFile' => Array ('self' => true, 'subitem' => true),
'OnSaveWidths' => Array ('self' => 'admin', 'subitem' => 'admin'),
+ 'OnTrackEditingWindowAJAX' => Array ('self' => 'admin', 'subitem' => 'admin'),
'OnValidateMInputFields' => Array ('self' => 'view'),
'OnValidateField' => Array ('self' => true, 'subitem' => true),
@@ -359,6 +360,7 @@
* @param bool $from_session return ids from session (written, when editing was started)
* @return Array
* @access protected
+ * @see EditingWindowManager::getSelectedIDs
*/
protected function getSelectedIDs(kEvent $event, $from_session = false)
{
@@ -1918,6 +1920,10 @@
// all temp tables are deleted here => all after hooks should think, that it's live mode now
$this->Application->SetVar($event->Prefix . '_mode', '');
+
+ /** @var EditingWindowManager $editing_window_manager */
+ $editing_window_manager = $this->Application->recallObject('EditingWindowManager');
+ $editing_window_manager->forgetWindows(EditingWindowManager::WINDOW_TYPE_CURRENT);
}
/**
@@ -2040,6 +2046,10 @@
$this->Application->RemoveVar($changes_var_name);
$event->SetRedirectParam('opener', 'u');
+
+ /** @var EditingWindowManager $editing_window_manager */
+ $editing_window_manager = $this->Application->recallObject('EditingWindowManager');
+ $editing_window_manager->forgetWindows(EditingWindowManager::WINDOW_TYPE_CURRENT);
}
/**
@@ -3484,4 +3494,29 @@
{
$event->setEventParam('constrain_info', Array ('', ''));
}
+
+ /**
+ * Tracking an editing window.
+ *
+ * @param kEvent $event Event.
+ *
+ * @return void
+ */
+ protected function OnTrackEditingWindowAJAX(kEvent $event)
+ {
+ $event->status = kEvent::erSTOP;
+
+ $window_id = (int)$this->Application->GetVar('m_wid');
+
+ // Not a popup.
+ if ( !$window_id ) {
+ return;
+ }
+
+ /** @var EditingWindowManager $editing_window_manager */
+ $editing_window_manager = $this->Application->recallObject('EditingWindowManager');
+
+ echo $editing_window_manager->trackWindow($event->Prefix);
+ }
+
}
Index: core/kernel/db/db_tag_processor.php
===================================================================
--- core/kernel/db/db_tag_processor.php
+++ core/kernel/db/db_tag_processor.php
@@ -3101,4 +3101,28 @@
return '';
}
+
+ /**
+ * Tracks an editing window.
+ *
+ * @param array $params Tag params.
+ *
+ * @return string
+ */
+ protected function TrackEditingWindow(array $params)
+ {
+ $window_id = (int)$this->Application->GetVar('m_wid');
+
+ // Not a popup.
+ if ( !$window_id ) {
+ return 'ignore';
+ }
+
+ /** @var EditingWindowManager $editing_window_manager */
+ $editing_window_manager = $this->Application->recallObject('EditingWindowManager');
+
+ // Failure to track a window (e.g. when it's a duplicate would prevent AJAX pings as well).
+ return $editing_window_manager->trackWindow($this->Prefix);
+ }
+
}
Index: core/kernel/utility/temp_handler.php
===================================================================
--- core/kernel/utility/temp_handler.php
+++ core/kernel/utility/temp_handler.php
@@ -1018,70 +1018,44 @@
*/
function CheckSimultaniousEdit($ids = null)
{
- $tables = $this->Conn->GetCol('SHOW TABLES');
- $mask_edit_table = '/' . TABLE_PREFIX . 'ses_(.*)_edit_' . $this->MasterTable . '$/';
+ $ids = isset($ids) ? $ids : $this->Tables['IDs'];
- $my_sid = $this->Application->GetSID();
- $my_wid = $this->Application->GetVar('m_wid');
- $ids = implode(',', isset($ids) ? $ids : $this->Tables['IDs']);
- $sids = Array ();
- if (!$ids) {
+ if ( implode(',', $ids) === '' ) {
return true;
}
- foreach ($tables as $table) {
- if ( preg_match($mask_edit_table, $table, $rets) ) {
- $sid = preg_replace('/(.*)_(.*)/', '\\1', $rets[1]); // remove popup's wid from sid
- if ($sid == $my_sid) {
- if ($my_wid) {
- // using popups for editing
- if (preg_replace('/(.*)_(.*)/', '\\2', $rets[1]) == $my_wid) {
- // don't count window, that is being opened right now
- continue;
- }
- }
- else {
- // not using popups for editing -> don't count my session tables
- continue;
- }
- }
-
- $sql = 'SELECT COUNT(' . $this->Tables['IdField'] . ')
- FROM ' . $table . '
- WHERE ' . $this->Tables['IdField'] . ' IN (' . $ids . ')';
- $found = $this->Conn->GetOne($sql);
-
- if (!$found || in_array($sid, $sids)) {
- continue;
- }
+ /** @var EditingWindowManager $editing_window_manager */
+ $editing_window_manager = $this->Application->recallObject('EditingWindowManager');
+ $sids = $editing_window_manager->isDuplicateWindow($this->Tables['Prefix'], $ids);
- $sids[] = $sid;
- }
+ if ( !$sids ) {
+ return true;
}
- 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('_simultaneous_edit_message',
- sprintf($this->Application->Phrase('la_record_being_edited_by'), join(",\n", $users))
- );
+ $sids = array_unique($sids);
- return false;
- }
+ // 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 ) {
+ return true;
}
- return true;
+ $this->Application->SetVar(
+ '_simultaneous_edit_message',
+ sprintf($this->Application->Phrase('la_record_being_edited_by'), implode(",\n", $users))
+ );
+
+ return false;
}
}
Index: core/units/admin/admin_config.php
===================================================================
--- core/units/admin/admin_config.php
+++ core/units/admin/admin_config.php
@@ -32,6 +32,7 @@
'optimize_performance' => Array ('EventName' => 'OnOptimizePerformance', 'RunSchedule' => '0 0 * * *'),
'purge_expired_database_cache' => Array ('EventName' => 'OnPurgeExpiredDatabaseCacheScheduledTask', 'RunSchedule' => '0 0,12 * * *'),
'populate_url_unit_cache' => array('EventName' => 'OnPopulateUrlUnitCacheScheduledTask', 'RunSchedule' => '0 * * * *'),
+ 'forget_closed_windows' => array('EventName' => 'OnForgetClosedWindowsScheduledTask', 'RunSchedule' => '*/5 * * * *'),
),
'TitlePresets' => Array (
Index: core/units/admin/admin_events_handler.php
===================================================================
--- core/units/admin/admin_events_handler.php
+++ core/units/admin/admin_events_handler.php
@@ -753,21 +753,27 @@
*/
protected function OnDropTempTablesByWID(kEvent $event)
{
- $sid = $this->Application->GetSID();
- $wid = $this->Application->GetVar('m_wid');
- $tables = $this->Conn->GetCol('SHOW TABLES');
- $mask_edit_table = '/' . TABLE_PREFIX . 'ses_' . $sid . '_' . $wid . '_edit_(.*)$/';
-
- foreach ($tables as $table) {
- if ( preg_match($mask_edit_table, $table, $rets) ) {
- $this->Conn->Query('DROP TABLE IF EXISTS ' . $table);
- }
- }
-
echo 'OK';
$event->status = kEvent::erSTOP;
+
+ /** @var EditingWindowManager $live_editing_tracker */
+ $live_editing_tracker = $this->Application->recallObject('EditingWindowManager');
+ $live_editing_tracker->forgetWindows(EditingWindowManager::WINDOW_TYPE_CURRENT);
}
+ /**
+ * Forgets closed windows.
+ *
+ * @param kEvent $event Event.
+ *
+ * @return void
+ */
+ protected function OnForgetClosedWindowsScheduledTask(kEvent $event)
+ {
+ /** @var EditingWindowManager $live_editing_tracker */
+ $live_editing_tracker = $this->Application->recallObject('EditingWindowManager');
+ $live_editing_tracker->forgetWindows(EditingWindowManager::WINDOW_TYPE_CLOSED);
+ }
/**
* Backup all data
Index: core/units/helpers/EditingWindowManager.php
===================================================================
--- /dev/null
+++ core/units/helpers/EditingWindowManager.php
@@ -0,0 +1,326 @@
+<?php
+
+
+class EditingWindowManager extends kHelper
+{
+
+ const WINDOW_TYPE_CURRENT = 1;
+
+ const WINDOW_TYPE_CLOSED = 2;
+
+ const IDS_STORAGE_SIZE = 510;
+
+ /**
+ * Table name.
+ *
+ * @var string
+ */
+ protected $tableName = '';
+
+ /**
+ * Ping interval (in seconds).
+ *
+ * @var integer
+ */
+ protected $pingInterval = 30;
+
+ /**
+ * EditingWindowManager constructor.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+
+ $this->tableName = TABLE_PREFIX . 'EditingWindowManager';
+ $this->pingInterval = $this->Application->ConfigValue('EditingWindowManagerPingInterval');
+
+ $this->ensureStorage();
+ }
+
+ /**
+ * Ensures, that storage is available.
+ *
+ * @return void
+ */
+ protected function ensureStorage()
+ {
+ $sql = 'SHOW TABLES LIKE ' . $this->Conn->qstr($this->tableName);
+
+ if ( $this->Conn->GetCol($sql) ) {
+ return;
+ }
+
+ $sql = 'CREATE TABLE ' . $this->tableName . ' (
+ `Id` int(11) NOT NULL AUTO_INCREMENT,
+ `SessionKey` int(10) unsigned DEFAULT NULL,
+ `WindowId` int(11) unsigned DEFAULT NULL,
+ `UserId` int(11) DEFAULT NULL,
+ `ItemPrefix` VARCHAR(255) NOT NULL DEFAULT "",
+ `ItemIds` VARCHAR(' . self::IDS_STORAGE_SIZE . ') NOT NULL DEFAULT "",
+ `LastPingOn` int(11) unsigned DEFAULT NULL,
+ PRIMARY KEY (`Id`),
+ UNIQUE KEY `IDX_INTEGRITY` (`SessionKey`,`WindowId`,`UserId`,`ItemPrefix`,`ItemIds`),
+ KEY `IDX_SEARCH` (`ItemPrefix`,`ItemIds`,`LastPingOn`),
+ KEY `IDX_DELETE` (`LastPingOn`)
+ ) ENGINE = MEMORY';
+ $this->Conn->Query($sql);
+ }
+
+ /**
+ * Returns condition for closed window detection.
+ *
+ * @return string
+ */
+ protected function getClosedWindowWhereClause()
+ {
+ return 'LastPingOn < ' . strtotime('-' . (2 * $this->pingInterval) . ' seconds');
+ }
+
+ /**
+ * Forgets closed windows.
+ *
+ * @param integer $window_type Window type.
+ *
+ * @return void
+ * @throws InvalidArgumentException When unsupported $window_type is given.
+ */
+ public function forgetWindows($window_type)
+ {
+ if ( $window_type === self::WINDOW_TYPE_CURRENT ) {
+ $windows = $this->getCurrentWindows();
+ }
+ elseif ( $window_type === self::WINDOW_TYPE_CLOSED ) {
+ $windows = $this->getClosedWindows();
+ }
+ else {
+ throw new InvalidArgumentException('The "' . $window_type . '" window type isn\'t supported.');
+ }
+
+ if ( !$windows ) {
+ return;
+ }
+
+ // Sample table name: "inp_ses_806561653_1_edit_inp_TableName".
+ $regexp = $this->getDatabaseTableRegExp($windows);
+ $editing_tables = $this->Conn->GetCol('SHOW TABLES LIKE "' . TABLE_PREFIX . 'ses_%"');
+
+ foreach ( $editing_tables as $table ) {
+ if ( !preg_match($regexp, $table) ) {
+ continue;
+ }
+
+ $this->Conn->Query('DROP TABLE IF EXISTS ' . $table);
+ }
+
+ $sql = 'DELETE FROM ' . $this->tableName . '
+ WHERE Id IN (' . implode(',', array_keys($windows)) . ')';
+ $this->Conn->Query($sql);
+ }
+
+ /**
+ * Returns current windows.
+ *
+ * @return array
+ */
+ protected function getCurrentWindows()
+ {
+ // Using real (not topmost) Window ID guarantees, that sub-item popup close won't cause any harm.
+ $where_clause = array(
+ 'SessionKey = ' . $this->Application->GetSID(),
+ 'WindowId = ' . $this->Application->GetVar('m_wid'),
+ );
+
+ $sql = 'SELECT SessionKey, WindowId, Id
+ FROM ' . $this->tableName . '
+ WHERE (' . implode(') AND (', $where_clause) . ')';
+
+ return $this->Conn->Query($sql, 'Id');
+ }
+
+ /**
+ * Returns closed window information.
+ *
+ * @return array
+ */
+ protected function getClosedWindows()
+ {
+ $sql = 'SELECT SessionKey, WindowId, Id
+ FROM ' . $this->tableName . '
+ WHERE ' . $this->getClosedWindowWhereClause();
+
+ return $this->Conn->Query($sql, 'Id');
+ }
+
+ /**
+ * Returns regular expression for locating window-based database tables.
+ *
+ * @param array $windows Windows.
+ *
+ * @return string
+ */
+ protected function getDatabaseTableRegExp(array $windows)
+ {
+ $parts = array();
+
+ foreach ( $windows as $window_data ) {
+ $parts[] = $window_data['SessionKey'] . '_' . $window_data['WindowId'];
+ }
+
+ return '/^' . TABLE_PREFIX . 'ses_(' . implode('|', $parts) . ')_edit_.*$/';
+ }
+
+ /**
+ * Checks if it's a duplicate window.
+ *
+ * @param string $prefix Prefix.
+ * @param array $ids IDs.
+ *
+ * @return integer[]
+ */
+ public function isDuplicateWindow($prefix, array $ids)
+ {
+ $ids_where_clause = array();
+
+ foreach ( $ids as $id ) {
+ $ids_where_clause[] = 'ItemIds LIKE ' . $this->Conn->qstr('%|' . $id . '|%');
+ }
+
+ $where_clause = array(
+ 'ItemPrefix = ' . $this->Conn->qstr($prefix),
+ '(' . implode(') OR (', $ids_where_clause) . ')',
+ '!(' . $this->getClosedWindowWhereClause() . ')',
+ );
+
+ $sql = 'SELECT *
+ FROM ' . $this->tableName . '
+ WHERE (' . implode(') AND (', $where_clause) . ')';
+ $windows = $this->Conn->Query($sql);
+
+ // Record isn't being edited.
+ if ( !$windows ) {
+ return array();
+ }
+
+ $sids = array();
+
+ foreach ( $windows as $window_data ) {
+ // Record being edited by another user.
+ if ( $window_data['UserId'] != $this->Application->RecallVar('user_id') ) {
+ $sids[] = $window_data['SessionKey'];
+ }
+
+ // Record being edited by current user, but in another window.
+ if ( $window_data['WindowId'] != $this->Application->GetTopmostWid($prefix) ) {
+ $sids[] = $window_data['SessionKey'];
+ }
+ }
+
+ return array_unique($sids);
+ }
+
+ /**
+ * Track new/existing editing window.
+ *
+ * @param string $prefix Prefix.
+ * @param array $ids IDs.
+ *
+ * @return string
+ */
+ public function trackWindow($prefix, array $ids = array())
+ {
+ // Only track editing windows using temp tables.
+ if ( !$this->Application->IsTempMode($prefix) ) {
+ return 'ignore';
+ }
+
+ if ( !$ids ) {
+ $ids = $this->getSelectedIDs($prefix);
+ }
+
+ $ids_storage = $this->prepareItemIdsForStorage($ids);
+
+ $where_clause = array(
+ 'SessionKey = ' . $this->Application->GetSID(),
+ 'WindowId = ' . $this->Application->GetTopmostWid($prefix),
+ 'UserId = ' . $this->Application->RecallVar('user_id'),
+ 'ItemPrefix = ' . $this->Conn->qstr($prefix),
+ 'ItemIds = ' . $this->Conn->qstr($ids_storage),
+ );
+
+ $sql = 'SELECT Id
+ FROM ' . $this->tableName . '
+ WHERE (' . implode(') AND (', $where_clause) . ')';
+ $tacking_id = $this->Conn->GetOne($sql);
+
+ if ( $tacking_id === false ) {
+ // Add the new window.
+ $this->Conn->doInsert(
+ array(
+ 'SessionKey' => $this->Application->GetSID(),
+ 'WindowId' => $this->Application->GetTopmostWid($prefix),
+ 'UserId' => $this->Application->RecallVar('user_id'),
+ 'ItemPrefix' => $prefix,
+ 'ItemIds' => $ids_storage,
+ 'LastPingOn' => time(),
+ ),
+ $this->tableName
+ );
+ }
+ else {
+ // Extend lifetime of an existing window.
+ $this->Conn->doUpdate(array('LastPingOn' => time()), $this->tableName, 'Id = ' . $tacking_id);
+ }
+
+ return 'tracked';
+ }
+
+ /**
+ * Prepares Item IDs for stage.
+ *
+ * @param array $ids IDs.
+ *
+ * @return string
+ */
+ protected function prepareItemIdsForStorage(array $ids)
+ {
+ $ret = '|' . implode('|', $ids) . '|';
+
+ // Already has correct size.
+ if ( strlen($ret) < self::IDS_STORAGE_SIZE ) {
+ return $ret;
+ }
+
+ $ret = substr($ret, 0, self::IDS_STORAGE_SIZE);
+
+ // Cut exactly to the ID.
+ if ( substr($ret, -1) === '|' ) {
+ return $ret;
+ }
+
+ // Cut in the middle of the ID.
+ return substr($ret, 0, strrpos($ret, '|') + 1);
+ }
+
+ /**
+ * Returns stored selected ids as an array
+ *
+ * @param string $prefix Prefix.
+ *
+ * @return array
+ * @see kDBEventHandler::getSelectedIDs
+ */
+ protected function getSelectedIDs($prefix)
+ {
+ $wid = $this->Application->GetTopmostWid($prefix);
+ $var_name = rtrim($prefix . '_selected_ids_' . $wid, '_');
+ $ret = $this->Application->RecallVar($var_name);
+
+ // Adding new record.
+ if ( $ret === false ) {
+ return array(0);
+ }
+
+ return explode(',', $ret);
+ }
+
+}
Index: core/units/helpers/helpers_config.php
===================================================================
--- core/units/helpers/helpers_config.php
+++ core/units/helpers/helpers_config.php
@@ -78,5 +78,6 @@
Array ('pseudo' => 'AjaxFormHelper', 'class' => 'AjaxFormHelper', 'file' => 'ajax_form_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kCronHelper', 'class' => 'kCronHelper', 'file' => 'cron_helper.php', 'build_event' => ''),
Array ('pseudo' => 'kUploadHelper', 'class' => 'kUploadHelper', 'file' => 'upload_helper.php', 'build_event' => ''),
+ Array ('pseudo' => 'EditingWindowManager', 'class' => 'EditingWindowManager', 'file' => 'EditingWindowManager.php', 'build_event' => ''),
),
);

Event Timeline