Browse Source

#137 #203 #204 #119 - Finalized the new (simplified) module for user rights management ; needed to review deeply the data integrity management to have the setup working again!

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@697 a333f486-631f-4898-b8df-5754b55c2be0
romainq 15 years ago
parent
commit
1ae75b9f68

+ 11 - 10
addons/userrights/userrightsprofile.class.inc.php

@@ -380,7 +380,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
 
 
 		$oContact = new Person();
 		$oContact = new Person();
 		$oContact->Set('name', 'My last name');
 		$oContact->Set('name', 'My last name');
-		//$oContact->Set('first_name', 'My first name');
+		$oContact->Set('first_name', 'My first name');
 		//$oContact->Set('status', 'available');
 		//$oContact->Set('status', 'available');
 		$oContact->Set('org_id', $iOrgId);
 		$oContact->Set('org_id', $iOrgId);
 		$oContact->Set('email', 'my.email@foo.org');
 		$oContact->Set('email', 'my.email@foo.org');
@@ -421,7 +421,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
 	{
 	{
 		SetupProfiles::ComputeITILProfiles();
 		SetupProfiles::ComputeITILProfiles();
 		//SetupProfiles::ComputeBasicProfiles();
 		//SetupProfiles::ComputeBasicProfiles();
-
 		SetupProfiles::DoCreateProfiles();
 		SetupProfiles::DoCreateProfiles();
 		return true;
 		return true;
 	}
 	}
@@ -562,16 +561,17 @@ exit;
 	{
 	{
 		// load and cache permissions for the current user on the given class
 		// load and cache permissions for the current user on the given class
 		//
 		//
-		$aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode];
+		$iUser = $oUser->GetKey();
+		$aTest = @$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode];
 		if (is_array($aTest)) return $aTest;
 		if (is_array($aTest)) return $aTest;
 
 
 		$sAction = self::$m_aActionCodes[$iActionCode];
 		$sAction = self::$m_aActionCodes[$iActionCode];
 
 
 		$iInstancePermission = UR_ALLOWED_NO;
 		$iInstancePermission = UR_ALLOWED_NO;
 		$aAttributes = array();
 		$aAttributes = array();
-		if (isset($this->m_aUserProfiles[$oUser->GetKey()]))
+		if (isset($this->m_aUserProfiles[$iUser]))
 		{
 		{
-			foreach($this->m_aUserProfiles[$oUser->GetKey()] as $iProfile => $oProfile)
+			foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile)
 			{
 			{
 				$oGrantRecord = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
 				$oGrantRecord = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
 				if (is_null($oGrantRecord))
 				if (is_null($oGrantRecord))
@@ -604,7 +604,7 @@ exit;
 			'permission' => $iInstancePermission,
 			'permission' => $iInstancePermission,
 			'attributes' => $aAttributes,
 			'attributes' => $aAttributes,
 		);
 		);
-		$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode] = $aRes;
+		$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
 		return $aRes;
 		return $aRes;
 	}
 	}
 	
 	
