Page MenuHomeIn-Portal Phabricator

ChangeLogHelper.php
No OneTemporary

File Metadata

Created
Wed, Feb 26, 11:55 AM

ChangeLogHelper.php

<?php
/**
* @version $Id: ChangeLogHelper.php 16824 2025-01-28 16:19:21Z alex $
* @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);
}
}

Event Timeline