浏览代码

#919 Circular references between tickets (parent/child). Protect the framework against infinite recursions on cascaded updates (done at the DBUpdate level).

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3190 a333f486-631f-4898-b8df-5754b55c2be0
romainq 11 年之前
父节点
当前提交
e6ddc09d33
共有 1 个文件被更改,包括 117 次插入98 次删除
  1. 117 98
      core/dbobject.class.php

+ 117 - 98
core/dbobject.class.php

@@ -1652,142 +1652,161 @@ abstract class DBObject implements iDisplay
 			throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead");
 		}
 
-		// Stop watches
-		$sState = $this->GetState();
-		if ($sState != '')
+		// Protect against reentrance (e.g. cascading the update of ticket logs)
+		static $aUpdateReentrance = array();
+		$sKey = get_class($this).'::'.$this->GetKey();
+		if (array_key_exists($sKey, $aUpdateReentrance))
 		{
-			foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
+			return;
+		}
+		$aUpdateReentrance[$sKey] = true;
+
+		try
+		{
+			// Stop watches
+			$sState = $this->GetState();
+			if ($sState != '')
 			{
-				if ($oAttDef instanceof AttributeStopWatch)
+				foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
 				{
-					if (in_array($sState, $oAttDef->GetStates()))
+					if ($oAttDef instanceof AttributeStopWatch)
 					{
-						// Compute or recompute the deadlines
-						$oSW = $this->Get($sAttCode);
-						$oSW->ComputeDeadlines($this, $oAttDef);
-						$this->Set($sAttCode, $oSW);
+						if (in_array($sState, $oAttDef->GetStates()))
+						{
+							// Compute or recompute the deadlines
+							$oSW = $this->Get($sAttCode);
+							$oSW->ComputeDeadlines($this, $oAttDef);
+							$this->Set($sAttCode, $oSW);
+						}
 					}
 				}
 			}
-		}
 
-		$this->DoComputeValues();
-		$this->OnUpdate();
+			$this->DoComputeValues();
+			$this->OnUpdate();
 
-		$aChanges = $this->ListChanges();
-		if (count($aChanges) == 0)
-		{
-			// Attempting to update an unchanged object
-			return;
-		}
+			$aChanges = $this->ListChanges();
+			if (count($aChanges) == 0)
+			{
+				// Attempting to update an unchanged object
+				unset($aUpdateReentrance[$sKey]);
+				return $this->m_iKey;
+			}
 