@@ -728,13 +728,14 @@ exit;
 	public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null)
 	public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null)
 	{
 	{
 		// Note: this code is VERY close to the code of IsActionAllowed()
 		// Note: this code is VERY close to the code of IsActionAllowed()
+		$iUser = $oUser->GetKey();
 
 
 		if (is_null($oInstanceSet))
 		if (is_null($oInstanceSet))
 		{
 		{
 			$iInstancePermission = UR_ALLOWED_NO;
 			$iInstancePermission = UR_ALLOWED_NO;
-			if (isset($this->m_aUserProfiles[$oUser->GetKey()]))
+			if (isset($this->m_aUserProfiles[$iUser]))
 			{
 			{
-				foreach($this->m_aUserProfiles[$oUser->GetKey()] as $iProfile => $oProfile)
+				foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile)
 				{
 				{
 					$oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
 					$oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
 					if (!is_null($oGrantRecord))
 					if (!is_null($oGrantRecord))
@@ -751,9 +752,9 @@ exit;
 		while($oObject = $oInstanceSet->Fetch())
 		while($oObject = $oInstanceSet->Fetch())
 		{
 		{
 			$iInstancePermission = UR_ALLOWED_NO;
 			$iInstancePermission = UR_ALLOWED_NO;
-			if (isset($this->m_aUserProfiles[$oUser->GetKey()]))
+			if (isset($this->m_aUserProfiles[$iUser]))
 			{
 			{
-				foreach($this->m_aUserProfiles[$oUser->GetKey()] as $iProfile => $oProfile)
+				foreach($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile)
 				{
 				{
 					$oGrantRecord = $this->GetClassStimulusGrant($iProfile, get_class($oObject), $sStimulusCode);
 					$oGrantRecord = $this->GetClassStimulusGrant($iProfile, get_class($oObject), $sStimulusCode);
 					if (!is_null($oGrantRecord))
 					if (!is_null($oGrantRecord))

+ 1 - 1
addons/userrights/userrightsprojection.class.inc.php

@@ -616,7 +616,7 @@ class UserRightsProjection extends UserRightsAddOnAPI
 
 
 		$oContact = new Person();
 		$oContact = new Person();
 		$oContact->Set('name', 'My last name');
 		$oContact->Set('name', 'My last name');
-		//$oContact->Set('first_name', 'My first name');
+		$oContact->Set('first_name', 'My first name');
 		//$oContact->Set('status', 'available');
 		//$oContact->Set('status', 'available');
 		$oContact->Set('org_id', $iOrgId);
 		$oContact->Set('org_id', $iOrgId);
 		$oContact->Set('email', 'my.email@foo.org');
 		$oContact->Set('email', 'my.email@foo.org');

+ 56 - 18
core/attributedef.class.inc.php

@@ -160,7 +160,6 @@ abstract class AttributeDefinition
 	public function IsExternalField() {return false;} 
 	public function IsExternalField() {return false;} 
 	public function IsWritable() {return false;} 
 	public function IsWritable() {return false;} 
 	public function IsNullAllowed() {return true;} 
 	public function IsNullAllowed() {return true;} 
-	public function GetNullValue() {return null;} 
 	public function GetCode() {return $this->m_sCode;} 
 	public function GetCode() {return $this->m_sCode;} 
 	public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);}
 	public function GetLabel() {return Dict::S('Class:'.$this->m_sHostClass.'/Attribute:'.$this->m_sCode, $this->m_sCode);}
 	public function GetLabel_Obsolete()
 	public function GetLabel_Obsolete()
@@ -192,6 +191,9 @@ abstract class AttributeDefinition
 	public function GetValuesDef() {return null;} 
 	public function GetValuesDef() {return null;} 
 	public function GetPrerequisiteAttributes() {return array();} 
 	public function GetPrerequisiteAttributes() {return array();} 
 
 
+	public function GetNullValue() {return null;} 
+	public function IsNull($proposedValue) {return is_null($proposedValue);} 
+
 	public function MakeRealValue($proposedValue) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!)
 	public function MakeRealValue($proposedValue) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!)
 
 
 	public function GetSQLExpressions() {return array();} // returns suffix/expression pairs (1 in most of the cases), for READING (Select)
 	public function GetSQLExpressions() {return array();} // returns suffix/expression pairs (1 in most of the cases), for READING (Select)
@@ -205,7 +207,7 @@ abstract class AttributeDefinition
 		return '';
 		return '';
 	}
 	}
 	
 	
-	public function CheckValue($value)
+	public function CheckFormat($value)
 	{
 	{
 		return true;
 		return true;
 	}
 	}
@@ -544,12 +546,22 @@ class AttributeInteger extends AttributeDBField
 		}
 		}
 	} 
 	} 
 
 
+	public function GetNullValue()
+	{
+		return null;
+	} 
+	public function IsNull($proposedValue)
+	{
+		return is_null($proposedValue);
+	} 
+
 	public function MakeRealValue($proposedValue)
 	public function MakeRealValue($proposedValue)
 	{
 	{
-		//return intval($proposedValue); could work as well
+		if (is_null($proposedValue)) return null;
 		if ($proposedValue == '') return null;
 		if ($proposedValue == '') return null;
 		return (int)$proposedValue;
 		return (int)$proposedValue;
 	}
 	}
+
 	public function ScalarToSQL($value)
 	public function ScalarToSQL($value)
 	{
 	{
 		assert(is_numeric($value) || is_null($value));
 		assert(is_numeric($value) || is_null($value));
@@ -577,9 +589,12 @@ class AttributeBoolean extends AttributeInteger
 	
 	
 	public function MakeRealValue($proposedValue)
 	public function MakeRealValue($proposedValue)
 	{
 	{
+		if (is_null($proposedValue)) return null;
+		if ($proposedValue == '') return null;
 		if ((int)$proposedValue) return true;
 		if ((int)$proposedValue) return true;
 		return false;
 		return false;
 	}
 	}
+
 	public function ScalarToSQL($value)
 	public function ScalarToSQL($value)
 	{
 	{
 		assert(is_bool($value));
 		assert(is_bool($value));
@@ -606,7 +621,7 @@ class AttributeString extends AttributeDBField
 	public function GetEditClass() {return "String";}
 	public function GetEditClass() {return "String";}
 	protected function GetSQLCol() {return "VARCHAR(255)";}
 	protected function GetSQLCol() {return "VARCHAR(255)";}
 
 
-	public function CheckValue($value)
+	public function CheckFormat($value)
 	{
 	{
 		$sRegExp = $this->GetValidationPattern();
 		$sRegExp = $this->GetValidationPattern();
 		if (empty($sRegExp))
 		if (empty($sRegExp))
@@ -659,15 +674,22 @@ class AttributeString extends AttributeDBField
 		}
 		}
 	} 
 	} 
 
 
+	public function GetNullValue()
+	{
+		return '';
+	} 
+
+	public function IsNull($proposedValue)
+	{
+		return ($proposedValue == '');
+	} 
+
 	public function MakeRealValue($proposedValue)
 	public function MakeRealValue($proposedValue)
 	{
 	{
-		if (is_null($proposedValue)) return null;
+		if (is_null($proposedValue)) return '';
 		return (string)$proposedValue;
 		return (string)$proposedValue;
-		// if (!settype($proposedValue, "string"))
-		// {
-		// 	throw new CoreException("Failed to change the type of '$proposedValue' to a string");
-		// }
 	}
 	}
+
 	public function ScalarToSQL($value)
 	public function ScalarToSQL($value)
 	{
 	{
 		if (!is_string($value) && !is_null($value))
 		if (!is_string($value) && !is_null($value))
@@ -1029,14 +1051,6 @@ class AttributeEnum extends AttributeString
 		}
 		}
   		return $aLocalizedValues;
   		return $aLocalizedValues;
   }
   }
-
-	public function MakeRealValue($proposedValue)
-	{
-		// For an enum, let's consider an empty value like a null value
-		// Could be implemented by changing the UI : no value => let the default value
-		if ($proposedValue == '') return null;
-		return parent::MakeRealValue($proposedValue);
-	}
 }
 }
 
 
 /**
 /**
@@ -1420,7 +1434,6 @@ class AttributeExternalKey extends AttributeDBFieldVoid
 
 
 	public function GetDefaultValue() {return 0;}
 	public function GetDefaultValue() {return 0;}
 	public function IsNullAllowed() {return $this->Get("is_null_allowed");}
 	public function IsNullAllowed() {return $this->Get("is_null_allowed");}
-	public function GetNullValue() {return 0;} 
 
 
 	public function GetBasicFilterOperators()
 	public function GetBasicFilterOperators()
 	{
 	{
@@ -1468,8 +1481,20 @@ class AttributeExternalKey extends AttributeDBFieldVoid
 		return $this->Get("on_target_delete");
 		return $this->Get("on_target_delete");
 	}
 	}
 
 
+	public function GetNullValue()
+	{
+		return 0;
+	} 
+
+	public function IsNull($proposedValue)
+	{
+		return ($proposedValue == 0);
+	} 
+
 	public function MakeRealValue($proposedValue)
 	public function MakeRealValue($proposedValue)
 	{
 	{
+		if (is_null($proposedValue)) return 0;
+		if (MetaModel::IsValidObject($proposedValue)) return $proposedValue->GetKey();
 		return (int)$proposedValue;
 		return (int)$proposedValue;
 	}
 	}
 }
 }
@@ -1619,11 +1644,24 @@ class AttributeExternalField extends AttributeDefinition
 		return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value);
 		return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value);
 	} 
 	} 
 
 
+	public function GetNullValue()
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetNullValue();
+	} 
+
+	public function IsNull($proposedValue)
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->IsNull($proposedValue);
+	} 
+
 	public function MakeRealValue($proposedValue)
 	public function MakeRealValue($proposedValue)
 	{
 	{
 		$oExtAttDef = $this->GetExtAttDef();
 		$oExtAttDef = $this->GetExtAttDef();
 		return $oExtAttDef->MakeRealValue($proposedValue);
 		return $oExtAttDef->MakeRealValue($proposedValue);
 	}
 	}
+
 	public function ScalarToSQL($value)
 	public function ScalarToSQL($value)
 	{
 	{
 		// This one could be used in case of filtering only
 		// This one could be used in case of filtering only

+ 9 - 5
core/bulkchange.class.inc.php

@@ -320,13 +320,15 @@ class BulkChange
 			// skip the private key, if any
 			// skip the private key, if any
 			if ($sAttCode == 'id') continue;
 			if ($sAttCode == 'id') continue;
 
 
-			if (!$oTargetObj->CheckValue($sAttCode, $aRowData[$iCol]))
+			$res = $oTargetObj->CheckValue($sAttCode, $aRowData[$iCol]);
+			if ($res === true)
 			{
 			{
-				$aErrors[$sAttCode] = "Unexpected value";
+				$oTargetObj->Set($sAttCode, $aRowData[$iCol]);
 			}
 			}
 			else
 			else
 			{
 			{
-				$oTargetObj->Set($sAttCode, $aRowData[$iCol]);
+				// $res is a string with the error description
+				$aErrors[$sAttCode] = "Unexpected value for attribute '$sAttCode': $res";
 			}
 			}
 		}
 		}
 	
 	
@@ -363,9 +365,11 @@ class BulkChange
 	
 	
 		// Checks
 		// Checks
 		//
 		//
-		if (!$oTargetObj->CheckConsistency())
+		$res = $oTargetObj->CheckConsistency();
+		if ($res !== true)
 		{
 		{
-			$aErrors["GLOBAL"] = "Attributes not consistent with each others";
+			// $res contains the error description
+			$aErrors["GLOBAL"] = "Attributes not consistent with each others: $res";
 		}
 		}
 		return $aResults;
 		return $aResults;
 	}
 	}

+ 4 - 0
core/coreexception.class.inc.php

@@ -107,4 +107,8 @@ class CoreWarning extends CoreException
 {
 {
 }
 }
 
 
+class CoreUnexpectedValue extends CoreException
+{
+}
+
 ?>
 ?>

+ 95 - 75
core/dbobject.class.php

@@ -41,6 +41,9 @@ abstract class DBObject
 
 
 	private $m_bDirty = false; // Means: "a modification is ongoing"
 	private $m_bDirty = false; // Means: "a modification is ongoing"
 										// The object may have incorrect external keys, then any attempt of reload must be avoided
 										// The object may have incorrect external keys, then any attempt of reload must be avoided
+	private $m_bCheckStatus = null; // Means: the object has been verified and is consistent with integrity rules
+													//        if null, then the check has to be performed again to know the status
+													//        otherwise, 
 	private $m_bFullyLoaded = false; // Compound objects can be partially loaded
 	private $m_bFullyLoaded = false; // Compound objects can be partially loaded
 	private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode
 	private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode
 
 
@@ -194,6 +197,7 @@ abstract class DBObject
 		$this->m_aCurrValues = array();
 		$this->m_aCurrValues = array();
 		$this->m_aOrigValues = array();
 		$this->m_aOrigValues = array();
 		$this->m_aLoadedAtt = array();
 		$this->m_aLoadedAtt = array();
+		$this->m_bCheckStatus = true;
 
 
 		// Get the key
 		// Get the key
 		//
 		//
@@ -251,13 +255,8 @@ abstract class DBObject
 		if ($sAttCode == 'finalclass')
 		if ($sAttCode == 'finalclass')
 		{
 		{
 			// Ignore it - this attribute is set upon object creation and that's it
 			// Ignore it - this attribute is set upon object creation and that's it
-			//throw new CoreWarning('Attempting to set the value for the internal attribute \"finalclass\"', array('current value'=>$this->Get('finalclass'), 'new value'=>$value));
 			return;
 			return;
 		}
 		}
-		if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
-		{
-			throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
-		}
 		$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
 		$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
 		if ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty)
 		if ($this->m_bIsInDB && !$this->m_bFullyLoaded && !$this->m_bDirty)
 		{
 		{
@@ -266,12 +265,7 @@ abstract class DBObject
 			//           + consistency does not make sense !
 			//           + consistency does not make sense !
 			$this->Reload();
 			$this->Reload();
 		}
 		}
-		if($oAttDef->IsScalar() && !$oAttDef->IsNullAllowed() && is_null($value))
-		{
-			throw new CoreWarning("null not allowed for attribute '$sAttCode', setting default value");
-			$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
-			return;
-		}
+
 		if ($oAttDef->IsExternalKey() && is_object($value))
 		if ($oAttDef->IsExternalKey() && is_object($value))
 		{
 		{
 			// Setting an external key with a whole object (instead of just an ID)
 			// Setting an external key with a whole object (instead of just an ID)
@@ -279,11 +273,11 @@ abstract class DBObject
 			// (useful when building objects in memory and not from a query)
 			// (useful when building objects in memory and not from a query)
 			if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
 			if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
 			{
 			{
-				throw new CoreWarning("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
-				$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+				throw new CoreUnexpectedValue("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
 			}
 			}
 			else
 			else
 			{
 			{
+				$this->m_bCheckStatus = null;
 				$this->m_aCurrValues[$sAttCode] = $value->GetKey();
 				$this->m_aCurrValues[$sAttCode] = $value->GetKey();
 				foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
 				foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
 				{
 				{
@@ -297,17 +291,13 @@ abstract class DBObject
 		}
 		}
 		if(!$oAttDef->IsScalar() && !is_object($value))
 		if(!$oAttDef->IsScalar() && !is_object($value))
 		{
 		{
-			throw new CoreWarning("scalar not allowed for attribute '$sAttCode', setting default value (empty list)");
-			$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
-			return;
+			throw new CoreUnexpectedValue("scalar not allowed for attribute '$sAttCode', setting default value (empty list)");
 		}
 		}
 		if($oAttDef->IsLinkSet())
 		if($oAttDef->IsLinkSet())
 		{
 		{
 			if((get_class($value) != 'DBObjectSet') && !is_subclass_of($value, 'DBObjectSet'))
 			if((get_class($value) != 'DBObjectSet') && !is_subclass_of($value, 'DBObjectSet'))
 			{
 			{
-				throw new CoreWarning("expecting a set of persistent objects (found a '".get_class($value)."'), setting default value (empty list)");
-				$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
-				return;
+				throw new CoreUnexpectedValue("expecting a set of persistent objects (found a '".get_class($value)."'), setting default value (empty list)");
 			}
 			}
 
 
 			$oObjectSet = $value;
 			$oObjectSet = $value;
@@ -316,18 +306,17 @@ abstract class DBObject
 			// not working fine :-(   if (!is_subclass_of($sSetClass, $sLinkClass))
 			// not working fine :-(   if (!is_subclass_of($sSetClass, $sLinkClass))
 			if ($sSetClass != $sLinkClass)
 			if ($sSetClass != $sLinkClass)
 			{
 			{
-				throw new CoreWarning("expecting a set of '$sLinkClass' objects (found a set of '$sSetClass'), setting default value (empty list)");
-				$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
-				return;
+				throw new CoreUnexpectedValue("expecting a set of '$sLinkClass' objects (found a set of '$sSetClass'), setting default value (empty list)");
 			}
 			}
 		}
 		}
-		if ($oAttDef->CheckValue($value))
-		{
-			$this->m_aCurrValues[$sAttCode] = $oAttDef->MakeRealValue($value);
-			$this->RegisterAsDirty(); // Make sure we do not reload it anymore... before saving it
-		}
+
+		$realvalue = $oAttDef->MakeRealValue($value);
+		$this->m_aCurrValues[$sAttCode] = $realvalue;
+
+		$this->m_bCheckStatus = null;
+		$this->RegisterAsDirty(); // Make sure we do not reload it anymore... before saving it
 	}
 	}
-	
+
 	public function Get($sAttCode)
 	public function Get($sAttCode)
 	{
 	{
 		if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
 		if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
@@ -599,6 +588,8 @@ abstract class DBObject
 	}
 	}
 
 
 	// check if the given (or current) value is suitable for the attribute
 	// check if the given (or current) value is suitable for the attribute
+	// return true if successfull
+	// return the error desciption otherwise
 	public function CheckValue($sAttCode, $value = null)
 	public function CheckValue($sAttCode, $value = null)
 	{
 	{
 		if (!is_null($value))
 		if (!is_null($value))
@@ -611,44 +602,46 @@ abstract class DBObject
 		}
 		}
 
 
 		$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
 		$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
-		if ($oAtt->IsExternalKey())
+		if (!$oAtt->IsWritable())
 		{
 		{
-			if (!$oAtt->IsNullAllowed() || ($toCheck != 0) )
+			return true;
+		}
+		elseif ($oAtt->IsNull($toCheck))
+		{
+			if ($oAtt->IsNullAllowed())
 			{
 			{
-				try
-				{
-					$oTargetObj = MetaModel::GetObject($oAtt->GetTargetClass(), $toCheck);
-					return true;
-				}
-				catch (CoreException $e)
-				{
-					return false;
-				}
+				return true;
+			}
+			else
+			{
+				return "Null not allowed";
 			}
 			}
 		}
 		}
-		elseif ($oAtt->IsWritable() && $oAtt->IsScalar())
+		elseif ($oAtt->IsExternalKey())
 		{
 		{
-			if (is_null($toCheck))
+			$sTargetClass = $oAtt->GetTargetClass();
+			$oTargetObj = MetaModel::GetObject($sTargetClass, $toCheck, false /*must be found*/, true /*allow all data*/);
+			if (is_null($oTargetObj))
 			{
 			{
-				if ($oAtt->IsNullAllowed())
-				{
-					return true;
-				}
-				else
-				{
-					return false;
-				}
+				return "Target object not found ($sTargetClass::$toCheck)";
 			}
 			}
