Index: branches/5.2.x/core/kernel/db/db_event_handler.php
===================================================================
--- branches/5.2.x/core/kernel/db/db_event_handler.php
+++ branches/5.2.x/core/kernel/db/db_event_handler.php
@@ -1909,8 +1909,9 @@
 
 			$object->setPendingActions(null, true);
 
-			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-			$this->Application->RemoveVar($changes_var_name);
+			/** @var ChangeLogHelper $change_log_helper */
+			$change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+			$change_log_helper->forgetChanges($this->Prefix);
 
 			/** @var kTempTablesHandler $temp_handler */
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
@@ -1949,8 +1950,6 @@
 			/** @var kTempTablesHandler $temp_handler */
 			$temp_handler = $this->Application->recallObject($event->getPrefixSpecial() . '_TempHandler', 'kTempTablesHandler', Array ('parent_event' => $event));
 
-			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-
 			if ( !$this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) {
 				$live_ids = $temp_handler->SaveEdit($event->getEventParam('master_ids') ? $event->getEventParam('master_ids') : Array ());
 
@@ -1969,7 +1968,9 @@
 				/** @var kDBItem $object */
 				$object = $event->getObject();
 
-				$this->SaveLoggedChanges($changes_var_name, $object->ShouldLogChanges());
+				/** @var ChangeLogHelper $change_log_helper */
+				$change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+				$change_log_helper->saveLoggedChanges($this->Prefix, $object->ShouldLogChanges());
 			}
 			else {
 				$event->status = kEvent::erFAIL;
@@ -1985,101 +1986,22 @@
 		}
 
 		/**
-		 * Saves changes made in temporary table to log
+		 * Saves changes made in the temporary table to log.
 		 *
-		 * @param string $changes_var_name
-		 * @param bool $save
-		 * @return void
-		 * @access public
+		 * @param string  $changes_var_name Changes var name.
+		 * @param boolean $save             Save changes.
+		 *
+		 * @return     void
+		 * @deprecated 5.2.2-B3
+		 * @see        ChangeLogHelper::saveLoggedChanges()
 		 */
 		public function SaveLoggedChanges($changes_var_name, $save = true)
 		{
-			// 1. get changes, that were made
-			$changes = $this->Application->RecallVar($changes_var_name);
-			$changes = $changes ? unserialize($changes) : Array ();
-			$this->Application->RemoveVar($changes_var_name);
-
-			if (!$changes) {
-				// no changes, skip processing
-				return ;
-			}
-
-			// TODO: 2. optimize change log records (replace multiple changes to same record with one change record)
-
-			$to_increment = Array ();
-
-			// 3. collect serials to reset based on foreign keys
-			foreach ($changes as $index => $rec) {
-				if (array_key_exists('DependentFields', $rec)) {
-
-					foreach ($rec['DependentFields'] as $field_name => $field_value) {
-						// will be "ci|ItemResourceId:345"
-						$to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value;
-
-						// also reset sub-item prefix general serial
-						$to_increment[] = $rec['Prefix'];
-					}
-
-					unset($changes[$index]['DependentFields']);
-				}
-
-				unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']);
-			}
-
-			// 4. collect serials to reset based on changed ids
-			foreach ($changes as $change) {
-				$to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId'];
-
-				if ($change['MasterPrefix'] != $change['Prefix']) {
-					// also reset sub-item prefix general serial
-					$to_increment[] = $change['Prefix'];
-
-					// will be "ci|ItemResourceId"
-					$to_increment[] = $change['Prefix'] . '|' . $change['ItemId'];
-				}
-			}
-
-			// 5. reset serials collected before
-			$to_increment = array_unique($to_increment);
-			$this->Application->incrementCacheSerial($this->Prefix);
-
-			foreach ($to_increment as $to_increment_mixed) {
-				if (strpos($to_increment_mixed, '|') !== false) {
-					list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2);
-					$this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id);
-				}
-				else {
-					$this->Application->incrementCacheSerial($to_increment_mixed);
-				}
-			}
-
-			// save changes to database
-			$sesion_log_id = $this->Application->RecallVar('_SessionLogId_');
-
-			if (!$save || !$sesion_log_id) {
-				// saving changes to database disabled OR related session log missing
-				return ;
-			}
-
-			$add_fields = Array (
-				'PortalUserId' => $this->Application->RecallVar('user_id'),
-				'SessionLogId' => $sesion_log_id,
-			);
-
-			$change_log_table = $this->Application->getUnitOption('change-log', 'TableName');
-
-			foreach ($changes as $rec) {
-				$this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table);
-			}
-
-			$this->Application->incrementCacheSerial('change-log');
-
-			$sql = 'UPDATE ' . $this->Application->getUnitOption('session-log', 'TableName') . '
-					SET AffectedItems = AffectedItems + ' . count($changes) . '
-					WHERE SessionLogId = ' . $sesion_log_id;
-			$this->Conn->Query($sql);
+			kUtil::deprecatedMethod(__METHOD__, '5.2.2-B3', 'ChangeLogHelper::saveLoggedChanges');
 
