diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 50a5e1ab4..1f71e33d9 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -68,6 +68,7 @@ class CMDBSource /** @var mysqli $m_oMysqli */ protected static $m_oMysqli; + protected static $oMySQLiForQuery; /** * @var int number of level for nested transactions : 0 if no transaction was ever opened, +1 for each 'START TRANSACTION' sent @@ -134,6 +135,7 @@ class CMDBSource self::$m_sDBTlsCA = empty($sTlsCA) ? null : $sTlsCA; self::$m_oMysqli = self::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, true); + self::SetMySQLiForQuery(self::$m_oMysqli); } /** @@ -151,8 +153,6 @@ class CMDBSource public static function GetMysqliInstance( $sDbHost, $sUser, $sPwd, $sSource = '', $bTlsEnabled = false, $sTlsCa = null, $bCheckTlsAfterConnection = false ) { - $oMysqli = null; - $sServer = null; $iPort = null; self::InitServerAndPort($sDbHost, $sServer, $iPort); @@ -450,6 +450,24 @@ class CMDBSource return self::$m_oMysqli; } + /** + * @return + */ + private static function GetMySQLiForQuery() + { + return self::$oMySQLiForQuery; + } + + + /** + * Used for test purpose (mysqli mock) + * @param $oMySQLi + */ + private static function SetMySQLiForQuery($oMySQLi) + { + self::$oMySQLiForQuery = $oMySQLi; + } + public static function GetErrNo() { if (self::$m_oMysqli->errno != 0) @@ -585,7 +603,7 @@ class CMDBSource */ private static function DBQuery($sSql) { - $sShortSQL = str_replace("\n", " ", substr($sSql, 0, 120)); + $sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSql, 0, 180)), 0, 150); if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) { IssueLog::Trace("$sShortSQL", 'cmdbsource'); } @@ -593,7 +611,7 @@ class CMDBSource $oKPI = new ExecutionKPI(); try { - $oResult = self::$m_oMysqli->query($sSql); + $oResult = self::GetMySQLiForQuery()->query($sSql); } catch (mysqli_sql_exception $e) { @@ -657,7 +675,7 @@ class CMDBSource ); DeadLockLog::Info($sMessage, $iMySqlErrorNo, $aLogContext); - IssueLog::Error($sMessage, 'DeadLock', $e->getMessage()); + IssueLog::Error($sMessage, LogChannels::DEADLOCK, $e->getMessage()); } /** @@ -855,7 +873,7 @@ class CMDBSource $oKPI = new ExecutionKPI(); try { - $oResult = self::$m_oMysqli->query($sSql); + $oResult = self::GetMySQLiForQuery()->query($sSql); } catch(mysqli_sql_exception $e) { @@ -895,7 +913,7 @@ class CMDBSource $oKPI = new ExecutionKPI(); try { - $oResult = self::$m_oMysqli->query($sSql); + $oResult = self::GetMySQLiForQuery()->query($sSql); } catch(mysqli_sql_exception $e) { @@ -977,7 +995,7 @@ class CMDBSource { try { - $oResult = self::$m_oMysqli->query($sSql); + $oResult = self::GetMySQLiForQuery()->query($sSql); } catch(mysqli_sql_exception $e) { @@ -1465,7 +1483,7 @@ class CMDBSource $sSql = "SELECT * FROM `$sTable`"; try { - $oResult = self::$m_oMysqli->query($sSql); + $oResult = self::GetMySQLiForQuery()->query($sSql); } catch(mysqli_sql_exception $e) { diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 9a49a4bad..222e63205 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -3271,13 +3271,6 @@ abstract class DBObject implements iDisplay $this->DBWriteLinks(); $this->WriteExternalAttributes(); - // following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore) - // new values are already in the object (call {@see DBObject::Get()} to get them) - // call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values - $this->m_bDirty = false; - $this->m_aTouchedAtt = array(); - $this->m_aModifiedAtt = array(); - if (count($aChanges) != 0) { $this->RecordAttChanges($aChanges, $aOriginalValues); diff --git a/core/inlineimage.class.inc.php b/core/inlineimage.class.inc.php index 8dc78d89f..583b5c4b8 100644 --- a/core/inlineimage.class.inc.php +++ b/core/inlineimage.class.inc.php @@ -198,31 +198,29 @@ class InlineImage extends DBObject $sOQL = 'SELECT InlineImage WHERE temp_id = :temp_id'; $oSearch = DBObjectSearch::FromOQL($sOQL); $oSet = new DBObjectSet($oSearch, array(), array('temp_id' => $sTempId)); - $aInlineImagesId = array(); - while($oInlineImage = $oSet->Fetch()) - { - $aInlineImagesId[] = $oInlineImage->GetKey(); + $aInlineImagesId = array(); + while ($oInlineImage = $oSet->Fetch()) { + $aInlineImagesId[] = $oInlineImage->GetKey(); $oInlineImage->SetItem($oObject); $oInlineImage->Set('temp_id', ''); $oInlineImage->DBUpdate(); } - IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', 'InlineImage', array( - '$sObjectClass' => get_class($oObject), - '$sTransactionId' => $iTransactionId, - '$sTempId' => $sTempId, - '$aInlineImagesId' => $aInlineImagesId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - )); + IssueLog::Trace('FinalizeInlineImages (see $aInlineImagesId for the id list)', LogChannels::INLINE_IMAGE, array( + '$sObjectClass' => get_class($oObject), + '$sTransactionId' => $iTransactionId, + '$sTempId' => $sTempId, + '$aInlineImagesId' => $aInlineImagesId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + )); } - else - { - IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', 'InlineImage', array( - '$sObjectClass' => get_class($oObject), - '$sTransactionId' => $iTransactionId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - )); + else { + IssueLog::Trace('FinalizeInlineImages "error" $iTransactionId is null', LogChannels::INLINE_IMAGE, array( + '$sObjectClass' => get_class($oObject), + '$sTransactionId' => $iTransactionId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + )); } } @@ -253,12 +251,12 @@ class InlineImage extends DBObject $aInlineImagesId[] = $oInlineImage->GetKey(); $oInlineImage->DBDelete(); } - IssueLog::Trace('OnFormCancel', 'InlineImage', array( - '$sTempId' => $sTempId, - '$aInlineImagesId' => $aInlineImagesId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - )); + IssueLog::Trace('OnFormCancel', LogChannels::INLINE_IMAGE, array( + '$sTempId' => $sTempId, + '$aInlineImagesId' => $aInlineImagesId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + )); } /** @@ -608,17 +606,17 @@ JS */ protected function AfterInsert() { - IssueLog::Trace(__METHOD__, 'InlineImage', array( - 'id' => $this->GetKey(), - 'expire' => $this->Get('expire'), - 'temp_id' => $this->Get('temp_id'), - 'item_class' => $this->Get('item_class'), - 'item_id' => $this->Get('item_id'), - 'item_org_id' => $this->Get('item_org_id'), - 'secret' => $this->Get('secret'), - 'user' => $sUser = UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array( + 'id' => $this->GetKey(), + 'expire' => $this->Get('expire'), + 'temp_id' => $this->Get('temp_id'), + 'item_class' => $this->Get('item_class'), + 'item_id' => $this->Get('item_id'), + 'item_org_id' => $this->Get('item_org_id'), + 'secret' => $this->Get('secret'), + 'user' => $sUser = UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); parent::AfterInsert(); @@ -629,17 +627,17 @@ JS */ protected function AfterUpdate() { - IssueLog::Trace(__METHOD__, 'InlineImage', array( - 'id' => $this->GetKey(), - 'expire' => $this->Get('expire'), - 'temp_id' => $this->Get('temp_id'), - 'item_class' => $this->Get('item_class'), - 'item_id' => $this->Get('item_id'), - 'item_org_id' => $this->Get('item_org_id'), - 'secret' => $this->Get('secret'), - 'user' => $sUser = UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array( + 'id' => $this->GetKey(), + 'expire' => $this->Get('expire'), + 'temp_id' => $this->Get('temp_id'), + 'item_class' => $this->Get('item_class'), + 'item_id' => $this->Get('item_id'), + 'item_org_id' => $this->Get('item_org_id'), + 'secret' => $this->Get('secret'), + 'user' => $sUser = UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); parent::AfterUpdate(); @@ -650,17 +648,17 @@ JS */ protected function AfterDelete() { - IssueLog::Trace(__METHOD__, 'InlineImage', array( - 'id' => $this->GetKey(), - 'expire' => $this->Get('expire'), - 'temp_id' => $this->Get('temp_id'), - 'item_class' => $this->Get('item_class'), - 'item_id' => $this->Get('item_id'), - 'item_org_id' => $this->Get('item_org_id'), - 'secret' => $this->Get('secret'), - 'user' => $sUser = UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + IssueLog::Trace(__METHOD__, LogChannels::INLINE_IMAGE, array( + 'id' => $this->GetKey(), + 'expire' => $this->Get('expire'), + 'temp_id' => $this->Get('temp_id'), + 'item_class' => $this->Get('item_class'), + 'item_id' => $this->Get('item_id'), + 'item_org_id' => $this->Get('item_org_id'), + 'secret' => $this->Get('secret'), + 'user' => $sUser = UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); parent::AfterDelete(); diff --git a/core/log.class.inc.php b/core/log.class.inc.php index 147a82044..a8b94091f 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -518,12 +518,9 @@ class FileLog { flock($hLogFile, LOCK_EX); $sDate = date('Y-m-d H:i:s'); - if (empty($aContext)) - { + if (empty($aContext)) { fwrite($hLogFile, "$sDate | $sText\n"); - } - else - { + } else { $sContext = var_export($aContext, true); fwrite($hLogFile, "$sDate | $sText\n$sContext\n"); } @@ -534,6 +531,21 @@ class FileLog } } + +/** + * Simple enum like class to factorize channels values as constants + * Channels are used especially as parameters in {@see \LogAPI} methods + * + * @since 2.7.5 3.0.0 N°4012 + */ +class LogChannels +{ + const DEADLOCK = 'DeadLock'; + const INLINE_IMAGE = 'InlineImage'; + const PORTAL = 'portal'; +} + + abstract class LogAPI { public const CHANNEL_DEFAULT = ''; @@ -546,6 +558,7 @@ abstract class LogAPI public const LEVEL_TRACE = 'Trace'; /** * @var string default log level + * @see GetMinLogLevel * @used-by GetLevelDefault * @since 2.7.1 N°2977 */ diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php index 474f26b8c..ce48aed3c 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/BrowseBrickController.php @@ -31,6 +31,7 @@ use DBObjectSet; use DBSearch; use FieldExpression; use IssueLog; +use LogChannels; use MetaModel; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -441,9 +442,9 @@ class BrowseBrickController extends BrickController } } - IssueLog::Debug('Portal BrowseBrick query', 'portal', array( - 'portalId' => $sPortalId, - 'brickId' => $sBrickId, + IssueLog::Debug('Portal BrowseBrick query', LogChannels::PORTAL, array( + 'sPortalId' => $sPortalId, + 'sBrickId' => $sBrickId, 'oql' => $oSet->GetFilter()->ToOQL(), )); diff --git a/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php b/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php index dfc1a59e1..28853bfc2 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Controller/ManageBrickController.php @@ -44,6 +44,7 @@ use FieldExpression; use iPopupMenuExtension; use IssueLog; use JSButtonItem; +use LogChannels; use MetaModel; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -816,10 +817,10 @@ class ManageBrickController extends BrickController ); } - IssueLog::Debug('Portal ManageBrick query', 'portal', array( - 'portalId' => $sPortalId, - 'brickId' => $sBrickId, - 'groupingTab' => $sGroupingTab, + IssueLog::Debug('Portal ManageBrick query', LogChannels::PORTAL, array( + 'sPortalId' => $sPortalId, + 'sBrickId' => $sBrickId, + 'sGroupingTab' => $sGroupingTab, 'oql' => $oSet->GetFilter()->ToOQL(), 'aGroupingTabs' => $aGroupingTabs, )); diff --git a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php index 043ad462f..fe20324fb 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Form/ObjectFormManager.php @@ -78,6 +78,12 @@ class ObjectFormManager extends FormManager protected $aFormProperties; /** @var array $aCallbackUrls */ protected $aCallbackUrls = array(); + /** + * List of hidden fields, used for form update (eg. remove them from the form regarding they dependencies) + * @var array $aHiddenFieldsId + * @since 2.7.5 + */ + protected $aHiddenFieldsId = array(); /** * Creates an instance of \Combodo\iTop\Portal\Form\ObjectFormManager from JSON data that must contain at least : @@ -954,6 +960,8 @@ class ObjectFormManager extends FormManager if($oField->GetHidden() === false) { $oForm->AddField($oField); + } else { + $this->aHiddenFieldsId[]=$oField->GetId(); } } @@ -1485,4 +1493,22 @@ class ObjectFormManager extends FormManager } return $oChangeOp; } + + /** + * @return array + * @since 2.7.5 + */ + public function GetHiddenFieldsId() + { + return $this->aHiddenFieldsId; + } + + /** + * @param array $aHiddenFieldsId + * @since 2.7.5 + */ + public function SetHiddenFieldsId($aHiddenFieldsId) + { + $this->aHiddenFieldsId = $aHiddenFieldsId; + } } diff --git a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php index 1e4deb3b9..f3cebbe03 100644 --- a/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php +++ b/datamodels/2.x/itop-portal-base/portal/src/Helper/ObjectFormHandlerHelper.php @@ -280,8 +280,8 @@ class ObjectFormHandlerHelper ->SetFormProperties($aFormProperties); $oFormManager->Build(); - - // Check the number of editable fields + $aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId(); + // Check the number of editable fields $aFormData['editable_fields_count'] = $oFormManager->GetForm()->GetEditableFieldCount(); } else @@ -388,6 +388,7 @@ class ObjectFormHandlerHelper $aFormData['object_state'] = $oFormManager->GetObject()->GetState(); $aFormData['fieldset'] = $aFieldSetData; $aFormData['display_mode'] = (isset($aFormProperties['properties'])) ? $aFormProperties['properties']['display_mode'] : ApplicationHelper::FORM_DEFAULT_DISPLAY_MODE; + $aFormData['hidden_fields'] = $oFormManager->GetHiddenFieldsId(); // - Set a text to be copied on title if the object is not in creation if($sMode !== static::ENUM_MODE_CREATE && !empty($sObjectId)) { diff --git a/js/form_handler.js b/js/form_handler.js index c3eff5c67..c709c5e43 100644 --- a/js/form_handler.js +++ b/js/form_handler.js @@ -151,9 +151,16 @@ $(function() // Intended for overloading in derived classes _onUpdateSuccess: function(oData, sFormPath) { + var me = this; + if(oData.form.hidden_fields !== undefined) + { + $.each(oData.form.hidden_fields, function( index, value ) { + me.element.find('[data-form-path="' + sFormPath + '"][data-field-id="'+value+'"][data-attribute-flag-hidden="false"]').children().remove(); + }); + } if(oData.form.updated_fields !== undefined) { - this.element.find('[data-form-path="' + sFormPath + '"]').trigger('update_form', {updated_fields: oData.form.updated_fields}); + me.element.find('[data-form-path="' + sFormPath + '"]').trigger('update_form', {updated_fields: oData.form.updated_fields}); } }, // Intended for overloading in derived classes diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 578158e19..871fc77e5 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -2491,15 +2491,15 @@ EOF $aResult['height'] = $aDimensions['height']; } - IssueLog::Trace('InlineImage created', 'InlineImage', array( - '$operation' => $operation, - '$aResult' => $aResult, - 'secret' => $oAttachment->Get('secret'), - 'temp_id' => $sTempId, - 'item_class' => $sObjClass, - 'user' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array( + '$operation' => $operation, + '$aResult' => $aResult, + 'secret' => $oAttachment->Get('secret'), + 'temp_id' => $sTempId, + 'item_class' => $sObjClass, + 'user' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); } else @@ -2542,15 +2542,15 @@ EOF $oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess $iAttId = $oAttachment->DBInsert(); - IssueLog::Trace('InlineImage created', 'InlineImage', array( - '$operation' => $operation, - 'secret' => $oAttachment->Get('secret'), - 'temp_id' => $sTempId, - 'item_class' => $sObjClass, - 'user' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); + IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array( + '$operation' => $operation, + 'secret' => $oAttachment->Get('secret'), + 'temp_id' => $sTempId, + 'item_class' => $sObjClass, + 'user' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); } } catch (FileUploadException $e) diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 21e284cd1..1fe4a515b 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -64,6 +64,14 @@ class MFCompiler /** @var \ThemeHandlerService */ protected static $oThemeHandlerService; + /** + * Path to the "calculate hKeys" file + * If this file is present, then we don't recalculate hkeys + * + * @var string + */ + public const REBUILD_HKEYS_NEVER= APPROOT.'data/.setup-rebuild-hkeys-never'; + /** @var \ModelFactory */ protected $oFactory; @@ -167,6 +175,20 @@ class MFCompiler unlink(static::USE_SYMBOLIC_LINKS_FILE_PATH); } + /** + * @return bool possible return values : + * * if flag is present true, false otherwise + * + * @uses \file_exists() + * @uses REBUILD_HKEYS_NEVER + * + * @since 2.7.5 + */ + public static function SkipRebuildHKeys(): bool + { + return (file_exists(static::REBUILD_HKEYS_NEVER)); + } + /** * Compile the data model into PHP files and data structures * diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 5f9023857..988a9bf1d 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -598,11 +598,16 @@ class RunTimeEnvironment $this->log_ok("Database structure successfully updated."); // Check (and update only if it seems needed) the hierarchical keys - ob_start(); - MetaModel::CheckHKeys(false /* bDiagnosticsOnly */, true /* bVerbose*/, true /* bForceUpdate */); // Since in 1.2-beta the detection was buggy, let's force the rebuilding of HKeys - $sFeedback = ob_get_clean(); - $this->log_ok("Hierchical keys rebuilt: $sFeedback"); - + if (MFCompiler::SkipRebuildHKeys()) { + $this->log_ok("Hierchical keys are NOT rebuilt due to the presence of the \"data/.setup-rebuild-hkeys-never\" file"); + } else { + ob_start(); + $this->log_ok("Start of rebuilt of hierchical keys. If you have problems with this step, you can skip it by creating a \".setup-rebuild-hkeys-never\" file in data"); + MetaModel::CheckHKeys(false /* bDiagnosticsOnly */, true /* bVerbose*/, true /* bForceUpdate */); // Since in 1.2-beta the detection was buggy, let's force the rebuilding of HKeys + $sFeedback = ob_get_clean(); + $this->log_ok("Hierchical keys rebuilt: $sFeedback"); + } + // Check (and fix) data sync configuration ob_start(); MetaModel::CheckDataSources(false /*$bDiagnostics*/, true/*$bVerbose*/); diff --git a/test/core/CMDBSourceTest.php b/test/core/CMDBSource/CMDBSourceTest.php similarity index 93% rename from test/core/CMDBSourceTest.php rename to test/core/CMDBSource/CMDBSourceTest.php index 229db18b5..a787c0316 100644 --- a/test/core/CMDBSourceTest.php +++ b/test/core/CMDBSource/CMDBSourceTest.php @@ -113,11 +113,6 @@ class CMDBSourceTest extends ItopTestCase "ENUM('value 1 (with parenthesis)','value 2') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", "enum('value 1 (with parenthesis)','value 3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", ), - 'ENUM with different values, containing parenthesis' => array( - false, - "ENUM('value 1 ) with parenthesis)','value 2') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", - "enum('value 1 (with parenthesis)','value 3') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", - ), ); } } diff --git a/test/core/CMDBSource/DeadLockInjection.php b/test/core/CMDBSource/DeadLockInjection.php new file mode 100644 index 000000000..12aa2e4c6 --- /dev/null +++ b/test/core/CMDBSource/DeadLockInjection.php @@ -0,0 +1,63 @@ +bMustFail = true; + $this->iRequestCount = 0; + $this->iFailAt = $iFailAt; + $this->bShowRequest = true; + } + + /** + * @param bool $bShowRequest + */ + public function SetShowRequest($bShowRequest) + { + $this->bShowRequest = $bShowRequest; + } + + + public function query($sSQL) + { + if (utils::StartsWith($sSQL, "SELECT")) { + return; + } + if ($this->bShowRequest) { + $sShortSQL = substr(preg_replace("/\s+/", " ", substr($sSQL, 0, 180)), 0, 150); + echo "$sShortSQL\n"; + } + if (CMDBSource::IsInsideTransaction() && $this->bMustFail) { + $this->iRequestCount++; + if ($this->iRequestCount == $this->iFailAt) { + echo "Generating a FAKE DEADLOCK\n"; + IssueLog::Trace("Generating a FAKE DEADLOCK", 'cmdbsource'); + throw new MySQLException("FAKE DEADLOCK", [], new Exception("FAKE DEADLOCK", 1213)); + } + } + } +} \ No newline at end of file diff --git a/test/core/CMDBSource/TransactionsTest.php b/test/core/CMDBSource/TransactionsTest.php new file mode 100644 index 000000000..4c07226d4 --- /dev/null +++ b/test/core/CMDBSource/TransactionsTest.php @@ -0,0 +1,248 @@ +oMySQLiMock = new DeadLockInjection(); + + $oMockMysqli = $this->getMockBuilder('mysqli') + ->setMethods(['query']) + ->getMock(); + $oMockMysqli->expects($this->any()) + ->method('query') + ->will($this->returnCallback( + function ($sSql) use ($oInitialMysqli) { + $this->oMySQLiMock->query($sSql); + return $oInitialMysqli->query($sSql); + } + )); + + $this->InvokeNonPublicStaticMethod('CMDBSource', 'SetMySQLiForQuery', [$oMockMysqli]); + } + + /** + * Test DBInsertNoReload database transaction by provoking deadlock exceptions + * + * @dataProvider DBInsertProvider + * + * @param int $iFailAt Specify the request occurrence that fails + * @param bool $bIsInDB Indicates if the object must have been created or not + * + * @throws \ArchivedObjectException + * @throws \CoreCannotSaveObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \DeleteException + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + public function testDBInsert($iFailAt, $bIsInDB) + { + // Create a UserRequest with 2 contacts + $oTicket = MetaModel::NewObject('UserRequest', [ + 'ref' => 'Test Ticket', + 'title' => 'Create OK', + 'description' => 'Create OK', + 'caller_id' => 15, + 'org_id' => 3, + ]); + $oLinkSet = $oTicket->Get('contacts_list'); + $oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 6])); + $oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 7])); + + $this->oMySQLiMock->SetFailAt($iFailAt); + $this->debug("---> DBInsert()"); + try { + $oTicket->DBWrite(); + } + catch (Exception $e) { + // If an exception occurs must be a deadlock + $this->assertTrue(CMDBSource::IsDeadlockException($e), $e->getMessage()); + } + + // Verify if the ticket is considered as saved in the database + $this->assertEquals($bIsInDB, !$oTicket->IsNew()); + + if (!$oTicket->IsNew()) { + $this->oMySQLiMock->SetShowRequest(false); + // Delete created objects + $oTicket->DBDelete(); + } + } + + public function DBInsertProvider() + { + return [ + "Normal case" => ['iFailAt' => -1, 'bIsInDB' => true], + "ticket" => ['iFailAt' => 1, 'bIsInDB' => false], + "ticket_request" => ['iFailAt' => 2, 'bIsInDB' => false], + "priv_change" => ['iFailAt' => 3, 'bIsInDB' => false], + "priv_changeop" => ['iFailAt' => 4, 'bIsInDB' => false], + "priv_changeop_create" => ['iFailAt' => 5, 'bIsInDB' => false], + "History 4" => ['iFailAt' => 6, 'bIsInDB' => false], + "History 5" => ['iFailAt' => 7, 'bIsInDB' => false], + "History 6" => ['iFailAt' => 8, 'bIsInDB' => false], + "History 7" => ['iFailAt' => 9, 'bIsInDB' => false], + "History 8" => ['iFailAt' => 10, 'bIsInDB' => false], + "History 9" => ['iFailAt' => 11, 'bIsInDB' => false], + "History 10" => ['iFailAt' => 12, 'bIsInDB' => false], + "History 11" => ['iFailAt' => 13, 'bIsInDB' => false], + "History 12" => ['iFailAt' => 14, 'bIsInDB' => false], + "History 13" => ['iFailAt' => 15, 'bIsInDB' => false], + "History 14" => ['iFailAt' => 16, 'bIsInDB' => false], + "History 15" => ['iFailAt' => 17, 'bIsInDB' => false], + // For 3.0 when CRUD sequence is ok +// "History 16" => ['iFailAt' => 18, 'bIsInDB' => false], +// "History 17" => ['iFailAt' => 19, 'bIsInDB' => false], +// "History 18" => ['iFailAt' => 20, 'bIsInDB' => false], +// "History 19" => ['iFailAt' => 21, 'bIsInDB' => false], +// "History 20" => ['iFailAt' => 22, 'bIsInDB' => false], +// "History 21" => ['iFailAt' => 23, 'bIsInDB' => false], +// "History 22" => ['iFailAt' => 24, 'bIsInDB' => false], +// "History 23" => ['iFailAt' => 25, 'bIsInDB' => false], +// "History 24" => ['iFailAt' => 26, 'bIsInDB' => false], +// "History 25" => ['iFailAt' => 27, 'bIsInDB' => false], +// "History 26" => ['iFailAt' => 28, 'bIsInDB' => false], +// "History 27" => ['iFailAt' => 29, 'bIsInDB' => false], +// "History 28" => ['iFailAt' => 30, 'bIsInDB' => false], +// "History 29" => ['iFailAt' => 31, 'bIsInDB' => false], +// "History 30" => ['iFailAt' => 32, 'bIsInDB' => false], +// "History 31" => ['iFailAt' => 33, 'bIsInDB' => false], +// "History 32" => ['iFailAt' => 34, 'bIsInDB' => false], +// "History 33" => ['iFailAt' => 35, 'bIsInDB' => false], +// "History 34" => ['iFailAt' => 36, 'bIsInDB' => false], +// "History 35" => ['iFailAt' => 37, 'bIsInDB' => false], +// "History 36" => ['iFailAt' => 38, 'bIsInDB' => false], +// "History 37" => ['iFailAt' => 39, 'bIsInDB' => false], +// "History 38" => ['iFailAt' => 40, 'bIsInDB' => false], + ]; + } + + /** + * Test DBUpdate database transaction by provoking deadlock exceptions + * + * @dataProvider DBUpdateProvider + * @param $iFailAt + * + * @throws \ArchivedObjectException + * @throws \CoreCannotSaveObjectException + * @throws \CoreException + * @throws \CoreUnexpectedValue + * @throws \DeleteException + * @throws \MySQLException + * @throws \MySQLHasGoneAwayException + * @throws \OQLException + */ + public function testDBUpdate($iFailAt, $bIsModified) + { + // Create a UserRequest into the database with 2 contacts + $oTicket = MetaModel::NewObject('UserRequest', [ + 'ref' => 'Test Ticket', + 'title' => 'Create OK', + 'description' => 'Create OK', + 'solution' => 'Create OK', + 'caller_id' => 15, + 'org_id' => 3, + ]); + $oLinkSet = $oTicket->Get('contacts_list'); + $oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 6])); + $oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 7])); + //$oTicket->Set('contacts_list', $oLinkSet); + + $this->oMySQLiMock->SetShowRequest(false); + $oTicket->DBWrite(); + + // Verify that the object is considered as saved in the database + $this->assertEquals(true, !$oTicket->IsNew()); + + // Reload from db + $oTicket = MetaModel::GetObject('UserRequest', $oTicket->GetKey()); + $oTicket->Set('description', 'Update OK'); + $oTicket->Set('solution', 'Test OK'); + $oLinkSet = $oTicket->Get('contacts_list'); + $oLinkSet->AddItem(MetaModel::NewObject('lnkContactToTicket', ['contact_id' => 8])); + $oTicket->Set('contacts_list', $oLinkSet); + + // Provoke an error during the update + $this->oMySQLiMock->SetFailAt($iFailAt); + $this->debug("---> DBUpdate()"); + try { + $oTicket->DBWrite(); + } + catch (Exception $e) { + // If an exception occurs must be a deadlock + $this->assertTrue(CMDBSource::IsDeadlockException($e)); + } + + // Verify if the ticket is considered as saved in the database + $this->assertEquals($bIsModified, $oTicket->IsModified()); + + // Reload from db after the update to check the value present in the database + $oTicket = MetaModel::GetObject('UserRequest', $oTicket->GetKey()); + if ($bIsModified) { + $this->assertEquals('Create OK', $oTicket->Get('solution')); + } else { + $this->assertEquals('Test OK', $oTicket->Get('solution')); + } + + if (!$oTicket->IsNew()) { + $this->oMySQLiMock->SetShowRequest(false); + // Delete created objects + $oTicket->DBDelete(); + } + } + + public function DBUpdateProvider() + { + return [ + "Normal case" => ['iFailAt' => -1, 'bIsModified' => false], + "ticket_request" => ['iFailAt' => 1, 'bIsModified' => true], + "lnkcontacttoticket" => ['iFailAt' => 2, 'bIsModified' => true], + "History 1" => ['iFailAt' => 3, 'bIsModified' => true], + "History 2" => ['iFailAt' => 4, 'bIsModified' => true], + "History 3" => ['iFailAt' => 5, 'bIsModified' => true], + "History 4" => ['iFailAt' => 6, 'bIsModified' => true], + "History 5" => ['iFailAt' => 7, 'bIsModified' => true], + "History 6" => ['iFailAt' => 8, 'bIsModified' => true], + "History 7" => ['iFailAt' => 9, 'bIsModified' => true], + "History 8" => ['iFailAt' => 10, 'bIsModified' => true], + "History 9" => ['iFailAt' => 11, 'bIsModified' => true], + "History 10" => ['iFailAt' => 12, 'bIsModified' => true], + "History 11" => ['iFailAt' => 13, 'bIsModified' => true], + "History 12" => ['iFailAt' => 14, 'bIsModified' => true], + "History 13" => ['iFailAt' => 15, 'bIsModified' => true], + ]; + } +} \ No newline at end of file