-		// Ultimate check - ensure DB integrity
-		list($bRes, $aIssues) = $this->CheckToWrite();
-		if (!$bRes)
-		{
-			$sIssues = implode(', ', $aIssues);
-			throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
-		}
+			// Ultimate check - ensure DB integrity
+			list($bRes, $aIssues) = $this->CheckToWrite();
+			if (!$bRes)
+			{
+				$sIssues = implode(', ', $aIssues);
+				throw new CoreException("Object not following integrity rules", array('issues' => $sIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
+			}
 
-		// Save the original values (will be reset to the new values when the object get written to the DB)
-		$aOriginalValues = $this->m_aOrigValues;
+			// Save the original values (will be reset to the new values when the object get written to the DB)
+			$aOriginalValues = $this->m_aOrigValues;
 
-		$bHasANewExternalKeyValue = false;
-		$aHierarchicalKeys = array();
-		foreach($aChanges as $sAttCode => $valuecurr)
-		{
-			$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
-			if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true;
-			if (!$oAttDef->IsDirectField()) unset($aChanges[$sAttCode]);
-			if ($oAttDef->IsHierarchicalKey())
+			$bHasANewExternalKeyValue = false;
+			$aHierarchicalKeys = array();
+			foreach($aChanges as $sAttCode => $valuecurr)
 			{
-				$aHierarchicalKeys[$sAttCode] = $oAttDef;
+				$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+				if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true;
+				if (!$oAttDef->IsDirectField()) unset($aChanges[$sAttCode]);
+				if ($oAttDef->IsHierarchicalKey())
+				{
+					$aHierarchicalKeys[$sAttCode] = $oAttDef;
+				}
 			}
-		}
 
-		if (!MetaModel::DBIsReadOnly())
-		{
-			// Update the left & right indexes for each hierarchical key
-			foreach($aHierarchicalKeys as $sAttCode => $oAttDef)
+			if (!MetaModel::DBIsReadOnly())
 			{
-				$sTable = $sTable = MetaModel::DBGetTable(get_class($this), $sAttCode);
-				$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".$this->GetKey();
-				$aRes = CMDBSource::QueryToArray($sSQL);
-				$iMyLeft = $aRes[0]['left'];
-				$iMyRight = $aRes[0]['right'];
-				$iDelta =$iMyRight - $iMyLeft + 1;
-				MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
-				
-				if ($aChanges[$sAttCode] == 0)
+				// Update the left & right indexes for each hierarchical key
+				foreach($aHierarchicalKeys as $sAttCode => $oAttDef)
 				{
-					// No new parent, insert completely at the right of the tree
-					$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
+					$sTable = $sTable = MetaModel::DBGetTable(get_class($this), $sAttCode);
+					$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".$this->GetKey();
 					$aRes = CMDBSource::QueryToArray($sSQL);
-					if (count($aRes) == 0)
+					$iMyLeft = $aRes[0]['left'];
+					$iMyRight = $aRes[0]['right'];
+					$iDelta =$iMyRight - $iMyLeft + 1;
+					MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
+					
+					if ($aChanges[$sAttCode] == 0)
 					{
-						$iNewLeft = 1;
+						// No new parent, insert completely at the right of the tree
+						$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
+						$aRes = CMDBSource::QueryToArray($sSQL);
+						if (count($aRes) == 0)
+						{
+							$iNewLeft = 1;
+						}
+						else
+						{
+							$iNewLeft = $aRes[0]['max']+1;
+						}
 					}
 					else
 					{
-						$iNewLeft = $aRes[0]['max']+1;
+						// Insert at the right of the specified parent
+						$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aChanges[$sAttCode]);
+						$iNewLeft = CMDBSource::QueryToScalar($sSQL);
 					}
-				}
-				else
-				{
-					// Insert at the right of the specified parent
-					$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aChanges[$sAttCode]);
-					$iNewLeft = CMDBSource::QueryToScalar($sSQL);
-				}
 
-				MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
+					MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
 
-				$aHKChanges = array();
-				$aHKChanges[$sAttCode] = $aChanges[$sAttCode];
-				$aHKChanges[$oAttDef->GetSQLLeft()] = $iNewLeft;
-				$aHKChanges[$oAttDef->GetSQLRight()] = $iNewLeft + $iDelta - 1;
-				$aChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below
-			}
+					$aHKChanges = array();
+					$aHKChanges[$sAttCode] = $aChanges[$sAttCode];
+					$aHKChanges[$oAttDef->GetSQLLeft()] = $iNewLeft;
+					$aHKChanges[$oAttDef->GetSQLRight()] = $iNewLeft + $iDelta - 1;
+					$aChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below
+				}
+				
+				// Update scalar attributes
+				if (count($aChanges) != 0)
+				{
+					$oFilter = new DBObjectSearch(get_class($this));
+					$oFilter->AddCondition('id', $this->m_iKey, '=');
 			
-			// Update scalar attributes
-			if (count($aChanges) != 0)
-			{
-				$oFilter = new DBObjectSearch(get_class($this));
-				$oFilter->AddCondition('id', $this->m_iKey, '=');
-		
-				$sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges);
-				CMDBSource::Query($sSQL);
+					$sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges);
+					CMDBSource::Query($sSQL);
+				}
 			}
-		}
 
-		$this->DBWriteLinks();
-		$this->m_bDirty = false;
+			$this->DBWriteLinks();
+			$this->m_bDirty = false;
 
-		$this->AfterUpdate();
+			$this->AfterUpdate();
 
-		// Reload to get the external attributes
-		if ($bHasANewExternalKeyValue)
-		{
-			$this->Reload();
-		}
-		else
-		{
-			// Reset original values although the object has not been reloaded
-			foreach ($this->m_aLoadedAtt as $sAttCode => $bLoaded)
+			// Reload to get the external attributes
+			if ($bHasANewExternalKeyValue)
 			{
-				if ($bLoaded)
+				$this->Reload();
+			}
+			else
+			{
+				// Reset original values although the object has not been reloaded
+				foreach ($this->m_aLoadedAtt as $sAttCode => $bLoaded)
 				{
-					$value = $this->m_aCurrValues[$sAttCode];
-					$this->m_aOrigValues[$sAttCode] = is_object($value) ? clone $value : $value;
+					if ($bLoaded)
+					{
+						$value = $this->m_aCurrValues[$sAttCode];
+						$this->m_aOrigValues[$sAttCode] = is_object($value) ? clone $value : $value;
+					}
 				}
+			
 			}
-		
-		}
 
-		if (count($aChanges) != 0)
+			if (count($aChanges) != 0)
+			{
+				$this->RecordAttChanges($aChanges, $aOriginalValues);
+			}
+		}
+		catch (Exception $e)
 		{
-			$this->RecordAttChanges($aChanges, $aOriginalValues);
+			unset($aUpdateReentrance[$sKey]);
+			throw $e;
 		}
 
+		unset($aUpdateReentrance[$sKey]);
 		return $this->m_iKey;
 	}