N°6043 - Booking: Add prerequisites in iTop core - CRUD extensibility (#520)

This commit is contained in:
bdalsass
2023-07-04 16:22:53 +02:00
committed by GitHub
parent 40dc3deabb
commit 9d38b4d1d6
28 changed files with 1665 additions and 123 deletions

View File

@@ -47,6 +47,7 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Service\Links\LinkSetDataTransformer;
use Combodo\iTop\Service\Links\LinkSetModel;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
@@ -2818,33 +2819,33 @@ JS
}
}
// Custom operation for the form ?
if (isset($aExtraParams['custom_operation'])) {
$sOperation = $aExtraParams['custom_operation'];
} else {
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
$sOperation = 'apply_modify';
} else {
$sOperation = 'apply_new';
}
}
if (isset($aExtraParams['custom_operation'])) {
$sOperation = $aExtraParams['custom_operation'];
} else {
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
$sOperation = 'apply_modify';
} else {
$sOperation = 'apply_new';
}
}
$oContentBlock = new UIContentBlock();
$oPage->AddUiBlock($oContentBlock);
$oContentBlock = new UIContentBlock();
$oPage->AddUiBlock($oContentBlock);
$oForm = new Form("form_{$this->m_iFormId}");
$oForm->SetAction($sFormAction);
$sOnSubmitForm = "let bOnSubmitForm = OnSubmit('form_{$this->m_iFormId}');";
if (isset($aExtraParams['js_handlers']['form_on_submit'])) {
$oForm->SetOnSubmitJsCode($sOnSubmitForm.$aExtraParams['js_handlers']['form_on_submit']);
} else {
$oForm->SetOnSubmitJsCode($sOnSubmitForm."return bOnSubmitForm;");
}
$oContentBlock->AddSubBlock($oForm);
$oForm = new Form("form_{$this->m_iFormId}");
$oForm->SetAction($sFormAction);
$sOnSubmitForm = "let bOnSubmitForm = OnSubmit('form_{$this->m_iFormId}');";
if (isset($aExtraParams['js_handlers']['form_on_submit'])) {
$oForm->SetOnSubmitJsCode($sOnSubmitForm . $aExtraParams['js_handlers']['form_on_submit']);
} else {
$oForm->SetOnSubmitJsCode($sOnSubmitForm . "return bOnSubmitForm;");
}
$oContentBlock->AddSubBlock($oForm);
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
// The object already exists in the database, it's a modification
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id"));
}
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
// The object already exists in the database, it's a modification
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id"));
}
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', $sOperation));
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass));
@@ -2853,6 +2854,11 @@ JS
$oPage->SetTransactionId($iTransactionId);
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
// Add temporary object watchdog (only on root form)
if (!utils::IsXmlHttpRequest()) {
$oPage->add_ready_script(TemporaryObjectHelper::GetWatchDogJS($iTransactionId));
}
// TODO 3.0.0: Is this (the if condition, not the code inside) still necessary?
if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) {
$sClassLabel = MetaModel::GetName($sClass);
@@ -2863,34 +2869,34 @@ JS
}
}
$oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null);
$oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel();
$oCancelButton->AddCSSClasses(['action', 'cancel']);
$oToolbarButtons->AddSubBlock($oCancelButton);
$oApplyButton = ButtonUIBlockFactory::MakeForPrimaryAction($sApplyButton, null, null, true);
$oApplyButton->AddCSSClass('action');
$oToolbarButtons->AddSubBlock($oApplyButton);
$bAreTransitionsHidden = isset($aExtraParams['hide_transitions']) && $aExtraParams['hide_transitions'] === true;
$aTransitions = $this->EnumTransitions();
if (!isset($aExtraParams['custom_operation']) && !$bAreTransitionsHidden && count($aTransitions)) {
// Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
$oSetToCheckRights = DBObjectSet::FromObject($this);
$oCancelButton = ButtonUIBlockFactory::MakeForCancel();
$oCancelButton->AddCSSClasses(['action', 'cancel']);
$oToolbarButtons->AddSubBlock($oCancelButton);
$oApplyButton = ButtonUIBlockFactory::MakeForPrimaryAction($sApplyButton, null, null, true);
$oApplyButton->AddCSSClass('action');
$oToolbarButtons->AddSubBlock($oApplyButton);
$bAreTransitionsHidden = isset($aExtraParams['hide_transitions']) && $aExtraParams['hide_transitions'] === true;
$aTransitions = $this->EnumTransitions();
if (!isset($aExtraParams['custom_operation']) && !$bAreTransitionsHidden && count($aTransitions)) {
// Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
$oSetToCheckRights = DBObjectSet::FromObject($this);
$oTransitionPopoverMenu = new PopoverMenu();
$sTPMSectionId = 'transitions';
$oTransitionPopoverMenu->AddSection($sTPMSectionId);
$aStimuli = Metamodel::EnumStimuli($sClass);
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
$sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
// Button to be displayed on its own on large screens
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClass('action');
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
$oToolbarButtons->AddSubBlock($oButton);
$oTransitionPopoverMenu = new PopoverMenu();
$sTPMSectionId = 'transitions';
$oTransitionPopoverMenu->AddSection($sTPMSectionId);
$aStimuli = Metamodel::EnumStimuli($sClass);
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
$sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
switch ($iActionAllowed) {
case UR_ALLOWED_YES:
// Button to be displayed on its own on large screens
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
$oButton->AddCSSClass('action');
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
$oToolbarButtons->AddSubBlock($oButton);
// Button to be displayed in a grouped button on smaller screens
$oTPMPopupMenuItem = new JSPopupMenuItem('next_action--'.$oButton->GetId(), $oButton->GetLabel(), "$(`#{$oButton->GetId()}`).trigger(`click`);");

View File

@@ -168,8 +168,6 @@ class UIExtKeyWidget
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
if ($this->bSearchMode) {
$sWizHelper = 'null';
@@ -1070,18 +1068,27 @@ JS
{
$oObj = MetaModel::NewObject($this->sTargetClass);
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
if (count($aErrors) == 0)
{
$oObj->DBInsert();
if (count($aErrors) == 0) {
// Retrieve JSON data
$sJSON = utils::ReadParam('json', '{}', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$oJSON = json_decode($sJSON);
$oObj->SetContextSection('temporary_objects', [
'create' => [
'transaction_id' => utils::ReadParam('root_transaction_id', '', false, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID),
'host_class' => $oJSON->m_sClass,
'host_att_code' => $this->sAttCode,
],
]);
$oObj->DBInsertNoReload();
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
}
else
{
} else {
return array('error' => implode(' ', $aErrors), 'id' => 0);
}
}
catch(Exception $e)
{
catch (Exception $e) {
return array('error' => $e->getMessage(), 'id' => 0);
}
}

View File

@@ -3371,5 +3371,22 @@ HTML;
{
return in_array($sTrait, self::TraitsUsedByClass($sClass, true));
}
/**
* Get stack trace as string array.
*
* @return array
* @since 3.1.0
*/
public static function GetStackTraceAsArray(): array
{
$e = new Exception();
$aTrace = explode("\n", $e->getTraceAsString());
// Remove call to this method
array_shift($aTrace);
// Remove Main
array_pop($aTrace);
return $aTrace;
}
}

View File

@@ -7221,14 +7221,26 @@ class AttributeExternalKey extends AttributeDBFieldVoid
{
return 0;
}
if (MetaModel::IsValidObject($proposedValue))
{
if (MetaModel::IsValidObject($proposedValue)) {
return $proposedValue->GetKey();
}
return (int)$proposedValue;
}
public function WriteExternalValues(DBObject $oHostObject): void
{
$sTargetKey = $oHostObject->Get($this->GetCode());
$oFilter = DBSearch::FromOQL('SELECT `'.TemporaryObjectDescriptor::class.'` WHERE item_class=:class AND item_id=:id');
$oSet = new DBObjectSet($oFilter, [], ['class' => $this->GetTargetClass(), 'id' => $sTargetKey]);
while ($oTemporaryObjectDescriptor = $oSet->Fetch()) {
$oTemporaryObjectDescriptor->Set('host_class', get_class($oHostObject));
$oTemporaryObjectDescriptor->Set('host_id', $oHostObject->GetKey());
$oTemporaryObjectDescriptor->Set('host_att_code', $this->GetCode());
$oTemporaryObjectDescriptor->DBUpdate();
}
}
public function GetMaximumComboLength()
{
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));

View File

@@ -137,7 +137,7 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'log_purge.max_keep_days' => [
'log_purge.max_keep_days' => [
'type' => 'integer',
'description' => 'Optional purge number of days to keep logs.',
'default' => 365,
@@ -145,7 +145,7 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'event_service.debug.filter_events' => [
'event_service.debug.filter_events' => [
'type' => 'array',
'description' => 'List of events name to filter Event Service debug messages',
'default' => [],
@@ -153,7 +153,7 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'event_service.debug.filter_sources' => [
'event_service.debug.filter_sources' => [
'type' => 'array',
'description' => 'List of event sources to filter Event Service debug messages',
'default' => '',
@@ -161,6 +161,38 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'temporary_object.force_creation' => [
'type' => 'bool',
'description' => 'If true, all the objects created by the external key are temporary',
'default' => false,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'temporary_object.lifetime' => [
'type' => 'integer',
'description' => 'Seconds for temporary objects created',
'default' => 300,
'value' => 300,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'temporary_object.watchdog_interval' => [
'type' => 'integer',
'description' => 'Seconds between watchdog signals',
'default' => 60,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'temporary_object.garbage_interval' => [
'type' => 'integer',
'description' => 'Seconds between garbage collections',
'default' => 60,
'value' => false,
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'app_env_label' => [
'type' => 'string',
'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")',
@@ -185,7 +217,7 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
],
'db_host' => [
'db_host' => [
'type' => 'string',
'default' => null,
'value' => '',

View File

@@ -489,6 +489,12 @@
<type>boolean</type>
<default>false</default>
</property>
<property id="create_temporary_object">
<php_param>create_temporary_object</php_param>
<mandatory>false</mandatory>
<type>boolean</type>
<default>false</default>
</property>
<property id="on_target_delete">
<php_param>on_target_delete</php_param>
<mandatory>false</mandatory>

View File

@@ -7,6 +7,7 @@
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
/**
* All objects to be displayed in the application (either as a list or as details)
@@ -194,27 +195,30 @@ abstract class DBObject implements iDisplay
*/
protected static array $m_aCrudStack = [];
/** @var array Context for update insert operations */
private array $aContext = [];
// Protect DBUpdate against infinite loop
protected $iUpdateLoopCount;
const MAX_UPDATE_LOOP_COUNT = 10;
/**
* DBObject constructor.
*
* You should preferably use MetaModel::NewObject() instead of this constructor.
* The whole collection of parameters is [*optional*] please refer to DBObjectSet::FromRow()
*
* @internal The availability of this method is not guaranteed in the long term, you should preferably use MetaModel::NewObject().
* @see MetaModel::NewObject()
*
* @param null|array $aRow If given : DBObjectSet::FromRow() will be used to fetch the object
* @param string $sClassAlias
* @param null|array $aAttToLoad
* @param null|array $aExtendedDataSpec
*
* @throws CoreException
*/
* DBObject constructor.
*
* You should preferably use MetaModel::NewObject() instead of this constructor.
* The whole collection of parameters is [*optional*] please refer to DBObjectSet::FromRow()
*
* @internal The availability of this method is not guaranteed in the long term, you should preferably use MetaModel::NewObject().
* @see MetaModel::NewObject()
*
* @param null|array $aRow If given : DBObjectSet::FromRow() will be used to fetch the object
* @param string $sClassAlias
* @param null|array $aAttToLoad
* @param null|array $aExtendedDataSpec
*
* @throws CoreException
*/
public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
{
$this->iUpdateLoopCount = 0;
@@ -1427,7 +1431,7 @@ abstract class DBObject implements iDisplay
}
$sPreview = '';
if(SummaryCardService::IsAllowedForClass($sObjClass) && $bIgnorePreview === false){
$sPreview = SummaryCardService::GetHyperlinkMarkup($sObjClass, $sObjKey);
$sPreview = SummaryCardService::GetHyperlinkMarkup($sObjClass, $sObjKey);
}
$sRet = "<span class=\"object-ref $sSpanClass\" $sPreview title=\"$sHint\">$sHLink</span>";
return $sRet;
@@ -3091,13 +3095,12 @@ abstract class DBObject implements iDisplay
$this->AddCurrentObjectInCrudStack('INSERT');
try {
if (MetaModel::DBIsReadOnly())
{
$sErrorMessage = "Cannot Insert object of class '$sClass' because of an ongoing maintenance: the database is in ReadOnly mode";
if (MetaModel::DBIsReadOnly()) {
$sErrorMessage = "Cannot Insert object of class '$sClass' because of an ongoing maintenance: the database is in ReadOnly mode";
IssueLog::Error("$sErrorMessage\n".MyHelpers::get_callstack_text(1));
throw new CoreException("$sErrorMessage (see the log for more information)");
}
IssueLog::Error("$sErrorMessage\n".MyHelpers::get_callstack_text(1));
throw new CoreException("$sErrorMessage (see the log for more information)");
}
if ($this->m_bIsInDB) {
throw new CoreException('The object already exists into the Database, you may want to use the clone function');
@@ -3131,7 +3134,7 @@ abstract class DBObject implements iDisplay
}
$this->ComputeStopWatchesDeadline(true);
$iTransactionRetry = 1;
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
if ($bIsTransactionEnabled) {
@@ -3170,6 +3173,8 @@ abstract class DBObject implements iDisplay
$this->DBWriteLinks();
$this->WriteExternalAttributes();
$this->HandleTemporaryDescriptor();
// Write object creation history within the transaction
$this->RecordObjCreation();
@@ -3290,8 +3295,8 @@ abstract class DBObject implements iDisplay
* This function is automatically called after cloning an object with the "clone" PHP language construct
* The purpose of this method is to reset the appropriate attributes of the object in
* order to make sure that the newly cloned object is really distinct from its clone
*
* @internal
*
* @internal
*/
public function __clone()
{
@@ -3300,7 +3305,6 @@ abstract class DBObject implements iDisplay
$this->m_iKey = self::GetNextTempId(get_class($this));
}
/**
* Update an object in DB
*
@@ -3439,6 +3443,8 @@ abstract class DBObject implements iDisplay
$this->DBWriteLinks();
$this->WriteExternalAttributes();
$this->HandleTemporaryDescriptor();
if (count($aChanges) != 0) {
$this->RecordAttChanges($aChanges, $aOriginalValues);
}
@@ -6391,5 +6397,62 @@ abstract class DBObject implements iDisplay
$sPadding = str_pad('', count(self::$m_aCrudStack), '!');
IssueLog::Error("CRUD !!$sPadding Error $sFunction $sClass:$sKey $sComment", LogChannels::DM_CRUD);
}
/**
* @param $aContext
*
* @return void
* @since 3.1.0
*/
private function HandleTemporaryDescriptor()
{
if ($this->HasContextSection('temporary_objects')) {
TemporaryObjectManager::GetInstance()->HandleTemporaryObjects($this, $this->GetContextSection('temporary_objects'));
}
}
/**
* @return array
*/
public function GetContext(): array
{
return $this->aContext;
}
/**
* Set context section data.
*
* @param string $sSection
* @param $value
*
*/
public function SetContextSection(string $sSection, $value)
{
$this->aContext[$sSection] = $value;
}
/**
* @param string $sSection
*
* @return mixed
*/
public function GetContextSection(string $sSection)
{
if ($this->HasContextSection($sSection)) {
return $this->aContext[$sSection];
}
return null;
}
/**
* @param string $sSection
*
* @return bool
*/
public function HasContextSection(string $sSection): bool
{
return array_key_exists($sSection, $this->aContext);
}
}

View File

@@ -612,6 +612,8 @@ class LogChannels
public const PORTAL = 'portal';
public const TEMPORARY_OBJECTS = 'TemporaryObjects';
/**
* @var string
* @since 3.1.0

View File

@@ -22,6 +22,7 @@ SetupWebPage::AddModule(
//
'datamodel' => array(
'main.itop-structure.php',
'src/Model/TemporaryObjectDescriptor.php',
),
'data.struct' => array(
),

View File

@@ -0,0 +1,96 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
/**
*
* @since 3.1
*/
class TemporaryObjectDescriptor extends DBObject
{
public static function Init()
{
$aParams = array(
'category' => 'structure',
'key_type' => 'autoincrement',
'name_attcode' => array('item_class', 'temp_id'),
'image_attcode' => '',
'state_attcode' => '',
'reconc_keys' => array(''),
'db_table' => 'priv_temporary_object_descriptor',
'db_key_field' => 'id',
'db_finalclass_field' => '',
'style' => new ormStyle(null, null, null, null, null, null),
'indexes' => array(
1 =>
array(
0 => 'temp_id',
),
2 =>
array(
0 => 'item_class',
1 => 'item_id',
),
),
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeDateTime('expiration_date', array('sql' => 'expiration_date', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('temp_id', array('sql' => 'temp_id', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('item_class', array('sql' => 'item_class', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeObjectKey('item_id', array('class_attcode' => 'item_class', 'sql' => 'item_id', 'is_null_allowed' => true, 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeDateTime('creation_date', array('sql' => 'creation_date', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('host_class', array('sql' => 'host_class', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeObjectKey('host_id', array('class_attcode' => 'host_class', 'sql' => 'host_id', 'is_null_allowed' => true, 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeString('host_att_code', array('sql' => 'host_att_code', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
MetaModel::Init_AddAttribute(new AttributeEnum("operation", array("allowed_values" => new ValueSetEnum('create,delete'), "sql" => "operation", "default_value" => "create", "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_SetZListItems('details', array(
0 => 'temp_id',
1 => 'item_class',
2 => 'item_id',
3 => 'creation_date',
4 => 'expiration_date',
5 => 'meta',
));
MetaModel::Init_SetZListItems('standard_search', array(
0 => 'temp_id',
1 => 'item_class',
2 => 'item_id',
));
MetaModel::Init_SetZListItems('list', array(
0 => 'temp_id',
1 => 'item_class',
2 => 'item_id',
3 => 'creation_date',
4 => 'expiration_date',
));;
}
public function DBInsertNoReload()
{
$this->SetCurrentDateIfNull('creation_date');
return parent::DBInsertNoReload();
}
/**
* Set/Update all of the '_item' fields
*
* @param object $oItem Container item
*
* @return void
*/
public function SetItem($oItem, $bUpdateOnChange = false)
{
$sClass = get_class($oItem);
$iItemId = $oItem->GetKey();
$this->Set('item_class', $sClass);
$this->Set('item_id', $iItemId);
}
}

View File

@@ -651,6 +651,25 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
$('#ac_create_'+me.id).dialog('close');
};
/**
* Extract transaction id of the root object edited.
* When create/update a new object via external key,
* this transaction id reflects the root form transaction id an not the current form transaction id.
*
* @constructor
*/
this.GetRootTransactionId = function(){
// Retrieve the object form
const oForm = $(`#${me.id}`).closest('form');
// If root transaction id exist, then use it
let oFieldTransaction = $('input[name=root_transaction_id]', oForm);
if(oFieldTransaction.length === 0){
// otherwise, use the object form transaction id
oFieldTransaction = $('input[name=transaction_id]', oForm);
}
return oFieldTransaction.val();
}
this.CreateObject = function (bTargetClassSelected) {
if ($('#'+me.id).prop('disabled')) {
return;
@@ -679,6 +698,10 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// Run the query and get the result back directly in HTML
var sLocalTargetClass = me.sTargetClass; // Remember the target class since it will be reset when closing the dialog
// Handle transaction id
const sRootFormTransactionId = me.GetRootTransactionId();
me.ajax_request = $.post(AddAppContext(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php'), theMap,
function (data) {
$('#ajax_'+me.id).html(data);
@@ -696,6 +719,8 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
if ($('#ac_create_'+me.id).height() > ($(window).height()-70)) {
$('#ac_create_'+me.id).height($(window).height()-70);
}
// Add root_transaction_id
$('#ac_create_'+me.id+' form').append(`<input type="hidden" name="root_transaction_id" value="${sRootFormTransactionId}"/>`)
});
},
'html'

View File

@@ -371,6 +371,7 @@ return array(
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => $baseDir . '/sources/Controller/Links/LinkSetController.php',
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php',
'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.php',
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => $baseDir . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
'Combodo\\iTop\\Controller\\iController' => $baseDir . '/sources/Controller/iController.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
@@ -463,6 +464,12 @@ return array(
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => $baseDir . '/sources/Service/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => $baseDir . '/sources/Service/Router/Exception/RouterException.php',
'Combodo\\iTop\\Service\\Router\\Router' => $baseDir . '/sources/Service/Router/Router.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectConfig' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectConfig.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectGC' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectGC.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectHelper' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectHelper.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => $baseDir . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'Config' => $baseDir . '/core/config.class.inc.php',

View File

@@ -735,6 +735,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Controller\\Links\\LinkSetController' => __DIR__ . '/../..' . '/sources/Controller/Links/LinkSetController.php',
'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php',
'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.php',
'Combodo\\iTop\\Controller\\TemporaryObjects\\TemporaryObjectController' => __DIR__ . '/../..' . '/sources/Controller/TemporaryObjects/TemporaryObjectController.php',
'Combodo\\iTop\\Controller\\iController' => __DIR__ . '/../..' . '/sources/Controller/iController.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php',
'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php',
@@ -827,6 +828,12 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\Router\\Exception\\RouteNotFoundException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouteNotFoundException.php',
'Combodo\\iTop\\Service\\Router\\Exception\\RouterException' => __DIR__ . '/../..' . '/sources/Service/Router/Exception/RouterException.php',
'Combodo\\iTop\\Service\\Router\\Router' => __DIR__ . '/../..' . '/sources/Service/Router/Router.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectConfig' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectConfig.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectGC' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectGC.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectHelper' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectHelper.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectManager' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectManager.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectRepository' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectRepository.php',
'Combodo\\iTop\\Service\\TemporaryObjects\\TemporaryObjectsEvents' => __DIR__ . '/../..' . '/sources/Service/TemporaryObjects/TemporaryObjectsEvents.php',
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',

View File

@@ -5,7 +5,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '1b7529fcb988f27abcc14834836b047e765323bd',
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
'name' => 'combodo/itop',
'dev' => true,
),
@@ -25,7 +25,7 @@
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => '1b7529fcb988f27abcc14834836b047e765323bd',
'reference' => 'dbf3393c9729a20f0bf389d343507238d61fef56',
'dev_requirement' => false,
),
'combodo/tcpdf' => array(

View File

@@ -16,6 +16,7 @@ use Combodo\iTop\Controller\PreferencesController;
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
use Combodo\iTop\Service\Router\Router;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
require_once('../approot.inc.php');
@@ -816,6 +817,9 @@ try
$bReleaseLock = iTopOwnershipLock::ReleaseLock($sObjClass, $iObjKey, $sToken);
}
// Invalidate temporary objects
TemporaryObjectManager::GetInstance()->CancelAllTemporaryObjects($iTransactionId);
IssueLog::Trace('on_form_cancel', $sObjClass, array(
'$iObjKey' => $iObjKey,
'$sTransactionId' => $iTransactionId,
@@ -2553,7 +2557,7 @@ EOF
/** @internal */
case 'object.modify':
$oController = new ObjectController();
$oPage = $oController->Modify();
$oPage = $oController->OperationModify();
break;
default:

View File

@@ -2083,6 +2083,7 @@ EOF
$this->CompileCommonProperty('min_autocomplete_chars', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('allow_target_creation', $oField, $aParameters, $sModuleRelativeDir);
$this->CompileCommonProperty('display_style', $oField, $aParameters, $sModuleRelativeDir, 'select');
$this->CompileCommonProperty('create_temporary_object', $oField, $aParameters, $sModuleRelativeDir, false);
$aParameters['depends_on'] = $sDependencies;
} elseif ($sAttType == 'AttributeObjectKey') {
$this->CompileCommonProperty('class_attcode', $oField, $aParameters, $sModuleRelativeDir);

View File

@@ -189,14 +189,17 @@ class XMLDataLoader
function LoadFile($sFilePath, $bUpdateKeyCacheOnly = false, bool $bSearch = false)
{
global $aKeys;
$oXml = simplexml_load_file($sFilePath);
$aReplicas = array();
foreach($oXml as $sClass => $oXmlObj)
{
if (!MetaModel::IsValidClass($sClass))
{
if (!$oXml) {
SetupLog::Error("Unable to load xml file - $sFilePath");
throw(new Exception("Unable to load xml file - $sFilePath"));
}
$aReplicas = array();
foreach ($oXml as $sClass => $oXmlObj) {
if (!MetaModel::IsValidClass($sClass)) {
SetupLog::Error("Unknown class - $sClass");
throw(new Exception("Unknown class - $sClass"));
}

View File

@@ -398,24 +398,31 @@ JS;
{
IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object not created (see $aErrors)', $sClass, array(
'$sTransactionId' => $sTransactionId,
'$aErrors' => $aErrors,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'$aErrors' => $aErrors,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors));
}
$oObj->DBInsertNoReload();// No need to reload
// Transactions are now handled in DBInsert
$oObj->SetContextSection('temporary_objects', [
'finalize' => [
'transaction_id' => $sTransactionId,
],
]);
$oObj->DBInsertNoReload();
IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object created', $sClass, array(
'$id' => $oObj->GetKey(),
'$id' => $oObj->GetKey(),
'$sTransactionId' => $sTransactionId,
'$aErrors' => $aErrors,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'$aErrors' => $aErrors,
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
utils::RemoveTransaction($sTransactionId);
@@ -596,23 +603,28 @@ JS;
else
{
IssueLog::Trace(__CLASS__.'::'.__METHOD__.' Object updated', $sClass, array(
'$id' => $id,
'$id' => $id,
'$sTransactionId' => $sTransactionId,
'$aErrors' => $aErrors,
'IsModified' => $oObj->IsModified(),
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
'$aErrors' => $aErrors,
'IsModified' => $oObj->IsModified(),
'$sUser' => UserRights::GetUser(),
'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'],
'REQUEST_URI' => @$_SERVER['REQUEST_URI'],
));
try
{
if (!empty($aErrors))
{
try {
if (!empty($aErrors)) {
throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors));
}
// Transactions are now handled in DBUpdate
$oObj->SetContextSection('temporary_objects', [
'finalize' => [
'transaction_id' => $sTransactionId,
],
]);
$oObj->DBUpdate();
$sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName());
$sSeverity = 'ok';
if ($this->IsHandlingXmlHttpRequest()) {

View File

@@ -0,0 +1,78 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Controller\TemporaryObjects;
use Combodo\iTop\Controller\AbstractController;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
use JsonPage;
use utils;
/**
* TemporaryObjectController.
*
* Temporary object endpoints.
*
* @since 3.1
*/
class TemporaryObjectController extends AbstractController
{
public const ROUTE_NAMESPACE = 'temporary_object';
/** @var \Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager Temporary object manager */
private TemporaryObjectManager $oTemporaryObjectManager;
/**
* Constructor.
*
*/
public function __construct()
{
// Retrieve controller dependencies
$this->oTemporaryObjectManager = TemporaryObjectManager::GetInstance();
}
/**
* OperationWatchDog.
*
* Watchdog for delaying expiration date of temporary objects linked to the provided temporary id.
*
* @return JsonPage
*/
public function OperationWatchDog(): JsonPage
{
$oPage = new JsonPage();
// Retrieve temp id
$sTempId = utils::ReadParam('temp_id', '', false, utils::ENUM_SANITIZATION_FILTER_STRING);
// Delay temporary objects expiration
$bResult = $this->oTemporaryObjectManager->ExtendTemporaryObjectsLifetime($sTempId);
return $oPage->SetData([
'success' => $bResult,
]);
}
/**
* OperationGarbage.
*
* Garbage temporary objects based on expiration date.
*
* @return JsonPage
*/
public function OperationGarbage(): JsonPage
{
$oPage = new JsonPage();
// Garbage expired temporary objects
$bResult = $this->oTemporaryObjectManager->GarbageExpiredTemporaryObjects();
return $oPage->SetData([
'success' => $bResult,
]);
}
}

View File

@@ -6,6 +6,7 @@
namespace Combodo\iTop\Service\Base;
use cmdbAbstractObject;
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
use DBObject;
use DBObjectSearch;
@@ -299,4 +300,40 @@ class ObjectRepository
return ObjectRepository::ComputeOthersData($oObject, $sObjectClass, $aObjectData, $aComplementAttributeSpec, $sObjectImageAttCode);
}
/**
* DeleteFromOql.
*
* @param string $sOql OQL expression
*
* @return bool
*/
static public function DeleteFromOql(string $sOql): bool
{
try {
// Create db search
$oDbObjectSearch = DBSearch::FromOQL($sOql);
// Create db set from db search
$oDbObjectSet = new DBObjectSet($oDbObjectSearch);
// Delete objects
while ($oObject = $oDbObjectSet->Fetch()) {
$oObject->DBDelete();
}
// return operation success
return true;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\TemporaryObjects;
use MetaModel;
class TemporaryObjectConfig
{
private int $iGarbageInterval;
private int $iConfigTemporaryLifetime;
private bool $bConfigTemporaryForce;
private int $iWatchdogInterval;
private static ?TemporaryObjectConfig $oSingletonInstance = null;
/**
* Constructor.
*
*/
private function __construct()
{
// Retrieve service configuration
$oConfig = MetaModel::GetConfig();
$this->iGarbageInterval = $oConfig->Get(TemporaryObjectHelper::CONFIG_GARBAGE_INTERVAL);
$this->iConfigTemporaryLifetime = $oConfig->Get(TemporaryObjectHelper::CONFIG_TEMP_LIFETIME);
$this->bConfigTemporaryForce = $oConfig->Get(TemporaryObjectHelper::CONFIG_FORCE);
$this->iWatchdogInterval = $oConfig->Get(TemporaryObjectHelper::CONFIG_GARBAGE_INTERVAL);
}
/**
* GetInstance.
*
* @return TemporaryObjectConfig
*/
public static function GetInstance(): TemporaryObjectConfig
{
if (is_null(self::$oSingletonInstance)) {
self::$oSingletonInstance = new TemporaryObjectConfig();
}
return self::$oSingletonInstance;
}
/**
* @return int
*/
public function GetGarbageInterval(): int
{
return $this->iGarbageInterval;
}
/**
* @param int $iGarbageInterval
*/
public function SetGarbageInterval(int $iGarbageInterval): void
{
$this->iGarbageInterval = $iGarbageInterval;
}
/**
* @return int
*/
public function GetConfigTemporaryLifetime(): int
{
return $this->iConfigTemporaryLifetime;
}
/**
* @param int $iConfigTemporaryLifetime
*/
public function SetConfigTemporaryLifetime(int $iConfigTemporaryLifetime): void
{
$this->iConfigTemporaryLifetime = $iConfigTemporaryLifetime;
}
/**
* @return bool
*/
public function GetConfigTemporaryForce(): bool
{
return $this->bConfigTemporaryForce;
}
/**
* @param bool $bConfigTemporaryForce
*/
public function SetConfigTemporaryForce(bool $bConfigTemporaryForce): void
{
$this->bConfigTemporaryForce = $bConfigTemporaryForce;
}
/**
* @return int
*/
public function GetWatchdogInterval(): int
{
return $this->iWatchdogInterval;
}
/**
* @param int $iWatchdogInterval
*/
public function SetWatchdogInterval(int $iWatchdogInterval): void
{
$this->iWatchdogInterval = $iWatchdogInterval;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\TemporaryObjects;
use iBackgroundProcess;
/**
* TemporaryObjectGC.
*
* Background task to collect and garbage expired temporary objects..
*
* @since 3.1
*/
class TemporaryObjectGC implements iBackgroundProcess
{
/** @var TemporaryObjectManager */
private TemporaryObjectManager $oTemporaryObjectManager;
/**
* Constructor.
*
*/
public function __construct()
{
// Retrieve service dependencies
$this->oTemporaryObjectManager = TemporaryObjectManager::GetInstance();
}
/** @inheritDoc * */
public function GetPeriodicity()
{
return TemporaryObjectConfig::GetInstance()->GetWatchdogInterval();
}
/** @inheritDoc * */
public function Process($iUnixTimeLimit)
{
// Garbage temporary objects
$this->oTemporaryObjectManager->GarbageExpiredTemporaryObjects();
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\TemporaryObjects;
/**
* TemporaryObjectHelper.
*
* Helper with useful functions.
*
* @since 3.1
*/
class TemporaryObjectHelper
{
// Global configuration
const CONFIG_FORCE = 'temporary_object.force_creation';
const CONFIG_TEMP_LIFETIME = 'temporary_object.lifetime';
const CONFIG_WATCHDOG_INTERVAL = 'temporary_object.watchdog_interval';
const CONFIG_GARBAGE_INTERVAL = 'temporary_object.garbage_interval';
// Temporary descriptor operation
const OPERATION_CREATE = 'create';
const OPERATION_DELETE = 'delete';
/**
* GetWatchDogJS.
*
* @param string $sTempId
*
* @return string
*/
static public function GetWatchDogJS(string $sTempId): string
{
$iWatchdogInterval = TemporaryObjectConfig::GetInstance()->GetWatchdogInterval();
return <<<JS
window.setInterval(function() {
$.post(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?route=temporary_object.watch_dog', {temp_id: '$sTempId'});
}, $iWatchdogInterval * 1000)
JS;
}
}

View File

@@ -0,0 +1,400 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\TemporaryObjects;
use DateTime;
use DBObject;
use Exception;
use ExceptionLog;
use IssueLog;
use LogChannels;
use MetaModel;
use TemporaryObjectDescriptor;
use utils;
/**
* TemporaryObjectManager.
*
* Manager class to perform global temporary objects tasks.
*
* @since 3.1
*/
class TemporaryObjectManager
{
/** @var TemporaryObjectManager|null Singleton */
static private ?TemporaryObjectManager $oSingletonInstance = null;
/** @var TemporaryObjectRepository $oTemporaryObjectRepository */
private TemporaryObjectRepository $oTemporaryObjectRepository;
/**
* GetInstance.
*
* @return TemporaryObjectManager
*/
public static function GetInstance(): TemporaryObjectManager
{
if (is_null(self::$oSingletonInstance)) {
self::$oSingletonInstance = new TemporaryObjectManager();
}
return self::$oSingletonInstance;
}
/**
* Constructor.
*
*/
private function __construct()
{
// Retrieve service dependencies
$this->oTemporaryObjectRepository = TemporaryObjectRepository::GetInstance();
}
/**
* CreateTemporaryObject.
*
* @param string $sTempId Temporary id context for the temporary object
* @param string $sObjectClass Temporary object class
* @param string $sObjectKey Temporary object key
* @param string $sOperation temporary operation on file TemporaryObjectHelper::OPERATION_CREATE or TemporaryObjectHelper::OPERATION_DELETE
*
* @return TemporaryObjectDescriptor|null
*/
public function CreateTemporaryObject(string $sTempId, string $sObjectClass, string $sObjectKey, string $sOperation): ?TemporaryObjectDescriptor
{
$result = $this->oTemporaryObjectRepository->Create($sTempId, $sObjectClass, $sObjectKey, $sOperation);
// Log
IssueLog::Debug("TemporaryObjectsManager: Create a temporary object attached to temporary id $sTempId", LogChannels::TEMPORARY_OBJECTS, [
'temp_id' => $sTempId,
'item_class' => $sObjectClass,
'item_id' => $sObjectKey,
'succeeded' => $result != null,
]);
return $result;
}
/**
* Cancel the ongoing operation (create or delete) on all the temporary objects impacted by this transaction id
*
* @param string $sTransactionId form transaction id
*
* @return bool true if success
*/
public function CancelAllTemporaryObjects(string $sTransactionId): bool
{
try {
// Get temporary object descriptors
$oDbObjectSet = $this->oTemporaryObjectRepository->SearchByTempId($sTransactionId, true);
// Cancel temporary objects...
$bResult = $this->CancelTemporaryObjects($oDbObjectSet->ToArray());
// Log
IssueLog::Debug("TemporaryObjectsManager: Cancel all temporary objects attached to temporary id $sTransactionId", LogChannels::TEMPORARY_OBJECTS, [
'temp_id' => $sTransactionId,
'succeeded' => $bResult,
]);
// return operation success
return true;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
}
/**
* Cancel the ongoing operation (create or delete) on the given temporary objects
*
* @param array{\TemporaryObjectDescriptor} $aTemporaryObjectDescriptor
*
* @return bool true if success
*/
private function CancelTemporaryObjects(array $aTemporaryObjectDescriptor): bool
{
try {
// All operations succeeded
$bResult = true;
/** @var TemporaryObjectDescriptor $oTemporaryObjectDescriptor */
foreach ($aTemporaryObjectDescriptor as $oTemporaryObjectDescriptor) {
// Cancel temporary objects
if (!$this->CancelTemporaryObject($oTemporaryObjectDescriptor)) {
$bResult = false;
}
}
return $bResult;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
}
/**
* Extends the temporary object descriptor lifetime
*
* @param string $sTransactionId
*
* @return bool
*/
public function ExtendTemporaryObjectsLifetime(string $sTransactionId): bool
{
try {
// Create db set from db search
$oDbObjectSet = $this->oTemporaryObjectRepository->SearchByTempId($sTransactionId);
// Expiration date
$iExpirationDate = time() + TemporaryObjectConfig::GetInstance()->GetConfigTemporaryLifetime();
// Delay objects expiration
while ($oObject = $oDbObjectSet->Fetch()) {
$oObject->Set('expiration_date', $iExpirationDate);
$oObject->DBUpdate();
}
// Log
$date = new DateTime();
$date->setTimestamp($iExpirationDate);
IssueLog::Debug("TemporaryObjectsManager: Delay all temporary objects descriptors expiration date attached to temporary id $sTransactionId", LogChannels::TEMPORARY_OBJECTS, [
'temp_id' => $sTransactionId,
'expiration_date' => date_format($date, 'Y-m-d H:i:s'),
'total_temporary_objects' => $this->oTemporaryObjectRepository->CountTemporaryObjectsByTempId($sTransactionId),
]);
// return operation success
return true;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
}
/**
* Accept all the temporary objects operations
*
* @param string $sTransactionId
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*/
private function FinalizeTemporaryObjects(string $sTransactionId)
{
// All operations succeeded
$bResult = true;
// Get temporary object descriptors
$oDBObjectSet = $this->oTemporaryObjectRepository->SearchByTempId($sTransactionId, true);
// Iterate throw descriptors...
/** @var TemporaryObjectDescriptor $oTemporaryObjectDescriptor */
while ($oTemporaryObjectDescriptor = $oDBObjectSet->Fetch()) {
// Retrieve attributes values
$sHostClass = $oTemporaryObjectDescriptor->Get('host_class');
$sHostId = $oTemporaryObjectDescriptor->Get('host_id');
// No host object
if ($sHostId === 0) {
$bResult = $bResult && $this->CancelTemporaryObject($oTemporaryObjectDescriptor);
continue;
}
// Host object pointed by descriptor doesn't exist anymore
$oHostObject = MetaModel::GetObject($sHostClass, $sHostId, false);
if (is_null($oHostObject)) {
$bResult = $bResult && $this->CancelTemporaryObject($oTemporaryObjectDescriptor);
continue;
}
// Otherwise confirm
$bResult = $bResult && $this->ConfirmTemporaryObject($oTemporaryObjectDescriptor);
}
// Log
IssueLog::Debug("TemporaryObjectsManager: Finalize all temporary objects attached to temporary id $sTransactionId", LogChannels::TEMPORARY_OBJECTS, [
'temp_id' => $sTransactionId,
'succeeded' => $bResult,
]);
}
/**
* Accept operation on the given temporary object
*
* @param TemporaryObjectDescriptor $oTemporaryObjectDescriptor
*
* @return bool
*/
private function ConfirmTemporaryObject(TemporaryObjectDescriptor $oTemporaryObjectDescriptor): bool
{
try {
// Retrieve attributes values
$sOperation = $oTemporaryObjectDescriptor->Get('operation');
$sItemClass = $oTemporaryObjectDescriptor->Get('item_class');
$sItemId = $oTemporaryObjectDescriptor->Get('item_id');
// Get temporary object
$oTemporaryObject = MetaModel::GetObject($sItemClass, $sItemId);
if ($sOperation === TemporaryObjectHelper::OPERATION_DELETE) {
// Delete temporary object
$oTemporaryObject->DBDelete();
IssueLog::Info("Temporary Object [$sItemClass:$sItemId] removed (operation: $sOperation)", LogChannels::TEMPORARY_OBJECTS, utils::GetStackTraceAsArray());
} elseif ($sOperation === TemporaryObjectHelper::OPERATION_CREATE) {
// Send an event in case of creation confirmation
$oTemporaryObject->FireEvent(TemporaryObjectsEvents::TEMPORARY_OBJECT_EVENT_CONFIRM_CREATE);
}
// Remove temporary object descriptor entry
$oTemporaryObjectDescriptor->DBDelete();
// Log
IssueLog::Debug("Temporary Object [$sItemClass:$sItemId] $sOperation confirmed", LogChannels::TEMPORARY_OBJECTS, utils::GetStackTraceAsArray());
return true;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
}
/**
* CancelTemporaryObject.
*
* @param TemporaryObjectDescriptor $oTemporaryObjectDescriptor
*
* @return bool
*/
private function CancelTemporaryObject(TemporaryObjectDescriptor $oTemporaryObjectDescriptor): bool
{
try {
// Retrieve attributes values
$sOperation = $oTemporaryObjectDescriptor->Get('operation');
$sItemClass = $oTemporaryObjectDescriptor->Get('item_class');
$sItemId = $oTemporaryObjectDescriptor->Get('item_id');
if ($sOperation === TemporaryObjectHelper::OPERATION_CREATE) {
// Get temporary object
$oTemporaryObject = MetaModel::GetObject($sItemClass, $sItemId, false);
// Delete temporary object
if (!is_null($oTemporaryObject)) {
$oTemporaryObject->DBDelete();
}
IssueLog::Info("Temporary Object [$sItemClass:$sItemId] removed (operation: $sOperation)", LogChannels::TEMPORARY_OBJECTS, utils::GetStackTraceAsArray());
}
// Remove temporary object descriptor entry
$oTemporaryObjectDescriptor->DBDelete();
return true;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
}
/**
* Handle temporary objects.
*
* @param \DBObject $oDBObject
* @param array $aContext
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*/
public function HandleTemporaryObjects(DBObject $oDBObject, array $aContext)
{
if (array_key_exists('create', $aContext)) {
// Retrieve context information
$aContextCreation = $aContext['create'];
$sTransactionId = $aContextCreation['transaction_id'] ?? null;
$sHostClass = $aContextCreation['host_class'] ?? null;
$sHostAttCode = $aContextCreation['host_att_code'] ?? null;
// Security
if (is_null($sTransactionId) || is_null($sHostClass) || is_null($sHostAttCode)) {
return;
}
// Get host class attribute definition
try {
$oAttDef = MetaModel::GetAttributeDef($sHostClass, $sHostAttCode);
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return;
}
// If creation as temporary object requested or force for all objects
if (($oAttDef->IsParam('create_temporary_object') && $oAttDef->Get('create_temporary_object'))
|| TemporaryObjectConfig::GetInstance()->GetConfigTemporaryForce()) {
$this->CreateTemporaryObject($sTransactionId, get_class($oDBObject), $oDBObject->GetKey(), TemporaryObjectHelper::OPERATION_CREATE);
}
}
if (array_key_exists('finalize', $aContext)) {
// Retrieve context information
$aContextFinalize = $aContext['finalize'];
$sTransactionId = $aContextFinalize['transaction_id'] ?? null;
// validate temporary objects
$this->FinalizeTemporaryObjects($sTransactionId);
}
}
/**
* GarbageExpiredTemporaryObjects.
*
* @return bool
*/
public function GarbageExpiredTemporaryObjects(): bool
{
try {
// Search for expired temporary objects
$oDBObjectSet = $this->oTemporaryObjectRepository->SearchByExpired();
// Cancel temporary objects
$this->CancelTemporaryObjects($oDBObjectSet->ToArray());
return true;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return false;
}
}
}

View File

@@ -0,0 +1,203 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\TemporaryObjects;
use AttributeDateTime;
use DBObjectSet;
use DBSearch;
use Exception;
use ExceptionLog;
use MetaModel;
use TemporaryObjectDescriptor;
/**
* TemporaryObjectRepository.
*
* Repository class to perform ORM tasks.
*
* @since 3.1
*/
class TemporaryObjectRepository
{
/** @var TemporaryObjectRepository|null Singleton */
static private ?TemporaryObjectRepository $oSingletonInstance = null;
/**
* GetInstance.
*
* @return TemporaryObjectRepository
*/
public static function GetInstance(): TemporaryObjectRepository
{
if (is_null(self::$oSingletonInstance)) {
self::$oSingletonInstance = new TemporaryObjectRepository();
}
return self::$oSingletonInstance;
}
/**
* Constructor.
*
*/
private function __construct()
{
}
/**
* Create.
*
* @param string $sTempId Temporary id
* @param string $sObjectClass Object class
* @param string $sObjectKey Object key
* @param string $sOperation temporary operation on file TemporaryObjectHelper::OPERATION_CREATE or TemporaryObjectHelper::OPERATION_DELETE
*
* @return TemporaryObjectDescriptor|null
*/
public function Create(string $sTempId, string $sObjectClass, string $sObjectKey, string $sOperation): ?TemporaryObjectDescriptor
{
try {
if (!MetaModel::IsValidClass($sObjectClass)) {
throw new Exception("Class $sObjectClass is not a valid class");
}
if (MetaModel::GetObject($sObjectClass, $sObjectKey, false) === false) {
throw new Exception("Object $sObjectClass:$sObjectKey is not a valid object");
}
// Create a temporary object descriptor
/** @var \TemporaryObjectDescriptor $oTemporaryObjectDescriptor */
$oTemporaryObjectDescriptor = MetaModel::NewObject(TemporaryObjectDescriptor::class, [
'operation' => $sOperation,
'temp_id' => $sTempId,
'expiration_date' => time() + TemporaryObjectConfig::GetInstance()->GetConfigTemporaryLifetime(),
'item_class' => $sObjectClass,
'item_id' => $sObjectKey,
]);
$oTemporaryObjectDescriptor->DBInsert();
return $oTemporaryObjectDescriptor;
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return null;
}
}
/**
* SearchByTempId.
*
* @param string $sTempId temporary id
* @param bool $bReverseOrder reverse order of result
*
* @return \DBObjectSet
* @throws \MySQLException
* @throws \OQLException
*/
public function SearchByTempId(string $sTempId, bool $bReverseOrder = false): DBObjectSet
{
// Prepare OQL
$sOQL = sprintf('SELECT `%s` WHERE temp_id=:temp_id', TemporaryObjectDescriptor::class);
// Create db search
$oDbObjectSearch = DBSearch::FromOQL($sOQL);
// Create db set from db search
$oDbObjectSet = new DBObjectSet($oDbObjectSearch, [], [
'temp_id' => $sTempId,
]);
// Reverse order
if ($bReverseOrder) {
$oDbObjectSet->SetOrderBy([
'id' => false,
]);
}
return $oDbObjectSet;
}
/**
* SearchByItem.
*
* @param string $sItemClass
* @param string $sItemId
* @param bool $bReverseOrder reverse order of result
*
* @return \DBObjectSet
* @throws \MySQLException
* @throws \OQLException
*/
public function SearchByItem(string $sItemClass, string $sItemId, bool $bReverseOrder = false): DBObjectSet
{
// Prepare OQL
$sOQL = sprintf('SELECT `%s` WHERE item_class=:item_class AND item_id=:item_id', TemporaryObjectDescriptor::class);
// Create db search
$oDbObjectSearch = DBSearch::FromOQL($sOQL);
// Create db set from db search
$oDbObjectSet = new DBObjectSet($oDbObjectSearch, [], [
'item_class' => $sItemClass,
'item_id' => $sItemId,
]);
// Reverse order
if ($bReverseOrder) {
$oDbObjectSet->SetOrderBy([
'id' => false,
]);
}
return $oDbObjectSet;
}
/**
* CountTemporaryObjectsByTempId.
*
* @param string $sTempId
*
* @return int
*/
public function CountTemporaryObjectsByTempId(string $sTempId): int
{
try {
$oDbObjectSet = $this->SearchByTempId($sTempId);
// return operation success
return $oDbObjectSet->count();
}
catch (Exception $e) {
ExceptionLog::LogException($e);
return -1;
}
}
/**
* SearchByExpired.
*
* @return DBObjectSet
* @throws \OQLException
*/
public function SearchByExpired(): DBObjectSet
{
// Prepare OQL
$sOQL = sprintf('SELECT `%s` WHERE expiration_date<:now', TemporaryObjectDescriptor::class);
// Create db search
$oDbObjectSearch = DBSearch::FromOQL($sOQL);
// Create db set from db search
$sDateNow = date(AttributeDateTime::GetSQLFormat(), time());
return new DBObjectSet($oDbObjectSearch, ['id' => false], ['now' => $sDateNow]);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Service\TemporaryObjects;
use Combodo\iTop\Service\Events\Description\EventDataDescription;
use Combodo\iTop\Service\Events\Description\EventDescription;
use Combodo\iTop\Service\Events\EventService;
use Combodo\iTop\Service\Events\iEventServiceSetup;
class TemporaryObjectsEvents implements iEventServiceSetup
{
// Startup events
const TEMPORARY_OBJECT_EVENT_CONFIRM_CREATE = 'TEMPORARY_OBJECT_EVENT_CONFIRM_CREATE';
/**
* @inheritDoc
*/
public function RegisterEventsAndListeners()
{
EventService::RegisterEvent(new EventDescription(
self::TEMPORARY_OBJECT_EVENT_CONFIRM_CREATE,
[
'cmdbAbstractObject' => 'cmdbAbstractObject',
],
'The MetaModel is fully started',
'',
[
new EventDataDescription(
'object',
'The object concerned by the creation confirmation',
'DBObject',
),
new EventDataDescription(
'debug_info',
'Debug string',
'string',
),
],
'application'));
}
}

View File

@@ -0,0 +1,172 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\Test\UnitTest\Service\TemporaryObjects;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectConfig;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectRepository;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class TemporaryObjectManagerTest extends ItopDataTestCase
{
const USE_TRANSACTION = true;
const CREATE_TEST_ORG = false;
private TemporaryObjectConfig $oConfig;
private $oManager;
protected function setUp(): void
{
parent::setUp();
$this->oConfig = TemporaryObjectConfig::GetInstance();
$this->oManager = TemporaryObjectManager::GetInstance();
}
public function testCreateTemporaryObject()
{
$sTempId = 'testCreateTemporaryObject';
$this->oConfig->SetConfigTemporaryLifetime(3000);
$this->oConfig->SetConfigTemporaryForce(true);
$oDescriptor = $this->oManager->CreateTemporaryObject($sTempId, 'FakedClass', -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertNull( $oDescriptor);
$oOrg = $this->CreateTestOrganization();
$oDescriptor = $this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertNotNull( $oDescriptor);
}
public function testCancelAllTemporaryObjects()
{
$sTempId = 'testCancelAllTemporaryObjects';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->CancelAllTemporaryObjects($sTempId);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(2, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->CancelAllTemporaryObjects($sTempId);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
}
public function testExtendTemporaryObjectsLifetime()
{
$sTempId = 'testExtendTemporaryObjectsLifetime';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->assertEquals(1, $oRepository->SearchByExpired()->Count());
$this->oConfig->SetConfigTemporaryLifetime(3000);
$this->oManager->ExtendTemporaryObjectsLifetime($sTempId);
$this->assertEquals(0, $oRepository->SearchByExpired()->Count());
}
public function testGarbageExpiredTemporaryObjects()
{
$sTempId = 'testGarbageExpiredTemporaryObjects';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->assertEquals(1, $oRepository->SearchByExpired()->Count());
$this->oManager->GarbageExpiredTemporaryObjects();
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1, TemporaryObjectHelper::OPERATION_CREATE);
$this->assertEquals(2, $oRepository->SearchByExpired()->Count());
$this->oManager->GarbageExpiredTemporaryObjects();
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->GarbageExpiredTemporaryObjects();
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
}
public function testHandleCreatedTemporaryObjects()
{
$sTempId = 'testHandleTemporaryObjects';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$oOrgTemp = $this->CreateTestOrganization();
$oOrg->Set('parent_id', $oOrgTemp->GetKey());
$oOrg->DBUpdate();
$aContext = ['create' => ['transaction_id' => $sTempId, 'host_class' => get_class($oOrg), 'host_att_code' => 'parent_id',]];
$this->oConfig->SetConfigTemporaryForce(true);
$this->oConfig->SetConfigTemporaryLifetime(3000);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$this->oManager->HandleTemporaryObjects($oOrg, $aContext);
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$aContext = ['finalize' => ['transaction_id' => $sTempId,]];
$this->oManager->HandleTemporaryObjects($oOrg, $aContext);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
}
public function testHandleDeletedTemporaryObjects()
{
$sTempId = 'testHandleTemporaryObjectsDelete';
$oRepository = TemporaryObjectRepository::GetInstance();
$oOrg = $this->CreateTestOrganization();
$oOrgTemp = $this->CreateTestOrganization();
$oOrg->Set('parent_id', $oOrgTemp->GetKey());
$oOrg->DBUpdate();
// Create a temporary delete
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oTemporaryObjectDescriptor = TemporaryObjectManager::GetInstance()->CreateTemporaryObject($sTempId, get_class($oOrgTemp), $oOrgTemp->Get('id'), TemporaryObjectHelper::OPERATION_DELETE);
$oTemporaryObjectDescriptor->Set('host_class', get_class($oOrg));
$oTemporaryObjectDescriptor->Set('host_id', $oOrg->GetKey());
$oTemporaryObjectDescriptor->Set('host_att_code', 'parent_id');
$oTemporaryObjectDescriptor->DBUpdate();
$this->assertEquals(1, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$aContext = ['finalize' => ['transaction_id' => $sTempId,]];
$this->oManager->HandleTemporaryObjects($oOrg, $aContext);
$this->assertEquals(0, $oRepository->CountTemporaryObjectsByTempId($sTempId));
$oDeletedObject = \MetaModel::GetObject(get_class($oOrgTemp), $oOrgTemp->Get('id'), false);
$this->assertNull($oDeletedObject);
}
private function CreateTemporaryObject($sTempId, $oDBObject, int $iLifetime, string $sOperation)
{
$this->oConfig->SetConfigTemporaryLifetime($iLifetime);
$this->oConfig->SetConfigTemporaryForce(true);
return $this->oManager->CreateTemporaryObject($sTempId, get_class($oDBObject), $oDBObject->GetKey(), $sOperation);
}
}

View File

@@ -0,0 +1,103 @@
<?php
/*
* @copyright Copyright (C) 2010-2023 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Service\TemporaryObjects;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectConfig;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectRepository;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use DBObject;
/**
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
* @backupGlobals disabled
*/
class TemporaryObjectRepositoryTest extends ItopDataTestCase
{
const USE_TRANSACTION = true;
const CREATE_TEST_ORG = false;
private TemporaryObjectConfig $oTemporaryObjectConfig;
protected function setUp(): void
{
parent::setUp();
$this->oTemporaryObjectConfig = TemporaryObjectConfig::GetInstance();
}
public function testSearchByExpired()
{
$sTempId = 'testSearchByExpired';
$oOrg = $this->CreateTestOrganization();
$oRepository = TemporaryObjectRepository::GetInstance();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oObjectSet = $oRepository->SearchByExpired();
$this->assertEquals(0, $oObjectSet->Count());
$this->CreateTemporaryObject($sTempId, $oOrg, -1);
$oObjectSet = $oRepository->SearchByExpired();
$this->assertEquals(1, $oObjectSet->Count());
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, -1);
$oObjectSet = $oRepository->SearchByExpired();
$this->assertEquals(2, $oObjectSet->Count());
}
public function testSearchByTempId()
{
$sTempId = 'testSearchByTempId';
// First temp object
$oOrg = $this->CreateTestOrganization();
$oDescriptor = $this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oRepository = TemporaryObjectRepository::GetInstance();
$oObjectSet = $oRepository->SearchByTempId($sTempId);
$this->assertEquals(1, $oObjectSet->Count());
$oDBObject = $oObjectSet->Fetch();
$this->assertEquals($oDescriptor->GetKey(), $oDBObject->GetKey());
$this->assertEquals(get_class($oDescriptor), get_class($oDBObject));
// Second temp object
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oObjectSet = $oRepository->SearchByTempId($sTempId);
$this->assertEquals(2, $oObjectSet->Count());
}
public function testCountTemporaryObjectsByTempId()
{
$sTempId = 'testCountTemporaryObjectsByTempId';
// First temp object
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$oRepository = TemporaryObjectRepository::GetInstance();
$iCount = $oRepository->CountTemporaryObjectsByTempId($sTempId);
$this->assertEquals(1, $iCount);
// Second temp object
$oOrg = $this->CreateTestOrganization();
$this->CreateTemporaryObject($sTempId, $oOrg, 3000);
$iCount = $oRepository->CountTemporaryObjectsByTempId($sTempId);
$this->assertEquals(2, $iCount);
}
private function CreateTemporaryObject($sTempId, DBObject $oDBObject, int $iLifetime)
{
$this->oTemporaryObjectConfig->SetConfigTemporaryLifetime($iLifetime);
$this->oTemporaryObjectConfig->SetConfigTemporaryForce(true);
$oManager = TemporaryObjectManager::GetInstance();
return $oManager->CreateTemporaryObject($sTempId, get_class($oDBObject), $oDBObject->GetKey(), TemporaryObjectHelper::OPERATION_CREATE);
}
}