-			$this->Application->incrementCacheSerial('session-log');
+			/** @var ChangeLogHelper $change_log_helper */
+			$change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+			$change_log_helper->saveLoggedChanges($this->Prefix, $save);
 		}
 
 		/**
@@ -2100,8 +2022,9 @@
 
 			$this->Application->RemoveVar($event->getPrefixSpecial() . '_modified');
 
-			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-			$this->Application->RemoveVar($changes_var_name);
+			/** @var ChangeLogHelper $change_log_helper */
+			$change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+			$change_log_helper->forgetChanges($this->Prefix);
 
 			$event->SetRedirectParam('opener', 'u');
 		}
@@ -2271,8 +2194,9 @@
 			$this->Application->SetVar($event->getPrefixSpecial() . '_id', 0);
 			$this->Application->SetVar($event->getPrefixSpecial() . '_PreCreate', 1);
 
-			$changes_var_name = $this->Prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-			$this->Application->RemoveVar($changes_var_name);
+			/** @var ChangeLogHelper $change_log_helper */
+			$change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+			$change_log_helper->forgetChanges($this->Prefix);
 
 			$event->redirect = false;
 		}
Index: branches/5.2.x/core/kernel/db/dbitem.php
===================================================================
--- branches/5.2.x/core/kernel/db/dbitem.php
+++ branches/5.2.x/core/kernel/db/dbitem.php
@@ -1147,46 +1147,15 @@
 
 		$this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID());
 
-		if ($this->ShouldLogChanges(true)) {
-			// Updating TempId in ChangesLog, if changes are disabled
-			$ses_var_name = $this->Application->GetTopmostPrefix($this->Prefix) . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-			$changes = $this->Application->RecallVar($ses_var_name);
-			$changes = $changes ? unserialize($changes) : Array ();
-
-			if ($changes) {
-				foreach ($changes as $key => $rec) {
-					if ($rec['Prefix'] == $this->Prefix && $rec['ItemId'] == $this->GetID()) {
-						// change log for record, that's ID was just updated -> update in change log record too
-						$changes[$key]['ItemId'] = $new_id;
-					}
-
-					if ($rec['MasterPrefix'] == $this->Prefix && $rec['MasterId'] == $this->GetID()) {
-						// master item id was changed
-						$changes[$key]['MasterId'] = $new_id;
-					}
-
-					if (in_array($this->Prefix, $rec['ParentPrefix']) && $rec['ParentId'][$this->Prefix] == $this->GetID()) {
-						// change log record of given item's sub item -> update changed id's in dependent fields
-						$changes[$key]['ParentId'][$this->Prefix] = $new_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
-							$parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
-							$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$this->Prefix] : $parent_table_key;
-
-							if ($parent_table_key == $this->IDField) {
-								$foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
-								$foreign_key = is_array($foreign_key) ? $foreign_key[$this->Prefix] : $foreign_key;
-
-								$changes[$key]['DependentFields'][$foreign_key] = $new_id;
-							}
-						}
-					}
-				}
-			}
-
-			$this->Application->StoreVar($ses_var_name, serialize($changes));
+		if ( $this->ShouldLogChanges(true) ) {
+			/** @var ChangeLogHelper $change_log_helper */
+			$change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+			$change_log_helper->updateForeignKeys(
+				array('Prefix' => $this->Prefix, 'IdField' => $this->IDField),
+				$new_id,
+				$this->GetID(),
+				$this->Application->GetTopmostPrefix($this->Prefix)
+			);
 		}
 
 		$old_id = $this->GetID();