-			$aValues = $oAtt->GetAllowedValues();
+		}
+		elseif ($oAtt->IsScalar())
+		{
+			$aValues = $oAtt->GetAllowedValues($this->ToArgs());
 			if (count($aValues) > 0)
 			if (count($aValues) > 0)
 			{
 			{
 				if (!array_key_exists($toCheck, $aValues))
 				if (!array_key_exists($toCheck, $aValues))
 				{
 				{
-					return false;
+					return "Value not allowed [$toCheck]";
 				}
 				}
 			}
 			}
+			elseif (!$oAtt->CheckFormat($toCheck))
+			{
+				return "Wrong format [$toCheck]";
+			}
 		}
 		}
-		return $oAtt->CheckValue($toCheck); // Check the format
+		return true;
 	}
 	}
 	
 	
 	// check attributes together
 	// check attributes together
@@ -657,45 +650,57 @@ abstract class DBObject
 		return true;
 		return true;
 	}
 	}
 	
 	
-	// check if it is allowed to record the new object into the database
+	// check integrity rules (before inserting or updating the object)
 	// a displayable error is returned
 	// a displayable error is returned
-	// Note: checks the values and consistency
-	public function CheckToInsert()
+	public function DoCheckToWrite()
 	{
 	{
-		$aIssues = array();
+		$this->m_aCheckIssues = array();
+
 		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
 		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
 		{
 		{
-			if (!$this->CheckValue($sAttCode))
+			$res = $this->CheckValue($sAttCode);
+			if ($res !== true)
 			{
 			{
-				$aIssues[$sAttCode] = array(
-					'issue' => 'unexpected value'
-				);
+				// $res contains the error description
+				$this->m_aCheckIssues[] = "Unexpected value for attribute '$sAttCode': $res";
 			}
 			}
 		}
 		}
-		if (count($aIssues) > 0)
+		if (count($this->m_aCheckIssues) > 0)
 		{
 		{
-			return array(false, $aIssues);
+			// No need to check consistency between attributes if any of them has
+			// an unexpected value
+			return;
 		}
 		}
-		if (!$this->CheckConsistency())
+		$res = $this->CheckConsistency();
+		if ($res !== true)
 		{
 		{
-			return array(false, $aIssues);
+			// $res contains the error description
+			$this->m_aCheckIssues[] = "Consistency rules not followed: $res";
 		}
 		}
-		return array(true, $aIssues);
 	}
 	}
 
 
