diff --git a/application/exceptions/CoreCannotSaveObjectException.php b/application/exceptions/CoreCannotSaveObjectException.php index 9e70998fe..d7e4235d9 100644 --- a/application/exceptions/CoreCannotSaveObjectException.php +++ b/application/exceptions/CoreCannotSaveObjectException.php @@ -28,14 +28,14 @@ class CoreCannotSaveObjectException extends CoreException * * @param array $aContextData containing at least those keys : issues, id, class */ - public function __construct($aContextData) + public function __construct($aContextData, $oPrevious = null) { $this->aIssues = $aContextData['issues']; $this->iObjectId = $aContextData['id']; $this->sObjectClass = $aContextData['class']; $sIssues = implode(', ', $this->aIssues); - parent::__construct($sIssues, $aContextData); + parent::__construct($sIssues, $aContextData, '', $oPrevious); } /** diff --git a/core/cmdbsource.class.inc.php b/core/cmdbsource.class.inc.php index 81f35d83e..50a5e1ab4 100644 --- a/core/cmdbsource.class.inc.php +++ b/core/cmdbsource.class.inc.php @@ -585,6 +585,11 @@ class CMDBSource */ private static function DBQuery($sSql) { + $sShortSQL = str_replace("\n", " ", substr($sSql, 0, 120)); + if (substr_compare($sShortSQL, "SELECT", 0, strlen("SELECT")) !== 0) { + IssueLog::Trace("$sShortSQL", 'cmdbsource'); + } + $oKPI = new ExecutionKPI(); try { @@ -668,10 +673,15 @@ class CMDBSource */ private static function StartTransaction() { + $aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3); + $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()'; $bHasExistingTransactions = self::IsInsideTransaction(); if (!$bHasExistingTransactions) { + IssueLog::Trace("START TRANSACTION $sCaller", 'cmdbsource'); self::DBQuery('START TRANSACTION'); + } else { + IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") START TRANSACTION $sCaller", 'cmdbsource'); } self::AddTransactionLevel(); @@ -689,9 +699,12 @@ class CMDBSource */ private static function Commit() { + $aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3); + $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()'; if (!self::IsInsideTransaction()) { // should not happen ! + IssueLog::Error("No Transaction COMMIT $sCaller", 'cmdbsource'); throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null); } @@ -699,8 +712,10 @@ class CMDBSource if (self::IsInsideTransaction()) { + IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") COMMIT $sCaller", 'cmdbsource'); return; } + IssueLog::Trace("COMMIT $sCaller", 'cmdbsource'); self::DBQuery('COMMIT'); } @@ -719,17 +734,22 @@ class CMDBSource */ private static function Rollback() { + $aStackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT , 3); + $sCaller = 'From '.$aStackTrace[1]['file'].'('.$aStackTrace[1]['line'].'): '.$aStackTrace[2]['class'].'->'.$aStackTrace[2]['function'].'()'; if (!self::IsInsideTransaction()) { // should not happen ! + IssueLog::Error("No Transaction ROLLBACK $sCaller", 'cmdbsource'); throw new MySQLNoTransactionException('Trying to commit transaction whereas none have been started !', null); } self::RemoveLastTransactionLevel(); if (self::IsInsideTransaction()) { + IssueLog::Trace("Ignore nested (".self::$m_iTransactionLevel.") ROLLBACK $sCaller", 'cmdbsource'); return; } + IssueLog::Trace("ROLLBACK $sCaller", 'cmdbsource'); self::DBQuery('ROLLBACK'); } @@ -779,6 +799,18 @@ class CMDBSource self::$m_iTransactionLevel = 0; } + public static function IsDeadlockException(Exception $e) + { + while ($e instanceof Exception) { + if (($e instanceof MySQLException) && ($e->getCode() == 1213)) { + return true; + } + $e = $e->getPrevious(); + } + return false; + } + + public static function GetInsertId() { $iRes = self::$m_oMysqli->insert_id; diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 8c14f20fd..a28421fad 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -2778,50 +2778,72 @@ abstract class DBObject implements iDisplay } } + $iTransactionRetry = 1; $bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled'); - try + if ($bIsTransactionEnabled) { - if ($bIsTransactionEnabled) - { - CMDBSource::Query('START TRANSACTION'); - } - - // First query built upon on the root class, because the ID must be created first - $this->m_iKey = $this->DBInsertSingleTable($sRootClass); - - // Then do the leaf class, if different from the root class - if ($sClass != $sRootClass) - { - $this->DBInsertSingleTable($sClass); - } - - // Then do the other classes - foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) - { - if ($sParentClass == $sRootClass) - { - continue; - } - $this->DBInsertSingleTable($sParentClass); - } - - $this->OnObjectKeyReady(); - - $this->DBWriteLinks(); - $this->WriteExternalAttributes(); - - if ($bIsTransactionEnabled) - { - CMDBSource::Query('COMMIT'); - } + // TODO Deep clone this object before the transaction (to use it in case of rollback) + // $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count'); + $iTransactionRetryCount = 1; + $iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms'); + $iTransactionRetry = $iTransactionRetryCount; } - catch (Exception $e) - { - if ($bIsTransactionEnabled) - { - CMDBSource::Query('ROLLBACK'); + while ($iTransactionRetry > 0) { + try { + $iTransactionRetry--; + if ($bIsTransactionEnabled) { + CMDBSource::Query('START TRANSACTION'); + } + + // First query built upon on the root class, because the ID must be created first + $this->m_iKey = $this->DBInsertSingleTable($sRootClass); + + // Then do the leaf class, if different from the root class + if ($sClass != $sRootClass) { + $this->DBInsertSingleTable($sClass); + } + + // Then do the other classes + foreach (MetaModel::EnumParentClasses($sClass) as $sParentClass) { + if ($sParentClass == $sRootClass) { + continue; + } + $this->DBInsertSingleTable($sParentClass); + } + + $this->OnObjectKeyReady(); + + $this->DBWriteLinks(); + $this->WriteExternalAttributes(); + + if ($bIsTransactionEnabled) { + CMDBSource::Query('COMMIT'); + } + break; + } + catch (Exception $e) { + IssueLog::Error($e->getMessage()); + if ($bIsTransactionEnabled) + { + CMDBSource::Query('ROLLBACK'); + if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e)) + { + // Deadlock found when trying to get lock; try restarting transaction (only in main transaction) + if ($iTransactionRetry > 0) + { + // wait and retry + IssueLog::Error("Insert TRANSACTION Retrying..."); + usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry)); + continue; + } + else + { + IssueLog::Error("Insert Deadlock TRANSACTION prevention failed."); + } + } + } + throw $e; } - throw $e; } $this->m_bIsInDB = true; @@ -3185,9 +3207,11 @@ abstract class DBObject implements iDisplay $bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled'); if ($bIsTransactionEnabled) { - $iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count'); + // TODO Deep clone this object before the transaction (to use it in case of rollback) + // $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count'); + $iTransactionRetryCount = 1; $iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms'); - $iTransactionRetry = $iIsTransactionRetryCount; + $iTransactionRetry = $iTransactionRetryCount; } while ($iTransactionRetry > 0) { @@ -3275,18 +3299,18 @@ abstract class DBObject implements iDisplay } catch (MySQLException $e) { + IssueLog::Error($e->getMessage()); if ($bIsTransactionEnabled) { CMDBSource::Query('ROLLBACK'); - if ($e->getCode() == 1213) + if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e)) { - // Deadlock found when trying to get lock; try restarting transaction - IssueLog::Error($e->getMessage()); + // Deadlock found when trying to get lock; try restarting transaction (only in main transaction) if ($iTransactionRetry > 0) { // wait and retry IssueLog::Error("Update TRANSACTION Retrying..."); - usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry)); + usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry)); continue; } else @@ -3300,10 +3324,11 @@ abstract class DBObject implements iDisplay 'id' => $this->GetKey(), 'class' => get_class($this), 'issues' => $aErrors - )); + ), $e); } catch (CoreCannotSaveObjectException $e) { + IssueLog::Error($e->getMessage()); if ($bIsTransactionEnabled) { CMDBSource::Query('ROLLBACK'); @@ -3312,6 +3337,7 @@ abstract class DBObject implements iDisplay } catch (Exception $e) { + IssueLog::Error($e->getMessage()); if ($bIsTransactionEnabled) { CMDBSource::Query('ROLLBACK'); @@ -3514,9 +3540,11 @@ abstract class DBObject implements iDisplay $bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled'); if ($bIsTransactionEnabled) { - $iIsTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count'); - $iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms'); - $iTransactionRetry = $iIsTransactionRetryCount; + // TODO Deep clone this object before the transaction (to use it in case of rollback) + // $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count'); + $iTransactionRetryCount = 1; + $iTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms'); + $iTransactionRetry = $iTransactionRetryCount; } while ($iTransactionRetry > 0) { @@ -3539,18 +3567,18 @@ abstract class DBObject implements iDisplay } catch (MySQLException $e) { + IssueLog::Error($e->getMessage()); if ($bIsTransactionEnabled) { CMDBSource::Query('ROLLBACK'); - if ($e->getCode() == 1213) + if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e)) { // Deadlock found when trying to get lock; try restarting transaction - IssueLog::Error($e->getMessage()); if ($iTransactionRetry > 0) { // wait and retry IssueLog::Error("Delete TRANSACTION Retrying..."); - usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iIsTransactionRetryCount - $iTransactionRetry)); + usleep(random_int(1, 5) * 1000 * $iTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry)); continue; } else diff --git a/core/log.class.inc.php b/core/log.class.inc.php index d100b5d6e..147a82044 100644 --- a/core/log.class.inc.php +++ b/core/log.class.inc.php @@ -503,6 +503,7 @@ class FileLog protected function Write($sText, $sLevel = '', $sChannel = '', $aContext = array()) { $sTextPrefix = empty($sLevel) ? '' : (str_pad($sLevel, 7).' | '); + $sTextPrefix .= str_pad(UserRights::GetUserId(), 5)." | "; $sTextSuffix = empty($sChannel) ? '' : " | $sChannel"; $sText = "{$sTextPrefix}{$sText}{$sTextSuffix}"; $sLogFilePath = $this->oFileNameBuilder->GetLogFilePath(); diff --git a/core/mutex.class.inc.php b/core/mutex.class.inc.php index 37310cccb..b94f31bde 100644 --- a/core/mutex.class.inc.php +++ b/core/mutex.class.inc.php @@ -242,6 +242,8 @@ class iTopMutex * * @throws \Exception * @throws \MySQLException + * + * @since 2.7.5 3.0.0 N°3968 specify `wait_timeout` for the mutex dedicated connection */ public function InitMySQLSession() { @@ -254,10 +256,36 @@ class iTopMutex $this->hDBLink = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $bTlsEnabled, $sTlsCA, false); - if (!$this->hDBLink) - { + if (!$this->hDBLink) { throw new Exception("Could not connect to the DB server (host=$sServer, user=$sUser): ".mysqli_connect_error().' (mysql errno: '.mysqli_connect_errno().')'); } + + // Make sure that the server variable `wait_timeout` is at least 86400 seconds for this connection, + // since the lock will be released if/when the connection times out. + // Source https://dev.mysql.com/doc/refman/5.7/en/locking-functions.html : + // > A lock obtained with GET_LOCK() is released explicitly by executing RELEASE_LOCK() or implicitly when your session terminates + // + // BEWARE: If you want to check the value of this variable, when run from an interactive console `SHOW VARIABLES LIKE 'wait_timeout'` + // will actually returns the value of the variable `interactive_timeout` which may be quite different. + $sSql = "SHOW VARIABLES LIKE 'wait_timeout'"; + $result = mysqli_query($this->hDBLink, $sSql); + if (!$result) { + throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')'); + } + if ($aRow = mysqli_fetch_array($result, MYSQLI_BOTH)) { + $iTimeout = (int)$aRow[1]; + } else { + mysqli_free_result($result); + throw new Exception("No result for query '".$sSql."'"); + } + mysqli_free_result($result); + + if ($iTimeout < 86400) { + $result = mysqli_query($this->hDBLink, 'SET SESSION wait_timeout=86400'); + if ($result === false) { + throw new Exception("Failed to issue MySQL query '".$sSql."': ".mysqli_error($this->hDBLink).' (mysql errno: '.mysqli_errno($this->hDBLink).')'); + } + } } diff --git a/datamodels/2.x/itop-portal-base/dictionaries/en.dict.itop-portal-base.php b/datamodels/2.x/itop-portal-base/dictionaries/en.dict.itop-portal-base.php index 94a20a71d..54c81f3fe 100644 --- a/datamodels/2.x/itop-portal-base/dictionaries/en.dict.itop-portal-base.php +++ b/datamodels/2.x/itop-portal-base/dictionaries/en.dict.itop-portal-base.php @@ -68,6 +68,8 @@ Dict::Add('EN US', 'English', 'English', array( Dict::Add('EN US', 'English', 'English', array( 'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Close this entry', 'Portal:Form:Close:Warning' => 'Do you want to leave this form ? Data entered may be lost', + 'Portal:Error:ObjectCannotBeCreated' => 'Error: object cannot be created. Check associated objects and attachments before submitting again this form.', + 'Portal:Error:ObjectCannotBeUpdated' => 'Error: object cannot be updated. Check associated objects and attachments before submitting again this form.', )); // UserProfile brick diff --git a/datamodels/2.x/itop-portal-base/dictionaries/fr.dict.itop-portal-base.php b/datamodels/2.x/itop-portal-base/dictionaries/fr.dict.itop-portal-base.php index ff9419f5e..d755e8e50 100644 --- a/datamodels/2.x/itop-portal-base/dictionaries/fr.dict.itop-portal-base.php +++ b/datamodels/2.x/itop-portal-base/dictionaries/fr.dict.itop-portal-base.php @@ -67,6 +67,8 @@ Dict::Add('FR FR', 'French', 'Français', array( Dict::Add('FR FR', 'French', 'Français', array( 'Portal:Form:Caselog:Entry:Close:Tooltip' => 'Fermer cette entrée', 'Portal:Form:Close:Warning' => 'Voulez-vous quitter ce formulaire ? Les données saisies seront perdues', + 'Portal:Error:ObjectCannotBeCreated' => 'Erreur: L\'objet n\'a pas été créé. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.', + 'Portal:Error:ObjectCannotBeUpdated' => 'Erreur: L\'objet n\'a pas été modifié. Vérifiez les objets liés et les attachements avant de soumettre à nouveau le formulaire.', )); // UserProfile brick 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 c8c851d9b..474f26b8c 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 @@ -30,6 +30,7 @@ use DBObjectSearch; use DBObjectSet; use DBSearch; use FieldExpression; +use IssueLog; use MetaModel; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -65,6 +66,8 @@ class BrowseBrickController extends BrickController */ public function DisplayAction(Request $oRequest, $sBrickId, $sBrowseMode = null, $sDataLoading = null) { + $sPortalId = $this->getParameter('combodo.portal.instance.id'); + /** @var \Combodo\iTop\Portal\Helper\BrowseBrickHelper $oBrowseBrickHelper */ $oBrowseBrickHelper = $this->get('browse_brick'); /** @var \Combodo\iTop\Portal\Helper\RequestManipulatorHelper $oRequestManipulator */ @@ -266,8 +269,7 @@ class BrowseBrickController extends BrickController // Note : This could be way more simpler if we had a SetInternalParam($sParam, $value) verb $aQueryParams = $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->GetInternalParams(); // Note : $iSearchloopMax was initialized on the previous loop - for ($j = 0; $j <= $iSearchLoopMax; $j++) - { + for ($j = 0; $j <= $iSearchLoopMax; $j++) { $aQueryParams['search_value_'.$j] = '%'.$aSearchValues[$j].'%'; } $aLevelsProperties[$aLevelsPropertiesKeys[$i]]['search']->SetInternalParams($aQueryParams); @@ -277,12 +279,11 @@ class BrowseBrickController extends BrickController $oQuery = $aLevelsProperties[$aLevelsPropertiesKeys[0]]['search']; // Testing appropriate data loading mode if we are in auto - if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO) - { + if ($sDataLoading === AbstractBrick::ENUM_DATA_LOADING_AUTO) { // - Check how many records there is. // - Update $sDataLoading with its new value regarding the number of record and the threshold $oCountSet = new DBObjectSet($oQuery); - $fThreshold = (float)MetaModel::GetModuleSetting($this->getParameter('combodo.portal.instance.id'), + $fThreshold = (float)MetaModel::GetModuleSetting($sPortalId, 'lazy_loading_threshold'); $sDataLoading = ($oCountSet->Count() > $fThreshold) ? AbstractBrick::ENUM_DATA_LOADING_LAZY : AbstractBrick::ENUM_DATA_LOADING_FULL; unset($oCountSet); @@ -440,17 +441,21 @@ class BrowseBrickController extends BrickController } } + IssueLog::Debug('Portal BrowseBrick query', 'portal', array( + 'portalId' => $sPortalId, + 'brickId' => $sBrickId, + 'oql' => $oSet->GetFilter()->ToOQL(), + )); + + // Preparing response - if ($oRequest->isXmlHttpRequest()) - { + if ($oRequest->isXmlHttpRequest()) { $aData = $aData + array( 'data' => $aItems, 'levelsProperties' => $aLevelsProperties, ); $oResponse = new JsonResponse($aData); - } - else - { + } else { $aData = $aData + array( 'oBrick' => $oBrick, 'sBrickId' => $sBrickId, 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 e634fa76f..dfc1a59e1 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 @@ -42,6 +42,7 @@ use Dict; use Exception; use FieldExpression; use iPopupMenuExtension; +use IssueLog; use JSButtonItem; use MetaModel; use Symfony\Component\HttpFoundation\JsonResponse; @@ -89,10 +90,10 @@ class ManageBrickController extends BrickController /** @var \Combodo\iTop\Portal\Brick\ManageBrick $oBrick */ $oBrick = $oBrickCollection->GetBrickById($sBrickId); - if (is_null($sDisplayMode)) - { + if (is_null($sDisplayMode)) { $sDisplayMode = $oBrick->GetDefaultDisplayMode(); } + $aData = $this->GetData($oRequest, $sBrickId, $sGroupingTab, $oBrick::AreDetailsNeededForDisplayMode($sDisplayMode)); $aExportFields = $oBrick->GetExportFields(); @@ -102,8 +103,7 @@ class ManageBrickController extends BrickController 'iDefaultListLength' => $oBrick->GetDefaultListLength(), ); // Preparing response - if ($oRequest->isXmlHttpRequest()) - { + if ($oRequest->isXmlHttpRequest()) { $oResponse = new JsonResponse($aData); } else @@ -815,30 +815,31 @@ class ManageBrickController extends BrickController 'aColumnsDefinition' => $aColumnsDefinition, ); } - } - else - { + + IssueLog::Debug('Portal ManageBrick query', 'portal', array( + 'portalId' => $sPortalId, + 'brickId' => $sBrickId, + 'groupingTab' => $sGroupingTab, + 'oql' => $oSet->GetFilter()->ToOQL(), + 'aGroupingTabs' => $aGroupingTabs, + )); + } else { $aGroupingAreasData = array(); $sGroupingArea = null; } // Preparing response - if ($oRequest->isXmlHttpRequest()) - { + if ($oRequest->isXmlHttpRequest()) { $aData = $aData + array( 'data' => $aGroupingAreasData[$sGroupingArea]['aItems'], ); - } - else - { + } else { $aDisplayValues = array(); $aUrls = array(); $aColumns = array(); $aNames = array(); - if ($bHasScope) - { - foreach ($aGroupingTabsValues as $aValues) - { + if ($bHasScope) { + foreach ($aGroupingTabsValues as $aValues) { $aDisplayValues[] = array( 'value' => $aValues['count'], 'label' => $aValues['label'], 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 c1b3ba073..043ad462f 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 @@ -25,14 +25,12 @@ use AttributeDateTime; use AttributeTagSet; use CMDBChangeOpAttachmentAdded; use CMDBChangeOpAttachmentRemoved; -use CMDBSource; use Combodo\iTop\Form\Field\Field; use Combodo\iTop\Form\Field\FileUploadField; use Combodo\iTop\Form\Field\LabelField; use Combodo\iTop\Form\Form; use Combodo\iTop\Form\FormManager; use Combodo\iTop\Portal\Helper\ApplicationHelper; -use CoreCannotSaveObjectException; use DBObject; use DBObjectSearch; use DBObjectSet; @@ -1121,30 +1119,28 @@ class ObjectFormManager extends FormManager return $aData; } - // The try catch is essentially to start a MySQL transaction in order to ensure that all or none objects are persisted when creating an object with links - try - { - $sObjectClass = get_class($this->oObject); + $sObjectClass = get_class($this->oObject); - // Starting transaction - CMDBSource::Query('START TRANSACTION'); + try { // Forcing allowed writing on the object if necessary. This is used in some particular cases. $bAllowWrite = ($sObjectClass === 'Person' && $this->oObject->GetKey() == UserRights::GetContactId()); - if ($bAllowWrite) - { + if ($bAllowWrite) { $this->oObject->AllowWrite(true); } // Writing object to DB - $bActivateTriggers = (!$this->oObject->IsNew() && $this->oObject->IsModified()); + $bIsNew = $this->oObject->IsNew(); $bWasModified = $this->oObject->IsModified(); + $bActivateTriggers = (!$bIsNew && $bWasModified); try { $this->oObject->DBWrite(); } - catch (CoreCannotSaveObjectException $e) - { - throw new Exception($e->getHtmlMessage()); + catch (Exception $e) { + if ($bIsNew) { + throw new Exception(Dict::S('Portal:Error:ObjectCannotBeCreated')); + } + throw new Exception(Dict::S('Portal:Error:ObjectCannotBeUpdated')); } // Finalizing images link to object, otherwise it will be cleaned by the GC InlineImage::FinalizeInlineImages($this->oObject); @@ -1155,9 +1151,6 @@ class ObjectFormManager extends FormManager $this->FinalizeAttachments($aArgs['attachmentIds']); } - // Ending transaction with a commit as everything was fine - CMDBSource::Query('COMMIT'); - // Checking if we have to apply a stimulus if (isset($aArgs['applyStimulus'])) { @@ -1205,14 +1198,11 @@ class ObjectFormManager extends FormManager } catch (Exception $e) { - // End transaction with a rollback as something failed - CMDBSource::Query('ROLLBACK'); $aData['valid'] = false; $aData['messages']['error'] += array('_main' => array($e->getMessage())); - IssueLog::Error(__METHOD__.' at line '.__LINE__.' : Rollback during submit ('.$e->getMessage().')'); + IssueLog::Error(__METHOD__.' at line '.__LINE__.' : '.$e->getMessage()); } - return $aData; }