mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-21 09:38:48 +02:00
N°6043 - Booking: Add prerequisites in iTop core - CRUD extensibility (#520)
This commit is contained in:
@@ -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`);");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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' => '',
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -612,6 +612,8 @@ class LogChannels
|
||||
|
||||
public const PORTAL = 'portal';
|
||||
|
||||
public const TEMPORARY_OBJECTS = 'TemporaryObjects';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.1.0
|
||||
|
||||
@@ -22,6 +22,7 @@ SetupWebPage::AddModule(
|
||||
//
|
||||
'datamodel' => array(
|
||||
'main.itop-structure.php',
|
||||
'src/Model/TemporaryObjectDescriptor.php',
|
||||
),
|
||||
'data.struct' => array(
|
||||
),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
111
sources/Service/TemporaryObjects/TemporaryObjectConfig.php
Normal file
111
sources/Service/TemporaryObjects/TemporaryObjectConfig.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
45
sources/Service/TemporaryObjects/TemporaryObjectGC.php
Normal file
45
sources/Service/TemporaryObjects/TemporaryObjectGC.php
Normal 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();
|
||||
}
|
||||
}
|
||||
45
sources/Service/TemporaryObjects/TemporaryObjectHelper.php
Normal file
45
sources/Service/TemporaryObjects/TemporaryObjectHelper.php
Normal 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;
|
||||
}
|
||||
}
|
||||
400
sources/Service/TemporaryObjects/TemporaryObjectManager.php
Normal file
400
sources/Service/TemporaryObjects/TemporaryObjectManager.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
203
sources/Service/TemporaryObjects/TemporaryObjectRepository.php
Normal file
203
sources/Service/TemporaryObjects/TemporaryObjectRepository.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
47
sources/Service/TemporaryObjects/TemporaryObjectsEvents.php
Normal file
47
sources/Service/TemporaryObjects/TemporaryObjectsEvents.php
Normal 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'));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user