-	// check if it is allowed to update the existing object into the database
-	// a displayable error is returned
-	// Note: checks the values and consistency
-	public function CheckToUpdate()
+	final public function CheckToWrite()
 	{
 	{
-		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		if (false)
 		{
 		{
-			if (!$this->CheckValue($sAttCode)) return false;
+			return array(true, array());
 		}
 		}
-		if (!$this->CheckConsistency()) return false;
-		return true;
+
+		if (is_null($this->m_bCheckStatus))
+		{
+			$this->DoCheckToWrite();
+			if (count($this->m_aCheckIssues) == 0)
+			{
+				$this->m_bCheckStatus = true;
+			}
+			else
+			{
+				$this->m_bCheckStatus = false;
+			}
+		}
+		return array($this->m_bCheckStatus, $this->m_aCheckIssues);
 	}
 	}
-	
+
 	// check if it is allowed to delete the existing object from the database
 	// check if it is allowed to delete the existing object from the database
 	// a displayable error is returned
 	// a displayable error is returned
 	public function CheckToDelete()
 	public function CheckToDelete()
@@ -863,10 +868,17 @@ abstract class DBObject
 		{
 		{
 			if (empty($this->m_iKey))
 			if (empty($this->m_iKey))
 			{
 			{
-				throw new CoreWarning("Missing key for the object to write - This class is supposed to have a user defined key, not an autonumber");
+				throw new CoreWarning("Missing key for the object to write - This class is supposed to have a user defined key, not an autonumber", array('class' => $sRootClass));
 			}
 			}
 		}
 		}
 
 