@@ -1214,14 +1183,12 @@
 		$this->Application->StoreVar($main_prefix . '_modified', '1', true); // true for optional
 
 		if ( $this->ShouldLogChanges(true) ) {
-			$this->LogChanges($main_prefix, $mode, $update_fields);
-
-			if (!$this->IsTempTable()) {
-				/** @var kDBEventHandler $handler */
-				$handler = $this->Application->recallObject($this->Prefix . '_EventHandler');
+			/** @var ChangeLogHelper $change_log_helper */
+			$change_log_helper = $this->Application->recallObject('ChangeLogHelper');
+			$change_log_helper->logChanges($this, $mode, $update_fields);
 
-				$ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-				$handler->SaveLoggedChanges($ses_var_name, $this->ShouldLogChanges());
+			if ( !$this->IsTempTable() ) {
+				$change_log_helper->saveLoggedChanges($this->Prefix, $this->ShouldLogChanges());
 			}
 		}
 	}
@@ -1242,97 +1209,6 @@
 		return $log_changes && !$this->Application->getUnitOption($this->Prefix, 'ForceDontLogChanges');
 	}
 
-	protected function LogChanges($main_prefix, $mode, $update_fields = null)
-	{
-		if ( !$mode ) {
-			return ;
-		}
-
-		$ses_var_name = $main_prefix . '_changes_' . $this->Application->GetTopmostWid($this->Prefix);
-		$changes = $this->Application->RecallVar($ses_var_name);
-		$changes = $changes ? unserialize($changes) : Array ();
-
-		$fields_hash = Array (
-			'Prefix' => $this->Prefix,
-			'ItemId' => $this->GetID(),
-			'OccuredOn' => adodb_mktime(),
-			'MasterPrefix' => $main_prefix,
-			'Action' => $mode,
-		);
-
-		if ( $this->Prefix == $main_prefix ) {
-			// main item
-			$fields_hash['MasterId'] = $this->GetID();
-			$fields_hash['ParentPrefix'] = Array ($main_prefix);
-			$fields_hash['ParentId'] = Array ($main_prefix => $this->GetID());
-		}
-		else {
-			// sub item
-			// collect foreign key values (for serial reset)
-			$foreign_keys = $this->Application->getUnitOption($this->Prefix, 'ForeignKey', Array ());
-			$dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = Array ();
-			/** @var Array $foreign_keys */
-
-			if ( is_array($foreign_keys) ) {
-				foreach ($foreign_keys as $prefix => $field_name) {
-					$dependent_fields[$field_name] = $this->GetDBField($field_name);
-					$fields_hash['ParentPrefix'][] = $prefix;
-					$fields_hash['ParentId'][$prefix] = $this->getParentId($prefix);
-				}
-			}
-			else {
-				$dependent_fields[$foreign_keys] = $this->GetDBField($foreign_keys);
-				$fields_hash['ParentPrefix'] = Array ( $this->Application->getUnitOption($this->Prefix, 'ParentPrefix') );
-				$fields_hash['ParentId'][ $fields_hash['ParentPrefix'][0] ] = $this->getParentId('auto');
-			}
-
-			$fields_hash['DependentFields'] = $dependent_fields;
-
-
-			// works only, when main item is present in url, when sub-item is changed
-			$master_id = $this->Application->GetVar($main_prefix . '_id');
-
-			if ( $master_id === false ) {
-				// works in case of we are not editing topmost item, when sub-item is created/updated/deleted
-				$master_id = $this->getParentId('auto', true);
-			}
-
-			$fields_hash['MasterId'] = $master_id;
-		}
-
-		switch ( $mode ) {
-			case ChangeLog::UPDATE:
-				$changed_fields = $this->GetChangedFields();
-
-				if ( $update_fields ) {
-					$changed_fields = array_intersect_key(
-						$changed_fields,
-						array_combine($update_fields, $update_fields)
-					);
-				}
-
-				$to_save = array_merge($this->GetTitleField(), $changed_fields);
-				break;
-
-			case ChangeLog::CREATE:
-				$to_save = $this->GetTitleField();
-				break;
-
-			case ChangeLog::DELETE:
-				$to_save = array_merge($this->GetTitleField(), $this->GetRealFields());
-				break;
-
-			default:
-				$to_save = Array ();
-				break;
-		}
-
-		$fields_hash['Changes'] = serialize($to_save);
-		$changes[] = $fields_hash;
-
-		$this->Application->StoreVar($ses_var_name, serialize($changes));
-	}
-
 	/**
 	 * Returns current item parent's ID
 	 *
Index: branches/5.2.x/core/kernel/utility/temp_handler.php
===================================================================
--- branches/5.2.x/core/kernel/utility/temp_handler.php
+++ branches/5.2.x/core/kernel/utility/temp_handler.php
@@ -74,6 +74,23 @@
 	protected $parentEvent = null;
 
 	/**
+	 * Change Log helper.
+	 *
+	 * @var $changeLogHelper
+	 */
+	protected $changeLogHelper;
+
+	/**
+	 * Creates class instance.
+	 */
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->changeLogHelper = $this->Application->recallObject('ChangeLogHelper');
+	}
+
+	/**
 	 * Sets new parent event to the object
 	 *
 	 * @param kEvent $event
@@ -697,7 +714,7 @@
 									WHERE ' . $master['IdField'] . ' = 0';
 						$this->Conn->Query($query);
 
-						$this->UpdateChangeLogForeignKeys($master, $live_ids[$an_id], $an_id);
+						$this->changeLogHelper->updateForeignKeys($master, $live_ids[$an_id], $an_id, $this->Prefix);
 					}
 				}
 
@@ -783,55 +800,9 @@
 		return $connection;
 	}
 
-	function UpdateChangeLogForeignKeys($master, $live_id, $temp_id)
-	{
-		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);
-		$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) {
-				// main item change log record
-				$changes[$key]['ItemId'] = $live_id;
-			}
-
-			if ($rec['MasterPrefix'] == $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) {
-				// parent item change log record
-				$changes[$key]['ParentId'][$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
-					$parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
-					$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$prefix] : $parent_table_key;
-
-					if ($parent_table_key == $master['IdField']) {
-						$foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
-						$foreign_key = is_array($foreign_key) ? $foreign_key[$prefix] : $foreign_key;
-
-						$changes[$key]['DependentFields'][$foreign_key] = $live_id;
-					}
-				}
-			}
-		}
-
-		$this->Application->StoreVar($ses_var_name, serialize($changes));
-	}
-
 	function UpdateForeignKeys($master, $live_id, $temp_id)
 	{
-		$this->UpdateChangeLogForeignKeys($master, $live_id, $temp_id);
+		$this->changeLogHelper->updateForeignKeys($master, $live_id, $temp_id, $this->Prefix);
 
 		foreach ($master['SubTables'] as $sub_table) {
 			$foreign_key_field =  is_array($sub_table['ForeignKey']) ? getArrayValue($sub_table, 'ForeignKey', $master['Prefix']) : $sub_table['ForeignKey'];
Index: branches/5.2.x/core/units/helpers/ChangeLogHelper.php
===================================================================
--- branches/5.2.x/core/units/helpers/ChangeLogHelper.php
+++ branches/5.2.x/core/units/helpers/ChangeLogHelper.php
@@ -0,0 +1,316 @@
+<?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.
+*/
+
+class ChangeLogHelper extends kHelper
+{
+
+	/**
+	 * Forgets changes.
+	 *
+	 * @param string $main_prefix Main prefix.
+	 *
+	 * @return void
+	 */
+	public function forgetChanges($main_prefix)
+	{
+		$ses_var_name = $this->getSessionVariableName($main_prefix, $main_prefix);
+		$this->Application->RemoveVar($ses_var_name);
+	}
+
+	/**
+	 * Updates the foreign keys in the changelog for a given table based on the provided identifiers.
+	 *
+	 * This method ensures that all references to the temporary ID in the changelog are updated to use
+	 * the live ID, while considering dependencies and relationships between items.
+	 *
+	 * @param array   $table_info    Table info.
+	 * @param integer $live_id       Live ID.
+	 * @param integer $temp_id       Temp ID.
+	 * @param string  $caller_prefix Caller prefix.
+	 *
+	 * @return void
+	 */
+	public function updateForeignKeys(array $table_info, $live_id, $temp_id, $caller_prefix)
+	{
+		if ( $live_id == $temp_id ) {
+			return;
+		}
+
+		$prefix = $table_info['Prefix'];
+		$main_prefix = $this->Application->GetTopmostPrefix($prefix);
+
+		$ses_var_name = $this->getSessionVariableName($main_prefix, $caller_prefix);
+		$changes = $this->Application->RecallVar($ses_var_name);
+		$changes = $changes ? unserialize($changes) : array();
+
+		if ( !$changes ) {
+			return;
+		}
+
+		foreach ( $changes as $key => $rec ) {
+			// The main item changelog record.
+			if ( $rec['Prefix'] == $prefix && $rec['ItemId'] == $temp_id ) {
+				$changes[$key]['ItemId'] = $live_id;
+			}
+
+			// The subitem changelog record.
+			if ( $rec['MasterPrefix'] == $prefix && $rec['MasterId'] == $temp_id ) {
+				$changes[$key]['MasterId'] = $live_id;
+			}
+
+			// The parent item changelog record.
+			if ( in_array($prefix, $rec['ParentPrefix']) && $rec['ParentId'][$prefix] == $temp_id ) {
+				$changes[$key]['ParentId'][$prefix] = $live_id;
+
+				if ( array_key_exists('DependentFields', $rec) ) {
+					/*
+					 * These are fields from the $rec['Prefix'] table!
+					 * Used, when one of the dependent fields uses IdField of its changed parent item.
+					 */
+					$parent_table_key = $this->Application->getUnitOption($rec['Prefix'], 'ParentTableKey');
+					$parent_table_key = is_array($parent_table_key) ? $parent_table_key[$prefix] : $parent_table_key;
+
+					if ( $parent_table_key == $table_info['IdField'] ) {
+						$foreign_key = $this->Application->getUnitOption($rec['Prefix'], 'ForeignKey');
+						$foreign_key = is_array($foreign_key) ? $foreign_key[$prefix] : $foreign_key;
+
+						$changes[$key]['DependentFields'][$foreign_key] = $live_id;
+					}
+				}
+			}
+		}
+
+		$this->Application->StoreVar($ses_var_name, serialize($changes));
+	}
+
+	/**
+	 * Logs changes for a database item, tracking relevant details for create, update, or delete actions.
+	 *
+	 * @param kDBItem    $object        Object.
+	 * @param integer    $mode          Mode (e.g., create, update, delete).
+	 * @param array|null $update_fields Update fields.
+	 *
+	 * @return void
+	 */
+	public function logChanges(kDBItem $object, $mode, array $update_fields = null)
+	{
+		if ( !$mode ) {
+			return;
+		}
+
+		$prefix = $object->Prefix;
+		$main_prefix = $this->Application->GetTopmostPrefix($prefix);
+
+		$ses_var_name = $this->getSessionVariableName($main_prefix, $prefix);
+		$changes = $this->Application->RecallVar($ses_var_name);
+		$changes = $changes ? unserialize($changes) : array();
+
+		$fields_hash = array(
+			'Prefix' => $prefix,
+			'ItemId' => $object->GetID(),
+			'OccuredOn' => adodb_mktime(),
+			'MasterPrefix' => $main_prefix,
+			'Action' => $mode,
+		);
+
+		if ( $prefix == $main_prefix ) {
+			// Main item.
+			$fields_hash['MasterId'] = $object->GetID();
+			$fields_hash['ParentPrefix'] = array($main_prefix);
+			$fields_hash['ParentId'] = array($main_prefix => $object->GetID());
+		}
+		else {
+			/*
+			 * Subitem.
+			 * Collect foreign key values (for serial reset).
+			 */
+			$foreign_keys = $this->Application->getUnitOption($prefix, 'ForeignKey', array());
+			$dependent_fields = $fields_hash['ParentId'] = $fields_hash['ParentPrefix'] = array();
+			/** @var Array $foreign_keys */
+
+			if ( is_array($foreign_keys) ) {
+				foreach ( $foreign_keys as $foreign_key_prefix => $foreign_key_field_name ) {
+					$dependent_fields[$foreign_key_field_name] = $object->GetDBField($foreign_key_field_name);
+					$fields_hash['ParentPrefix'][] = $foreign_key_prefix;
+					$fields_hash['ParentId'][$foreign_key_prefix] = $object->getParentId($foreign_key_prefix);
+				}
+			}
+			else {
+				$dependent_fields[$foreign_keys] = $object->GetDBField($foreign_keys);
+				$fields_hash['ParentPrefix'] = array(
+					$this->Application->getUnitOption($prefix, 'ParentPrefix'),
+				);
+				$fields_hash['ParentId'][$fields_hash['ParentPrefix'][0]] = $object->getParentId('auto');
+			}
+
+			$fields_hash['DependentFields'] = $dependent_fields;
+
+			// Works only when the main item is present in url when a subitem is changed.
+			$master_id = $this->Application->GetVar($main_prefix . '_id');
+
+			if ( $master_id === false ) {
+				// Works in case when we're not editing topmost item, when subitem is created/updated/deleted.
+				$master_id = $object->getParentId('auto', true);
+			}
+
+			$fields_hash['MasterId'] = $master_id;
+		}
+
+		switch ( $mode ) {
+			case ChangeLog::UPDATE:
+				$changed_fields = $object->GetChangedFields();
+
+				if ( $update_fields ) {
+					$changed_fields = array_intersect_key(
+						$changed_fields,
+						array_combine($update_fields, $update_fields)
+					);
+				}
+
+				$to_save = array_merge($object->GetTitleField(), $changed_fields);
+				break;
+
+			case ChangeLog::CREATE:
+				$to_save = $object->GetTitleField();
+				break;
+
+			case ChangeLog::DELETE:
+				$to_save = array_merge($object->GetTitleField(), $object->GetRealFields());
+				break;
+
+			default:
+				$to_save = array();
+				break;
+		}
+
+		$fields_hash['Changes'] = serialize($to_save);
+		$changes[] = $fields_hash;
+
+		$this->Application->StoreVar($ses_var_name, serialize($changes));
+	}
+
+	/**
+	 * Saves changes made in the temporary table to log.
+	 *
+	 * @param string  $prefix Prefix.
+	 * @param boolean $save   Save changes.
+	 *
+	 * @return void
+	 */
+	public function saveLoggedChanges($prefix, $save = true)
+	{
+		$main_prefix = $this->Application->GetTopmostPrefix($prefix);
+
+		// 1. get changes that were made.
+		$ses_var_name = $this->getSessionVariableName($main_prefix, $prefix);
+		$changes = $this->Application->RecallVar($ses_var_name);
+		$changes = $changes ? unserialize($changes) : array();
+		$this->Application->RemoveVar($ses_var_name);
+
+		if ( !$changes ) {
+			// No changes, skip processing.
+			return;
+		}
+
+		// TODO: 2. optimize change log records (replace multiple changes to same record with one change record).
+		$to_increment = array();
+
+		// 3. collect serials to reset based on foreign keys
+		foreach ( $changes as $index => $rec ) {
+			if ( array_key_exists('DependentFields', $rec) ) {
+				foreach ( $rec['DependentFields'] as $field_name => $field_value ) {
+					// Will be "ci|ItemResourceId:345".
+					$to_increment[] = $rec['Prefix'] . '|' . $field_name . ':' . $field_value;
+
+					// Also reset sub-item prefix general serial.
+					$to_increment[] = $rec['Prefix'];
+				}
+
+				unset($changes[$index]['DependentFields']);
+			}
+
+			// Remove keys that don't have corresponding columns in the "ChangeLogs" database table.
+			unset($changes[$index]['ParentId'], $changes[$index]['ParentPrefix']);
+		}
+
+		// 4. collect serials to reset based on changed ids
+		foreach ( $changes as $change ) {
+			$to_increment[] = $change['MasterPrefix'] . '|' . $change['MasterId'];
+
+			if ( $change['MasterPrefix'] != $change['Prefix'] ) {
+				// Also reset sub-item prefix general serial.
+				$to_increment[] = $change['Prefix'];
+
+				// Will be "ci|ItemResourceId".
+				$to_increment[] = $change['Prefix'] . '|' . $change['ItemId'];
+			}
+		}
+
+		// 5. reset serials collected before
+		$to_increment = array_unique($to_increment);
+		$this->Application->incrementCacheSerial($prefix);
+
+		foreach ( $to_increment as $to_increment_mixed ) {
+			if ( strpos($to_increment_mixed, '|') !== false ) {
+				list ($to_increment_prefix, $to_increment_id) = explode('|', $to_increment_mixed, 2);
+				$this->Application->incrementCacheSerial($to_increment_prefix, $to_increment_id);
+			}
+			else {
+				$this->Application->incrementCacheSerial($to_increment_mixed);
+			}
+		}
+
+		// Save changes to a database.
+		$session_log_id = $this->Application->RecallVar('_SessionLogId_');
+
+		if ( !$save || !$session_log_id ) {
+			// Saving changes to database disabled OR related session log missing.
+			return;
+		}
+
+		$add_fields = array(
+			'PortalUserId' => $this->Application->RecallVar('user_id'),
+			'SessionLogId' => $session_log_id,
+		);
+
+		$change_log_table = $this->Application->getUnitOption('change-log', 'TableName');
+
+		foreach ( $changes as $rec ) {
+			$this->Conn->doInsert(array_merge($rec, $add_fields), $change_log_table);
+		}
+
+		$this->Application->incrementCacheSerial('change-log');
+
+		$sql = 'UPDATE ' . $this->Application->getUnitOption('session-log', 'TableName') . '
+				SET AffectedItems = AffectedItems + ' . count($changes) . '
+				WHERE SessionLogId = ' . $session_log_id;
+		$this->Conn->Query($sql);
+
+		$this->Application->incrementCacheSerial('session-log');
+	}
+
+	/**
+	 * Returns change log session variable name.
+	 *
+	 * @param string $main_prefix Main prefix.
+	 * @param string $prefix      Prefix.
+	 *
+	 * @return string
+	 */
+	protected function getSessionVariableName($main_prefix, $prefix)
+	{
+		return $main_prefix . '_changes_' . $this->Application->GetTopmostWid($prefix);
+	}
+
+}
Index: branches/5.2.x/core/units/helpers/helpers_config.php
===================================================================
--- branches/5.2.x/core/units/helpers/helpers_config.php
+++ branches/5.2.x/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' => 'ChangeLogHelper', 'class' => 'ChangeLogHelper', 'file' => 'ChangeLogHelper.php', 'build_event' => ''),
 		),
 	);