+		// Ultimate check - ensure DB integrity
+		list($bRes, $aIssues) = $this->CheckToWrite();
+		if (!$bRes)
+		{
+			throw new CoreException("Object not following integrity rules - it will not be written into the DB", array('class' => $sClass, 'id' => $this->GetKey(), 'issues' => $aIssues));
+		}
+
 		// First query built upon on the root class, because the ID must be created first
 		// First query built upon on the root class, because the ID must be created first
 		$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
 		$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
 
 
@@ -951,6 +963,14 @@ abstract class DBObject
 			//throw new CoreWarning("Attempting to update an unchanged object");
 			//throw new CoreWarning("Attempting to update an unchanged object");
 			return;
 			return;
 		}
 		}
+
+		// Ultimate check - ensure DB integrity
+		list($bRes, $aIssues) = $this->CheckToWrite();
+		if (!$bRes)
+		{
+			throw new CoreException("Object not following integrity rules - it will not be written into the DB", array('class' => get_class($this), 'id' => $this->GetKey(), 'issues' => $aIssues));
+		}
+
 		$bHasANewExternalKeyValue = false;
 		$bHasANewExternalKeyValue = false;
 		foreach($aChanges as $sAttCode => $valuecurr)
 		foreach($aChanges as $sAttCode => $valuecurr)
 		{
 		{

+ 7 - 3
core/metamodel.class.php

@@ -3257,7 +3257,7 @@ abstract class MetaModel
 		return $iTotalHits.' ('.implode(', ', $aRes).')';
 		return $iTotalHits.' ('.implode(', ', $aRes).')';
 	}
 	}
 
 
-	public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true)
+	public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false)
 	{
 	{
 		if (!array_key_exists($sClass, self::$aQueryCacheGetObject))
 		if (!array_key_exists($sClass, self::$aQueryCacheGetObject))
 		{
 		{
@@ -3268,6 +3268,10 @@ abstract class MetaModel
 			//       or a view... next optimization to come! 
 			//       or a view... next optimization to come! 
 			$oFilter = new DBObjectSearch($sClass);
 			$oFilter = new DBObjectSearch($sClass);
 			$oFilter->AddCondition('id', 987654321, '=');
 			$oFilter->AddCondition('id', 987654321, '=');
+			if ($bAllowAllData)
+			{
+				$oFilter->AllowAllData();
+			}
 	
 	
 			$sSQL = self::MakeSelectQuery($oFilter);
 			$sSQL = self::MakeSelectQuery($oFilter);
 			self::$aQueryCacheGetObject[$sClass] = $sSQL;
 			self::$aQueryCacheGetObject[$sClass] = $sSQL;
@@ -3323,10 +3327,10 @@ abstract class MetaModel
 		return new $sClass($aRow, $sClassAlias);
 		return new $sClass($aRow, $sClassAlias);
 	}
 	}
 
 
-	public static function GetObject($sClass, $iKey, $bMustBeFound = true)
+	public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false)
 	{
 	{
 		self::_check_subclass($sClass);	
 		self::_check_subclass($sClass);	
-		$aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound);
+		$aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound, $bAllowAllData);
 		if (empty($aRow))
 		if (empty($aRow))
 		{
 		{
 			return null;
 			return null;

+ 2 - 2
core/test.class.inc.php

@@ -395,10 +395,10 @@ abstract class TestBizModel extends TestHandler
 	protected $m_oChange;
 	protected $m_oChange;
 	protected function ObjectToDB($oNew, $bReload = false)
 	protected function ObjectToDB($oNew, $bReload = false)
 	{
 	{
-		list($bRes, $aIssues) = $oNew->CheckToInsert();
+		list($bRes, $aIssues) = $oNew->CheckToWrite();
 		if (!$bRes)
 		if (!$bRes)
 		{
 		{
-			throw new CoreException('Could not create object, unexpected values', array('attributes' => $aIssues));
+			throw new CoreException('Could not create object, unexpected values', array('issues' => $aIssues));
 		}
 		}
 		if ($oNew instanceof CMDBObject)
 		if ($oNew instanceof CMDBObject)
 		{
 		{

+ 10 - 6
core/userrights.class.inc.php

@@ -498,8 +498,8 @@ class UserRights
 		// Need to load some records before the login is performed (user preferences)
 		// Need to load some records before the login is performed (user preferences)
 		if (MetaModel::HasCategory($sClass, 'alwaysreadable')) return true;
 		if (MetaModel::HasCategory($sClass, 'alwaysreadable')) return true;
 
 
-		// ne marche pas... pourquoi?
-		//if (!self::CheckLogin()) return false;
+		// When initializing, we need to let everything pass trough
+		if (!self::CheckLogin()) return true;
 
 
 		if (self::IsAdministrator()) return true;
 		if (self::IsAdministrator()) return true;
 
 
@@ -514,7 +514,9 @@ class UserRights
 
 
 	public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
 	public static function IsActionAllowed($sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
 	{
 	{
-		if (!self::CheckLogin()) return false;
+		// When initializing, we need to let everything pass trough
+		if (!self::CheckLogin()) return true;
+
 		if (self::IsAdministrator($oUser)) return true;
 		if (self::IsAdministrator($oUser)) return true;
 
 
 
 
@@ -537,7 +539,9 @@ class UserRights
 
 
 	public static function IsStimulusAllowed($sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
 	public static function IsStimulusAllowed($sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
 	{
 	{
-		if (!self::CheckLogin()) return false;
+		// When initializing, we need to let everything pass trough
+		if (!self::CheckLogin()) return true;
+
 		if (self::IsAdministrator($oUser)) return true;
 		if (self::IsAdministrator($oUser)) return true;
 
 
 		// this module is forbidden for non admins
 		// this module is forbidden for non admins
@@ -555,8 +559,8 @@ class UserRights
 
 
 	public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
 	public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null, $oUser = null)
 	{
 	{
-		if (!self::CheckLogin()) return false;
-		if (self::IsAdministrator($oUser)) return true;
+		// When initializing, we need to let everything pass trough
+		if (!self::CheckLogin()) return true;
 
 
 		// this module is forbidden for non admins
 		// this module is forbidden for non admins
 		if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false;
 		if (MetaModel::HasCategory($sClass, 'addon/userrights')) return false;

+ 19 - 0
dictionaries/dictionary.itop.ui.php

@@ -160,6 +160,25 @@ Dict::Add('EN US', 'English', 'English', array(
 ));
 ));
 
 
 //
 //
+// Class: URP_UserOrg
+//
+
+Dict::Add('EN US', 'English', 'English', array(
+	'Class:URP_UserOrg' => 'User organizations',
+	'Class:URP_UserOrg+' => 'Allowed organizations',
+	'Class:URP_UserProfile/Attribute:userid' => 'User',
+	'Class:URP_UserProfile/Attribute:userid+' => 'user account',
+	'Class:URP_UserProfile/Attribute:userlogin' => 'Login',
+	'Class:URP_UserProfile/Attribute:userlogin+' => 'User\'s login',
+	'Class:URP_UserProfile/Attribute:allowed_org_id' => 'Organization',
+	'Class:URP_UserProfile/Attribute:allowed_org_id+' => 'Allowed organization',
+	'Class:URP_UserProfile/Attribute:allowed_org_name' => 'Organization',
+	'Class:URP_UserProfile/Attribute:allowed_org_name+' => 'Allowed organization',
+	'Class:URP_UserProfile/Attribute:reason' => 'Reason',
+	'Class:URP_UserProfile/Attribute:reason+' => 'explain why this person is allowed to see the data belonging to this organization',
+));
+
+//
 // Class: URP_ProfileProjection
 // Class: URP_ProfileProjection
 //
 //
 
 

+ 20 - 1
dictionaries/fr.dictionary.itop.ui.php

@@ -156,7 +156,26 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'Class:URP_UserProfile/Attribute:profile' => 'Profil',
 	'Class:URP_UserProfile/Attribute:profile' => 'Profil',
 	'Class:URP_UserProfile/Attribute:profile+' => '',
 	'Class:URP_UserProfile/Attribute:profile+' => '',
 	'Class:URP_UserProfile/Attribute:reason' => 'Raison',
 	'Class:URP_UserProfile/Attribute:reason' => 'Raison',
-	'Class:URP_UserProfile/Attribute:reason+' => 'Justifie le rôles affecté à cet utilisateur',
+	'Class:URP_UserProfile/Attribute:reason+' => 'Justifie le rôle affecté à cet utilisateur',
+));
+
+//
+// Class: URP_UserOrg
+//
+
+Dict::Add('EN US', 'English', 'English', array(
+	'Class:URP_UserOrg' => 'Utilisateur/Organisation',
+	'Class:URP_UserOrg+' => 'Organizations permises pour l\'utilisateur',
+	'Class:URP_UserProfile/Attribute:userid' => 'Utilisateur',
+	'Class:URP_UserProfile/Attribute:userid+' => '',
+	'Class:URP_UserProfile/Attribute:userlogin' => 'Login',
+	'Class:URP_UserProfile/Attribute:userlogin+' => '',
+	'Class:URP_UserProfile/Attribute:allowed_org_id' => 'Organisation',
+	'Class:URP_UserProfile/Attribute:allowed_org_id+' => '',
+	'Class:URP_UserProfile/Attribute:allowed_org_name' => 'Organisation',
+	'Class:URP_UserProfile/Attribute:allowed_org_name+' => '',
+	'Class:URP_UserProfile/Attribute:reason' => 'Raison',
+	'Class:URP_UserProfile/Attribute:reason+' => 'Justifie la permission de voir les données de cette organisation',
 ));
 ));
 
 
 //
 //

+ 2 - 6
pages/UI.php

@@ -874,7 +874,7 @@ try
 					{
 					{
 						$oP->p(Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()));
 						$oP->p(Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()));
 					}
 					}
-					else if ($oObj->CheckToUpdate())
+					else
 					{
 					{
 						$oMyChange = MetaModel::NewObject("CMDBChange");
 						$oMyChange = MetaModel::NewObject("CMDBChange");
 						$oMyChange->Set("date", time());
 						$oMyChange->Set("date", time());
@@ -892,10 +892,6 @@ try
 			
 			
 						$oP->p(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()));
 						$oP->p(Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()));
 					}
 					}
-					else
-					{
-						$oP->p("<strong>".Dict::S('UI:Error:ObjectCannotBeUpdated')."</strong>\n");
-					}
 				}
 				}
 				else
 				else
 				{
 				{
@@ -1215,7 +1211,7 @@ EOF
 						$oObj->Set($sAttCode, $paramValue);
 						$oObj->Set($sAttCode, $paramValue);
 					}
 					}
 				}
 				}
-				if ($oObj->ApplyStimulus($sStimulus) && $oObj->CheckToUpdate())
+				if ($oObj->ApplyStimulus($sStimulus))
 				{
 				{
 					$oMyChange = MetaModel::NewObject("CMDBChange");
 					$oMyChange = MetaModel::NewObject("CMDBChange");
 					$oMyChange->Set("date", time());
 					$oMyChange->Set("date", time());

+ 0 - 1
pages/run_query.php

@@ -112,7 +112,6 @@ try
 		echo "<strong>FYI: '$sClearText'</strong><br/>\n";
 		echo "<strong>FYI: '$sClearText'</strong><br/>\n";
 		$oFilter = DBObjectSearch::unserialize($sExpression);
 		$oFilter = DBObjectSearch::unserialize($sExpression);
 		$sExpression = $oFilter->ToOQL();
 		$sExpression = $oFilter->ToOQL();
-		exit;
 	}
 	}
 	else
 	else
 	{
 	{

+ 7 - 1
setup/index.php

@@ -411,6 +411,12 @@ function CreateAdminAccount(SetupWebPage $oP, Config $oConfig, $sAdminUser, $sAd
 {
 {
 	$oP->log('Info - CreateAdminAccount');
 	$oP->log('Info - CreateAdminAccount');
 	InitDataModel($oP, TMP_CONFIG_FILE, false);  // load data model and connect to the database
 	InitDataModel($oP, TMP_CONFIG_FILE, false);  // load data model and connect to the database
+
+	if (!UserRights::Setup())
+	{
+		return false;
+	}
+
 	if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage))
 	if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage))
 	{
 	{
 		$oP->ok("Administrator account '$sAdminUser' created.");
 		$oP->ok("Administrator account '$sAdminUser' created.");
@@ -908,7 +914,7 @@ function SampleDataSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, Con
 	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
 	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
 	AddParamsToForm($oP, $aParamValues, array('sample_data'));
 	AddParamsToForm($oP, $aParamValues, array('sample_data'));
 	
 	
-	if (CreateAdminAccount($oP, $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) && UserRights::Setup())
+	if (CreateAdminAccount($oP, $oConfig, $sAdminUser, $sAdminPwd, $sLanguage))
 	{
 	{
 		$oP->add("<h2>Loading of sample data</h2>\n");
 		$oP->add("<h2>Loading of sample data</h2>\n");
 		$oP->p("<fieldset><legend> Do you want to load sample data into the database ? </legend>\n");
 		$oP->p("<fieldset><legend> Do you want to load sample data into the database ? </legend>\n");

+ 5 - 3
setup/xmldataloader.class.inc.php

@@ -202,10 +202,12 @@ class XMLDataLoader
 					else
 					else
 					{
 					{
 						// tested by Romain, little impact on perf (not significant on the intial setup)
 						// tested by Romain, little impact on perf (not significant on the intial setup)
-						if (!$oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode))
+						$res = $oTargetObj->CheckValue($sAttCode, (string)$oXmlObj->$sAttCode);
+						if ($res !== true)
 						{
 						{
-							SetupWebPage::log_error("Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."'");
-							throw(new Exception("Wrong value for attribute $sAttCode: '".$oXmlObj->$sAttCode."'"));
+							// $res contains the error description
+							SetupWebPage::log_error("Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."' ; $res");
+							throw(new Exception("Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oXmlObj->$sAttCode."' ; $res"));
 						}
 						}
 						$oTargetObj->Set($sAttCode, (string)$oXmlObj->$sAttCode);
 						$oTargetObj->Set($sAttCode, (string)$oXmlObj->$sAttCode);
 					}
 					}

+ 9 - 5
webservices/webservices.class.inc.php

@@ -280,15 +280,15 @@ class WebServices
 	 */
 	 */
 	protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes)
 	protected function MyObjectSetScalar($sAttCode, $sParamName, $value, &$oTargetObj, &$oRes)
 	{
 	{
-		if ($oTargetObj->CheckValue($sAttCode, $value))
+		$res = $oTargetObj->CheckValue($sAttCode, $value);
+		if ($res === true)
 		{
 		{
 			$oTargetObj->Set($sAttCode, $value);
 			$oTargetObj->Set($sAttCode, $value);
 		}
 		}
 		else
 		else
 		{
 		{
-			$aAllowedValues = MetaModel::GetAllowedValues_att(get_class($oTargetObj), $sAttCode);
-			$sValues = implode(', ', $aAllowedValues);
-			$oRes->LogError("Parameter $sParamName: found '$value' while expecting a value in {".$sValues."}");
+			// $res contains the error description
+			$oRes->LogError("Unexpected value for parameter $sParamName: $res");
 		}
 		}
 	}
 	}
 
 
@@ -483,7 +483,7 @@ class WebServices
 	{
 	{
 		if ($oRes->IsOk())
 		if ($oRes->IsOk())
 		{
 		{
-			list($bRes, $aIssues) = $oTargetObj->CheckToInsert();
+			list($bRes, $aIssues) = $oTargetObj->CheckToWrite();
 			if ($bRes)
 			if ($bRes)
 			{
 			{
 				$iId = $oTargetObj->DBInsertTrackedNoReload($oChange);
 				$iId = $oTargetObj->DBInsertTrackedNoReload($oChange);
@@ -493,6 +493,10 @@ class WebServices
 			else
 			else
 			{
 			{
 				$oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)");
 				$oRes->LogError("The ticket could not be created due to forbidden values (or inconsistent values)");
+				foreach($aIssues as $iIssue => $sIssue)
+				{
+					$oRes->LogError("Issue #$iIssue: $sIssue");
+				}
 			}
 			}
 		}
 		}
 	}
 	}