Sfoglia il codice sorgente

User management by profile ready for integration with the UI
and some caching has been implemented for the building of queries (both from an OQL or from a programmatic query)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@90 a333f486-631f-4898-b8df-5754b55c2be0

romainq 16 anni fa
parent
commit
018be02416

+ 13 - 0
addons/userrights/userrightsmatrix.class.inc.php

@@ -318,6 +318,19 @@ class UserRightsMatrix extends UserRightsAddOnAPI
 		return false;
 	}
 
+	public function GetUserId($sUserName)
+	{
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixUsers WHERE login = '$sUserName'"));
+		if ($oSet->Count() < 1)
+		{
+		// todo: throw an exception?
+			return false;
+		}
+
+		$oLogin = $oSet->Fetch();
+		return $oLogin->Get('userid');
+	}
+
 	public function GetFilter($sUserName, $sClass)
 	{
 		$oNullFilter  = new DBObjectSearch($sClass);

+ 9 - 4
addons/userrights/userrightsnull.class.inc.php

@@ -34,7 +34,12 @@ class UserRightsNull extends UserRightsAddOnAPI
 
 	public function CheckCredentials($sUserName, $sPassword)
 	{
-		return true;
+		return 1;
+	}
+
+	public function GetUserId($sUserName)
+	{
+		return 1;
 	}
 
 	public function GetFilter($sUserName, $sClass)
@@ -43,17 +48,17 @@ class UserRightsNull extends UserRightsAddOnAPI
 		return $oNullFilter;
 	}
 
-	public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances)
+	public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances)
 	{
 		return UR_ALLOWED_YES;
 	}
 
-	public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances)
+	public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances)
 	{
 		return UR_ALLOWED_YES;
 	}
 
-	public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances)
+	public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances)
 	{
 		return UR_ALLOWED_YES;
 	}

+ 330 - 81
addons/userrights/userrightsprofile.class.inc.php

@@ -245,22 +245,22 @@ class URP_ProfileProjection extends DBObject
 	public function ProjectUser(URP_Users $oUser)
 	{
 		$sExpr = $this->Get('value');
-		if (preg_match('/^\s*([a-zA-Z0-9;]+)\s*$/', $sExpr, $aMatches))
-		{
-			// Constant value(s)
-			$aRes = explode(';', $aMatches[1]);
+		if (strtolower(substr($sExpr, 0, 6)) == 'select')
+		{ 
+			$sColumn = $this->Get('attribute');
+			// SELECT...
+			$oValueSetDef = new ValueSetObjects($sExpr, $sColumn);
+			$aValues = $oValueSetDef->GetValues(array('user' => $oUser), '');
+			$aRes = array_values($aValues);
 		}
 		elseif ($sExpr == '<any>')
 		{
-			$aRes = array('<any>');
+			$aRes = null;
 		}
 		else
 		{
-			$sColumn = $this->Get('attribute');
-			// SELECT...
-			$oValueSetDef = new ValueSetObjects($sExpr, $sColumn);
-			$aValues = $oValueSetDef->GetValues(array('user' => $oUser), '');
-			$aRes = array_values($aValues);
+			// Constant value(s)
+			$aRes = explode(';', trim($sExpr));
 		}
 		return $aRes;
 	}
@@ -302,22 +302,22 @@ class URP_ClassProjection extends DBObject
 	public function ProjectObject($oObject)
 	{
 		$sExpr = $this->Get('value');
-		if (preg_match('/^\s*([a-zA-Z0-9;]+)\s*$/', $sExpr, $aMatches))
-		{
-			// Constant value(s)
-			$aRes = explode(';', $aMatches[1]);
+		if (strtolower(substr($sExpr, 0, 6)) == 'select')
+		{ 
+			$sColumn = $this->Get('attribute');
+			// SELECT...
+			$oValueSetDef = new ValueSetObjects($sExpr, $sColumn);
+			$aValues = $oValueSetDef->GetValues(array('user' => $oObject), '');
+			$aRes = array_values($aValues);
 		}
 		elseif ($sExpr == '<any>')
 		{
-			$aRes = array('<any>');
+			$aRes = null;
 		}
 		else
 		{
-			$sColumn = $this->Get('attribute');
-			// SELECT...
-			$oValueSetDef = new ValueSetObjects($sExpr, $sColumn);
-			$aValues = $oValueSetDef->GetValues(array('this' => $oObject), '');
-			$aRes = array_values($aValues);
+			// Constant value(s)
+			$aRes = explode(';', trim($sExpr));
 		}
 		return $aRes;
 	}
@@ -389,7 +389,7 @@ class URP_StimulusGrant extends DBObject
 		MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array())));
 
-		MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"stimulus", "description"=>"stimulus code", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
 
 		//MetaModel::Init_InheritFilters();
 		// Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked)
@@ -498,7 +498,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
 				}
 				else
 				{
-					$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection WHERE class = '$sClass' AND dimensionid = $iDimensionId"));
+					$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection WHERE class = :class AND dimensionid = :dimension"), array(), array('class'=>$sClass, 'dimension'=>$iDimensionId));
 					$bAddCell = ($oSet->Count() < 1);
 				}
 				if ($bAddCell)
@@ -524,7 +524,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
 			}
 			else
 			{
-				$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection WHERE dimensionid = $iDimensionId AND profileid = $iProfileId"));
+				$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection WHERE dimensionid = :dimension AND profileid = :profile"), array(), array('dimension'=>$iDimensionId, 'profile'=>$iProfileId));
 				$bAddCell = ($oSet->Count() < 1);
 			}
 			if ($bAddCell)
@@ -557,7 +557,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
 					}
 					else
 					{
-						$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = '$sClass' AND action = '$sAction' AND profileid = $iProfileId"));
+						$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile"), array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfileId));
 						$bAddCell = ($oSet->Count() < 1);
 					}
 					if ($bAddCell)
@@ -579,7 +579,7 @@ class UserRightsProfile extends UserRightsAddOnAPI
 					}
 					else
 					{
-						$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND profileid = $iProfileId"));
+						$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile"), array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfileId));
 						$bAddCell = ($oSet->Count() < 1);
 					}
 					if ($bAddCell)
@@ -621,119 +621,368 @@ class UserRightsProfile extends UserRightsAddOnAPI
 		}
 	}
 
-
 	public function Init()
 	{
+		MetaModel::RegisterPlugin('userrights', 'ACbyProfile', array($this, 'CacheData'));
+	}
+
+	protected $m_aUsers = array(); // id -> object
+	protected $m_aDimensions = array(); // id -> object
+	protected $m_aClassProj = array(); // class,dimensionid -> object
+	protected $m_aProfiles = array(); // id -> object
+	protected $m_aUserProfiles = array(); // userid,profileid -> object
+	protected $m_aProPro = array(); // profileid,dimensionid -> object
+
+	protected $m_aClassActionGrants = array(); // profile, class, action -> permission
+	protected $m_aObjectActionGrants = array(); // userid, class, id, action -> permission, list of attributes
+
+	public function CacheData()
+	{
 		// Could be loaded in a shared memory (?)
+
+		$oUserSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users"));
+		while ($oUser = $oUserSet->Fetch())
+		{
+			$this->m_aUsers[$oUser->GetKey()] = $oUser; 
+		}
+
+		$oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions"));
+		while ($oDimension = $oDimensionSet->Fetch())
+		{
+			$this->m_aDimensions[$oDimension->GetKey()] = $oDimension; 
+		}
+		
+		$oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection"));
+		while ($oClassProj = $oClassProjSet->Fetch())
+		{
+			$this->m_aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; 
+		}
+
+		$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles"));
+		while ($oProfile = $oProfileSet->Fetch())
+		{
+			$this->m_aProfiles[$oProfile->GetKey()] = $oProfile; 
+		}
+
+		$oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile"));
+		while ($oUserProfile = $oUserProfileSet->Fetch())
+		{
+			$this->m_aUserProfiles[$oUserProfile->Get('userid')][$oUserProfile->Get('profileid')] = $oUserProfile; 
+		}
+
+		$oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection"));
+		while ($oProPro = $oProProSet->Fetch())
+		{
+			$this->m_aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; 
+		}
+
+/*
+		echo "<pre>\n";
+		print_r($this->m_aUsers);
+		print_r($this->m_aDimensions);
+		print_r($this->m_aClassProjs);
+		print_r($this->m_aProfiles);
+		print_r($this->m_aUserProfiles);
+		print_r($this->m_aProPros);
+		echo "</pre>\n";
+exit;
+*/
+
 		return true;
 	}
 
 	public function CheckCredentials($sUserName, $sPassword)
 	{
-		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users WHERE login = '$sUserName'"));
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users WHERE login = :login"), array(), array('login' => $sUserName));
 		if ($oSet->Count() < 1)
 		{
 		// todo: throw an exception?
 			return false;
 		}
 
-		$oLogin = $oSet->Fetch();
-		if ($oLogin->Get('password') == $sPassword)
+		$oUser = $oSet->Fetch();
+		if ($oUser->Get('password') == $sPassword)
 		{
-			return true;
+			return $oUser->GetKey();
 		}
 		// todo: throw an exception?
 		return false;
 	}
 
+	public function GetUserId($sUserName)
+	{
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Users WHERE login = :login"), array(), array('login' => $sUserName));
+		if ($oSet->Count() < 1)
+		{
+		// todo: throw an exception?
+			return false;
+		}
+
+		$oUser = $oSet->Fetch();
+		return $oUser->GetKey();
+	}
+
 	public function GetFilter($sUserName, $sClass)
 	{
 		$oNullFilter  = new DBObjectSearch($sClass);
 		return $oNullFilter;
 	}
 
-	public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances)
+	protected function GetClassActionGrant($iProfile, $sClass, $sAction)
 	{
-		// #@# temporary
-		return true;
-		if (!array_key_exists($iActionCode, self::$m_aActionCodes))
+		$aTest = @$this->m_aClassActionGrants[$iProfile][$sClass][$sAction];
+		if (isset($aTest)) return $aTest;
+
+		// Get the permission for this profile/class/action
+		$oSearch = DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = :class AND action = :action AND profileid = :profile");
+		$oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'action'=>$sAction, 'profile'=>$iProfile));
+		if ($oSet->Count() < 1)
 		{
-			return UR_ALLOWED_NO;
+			return null;
 		}
+		$oGrantRecord = $oSet->Fetch();
+
+		$this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $oGrantRecord;
+		return $oGrantRecord;
+	}
+
+	protected function GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject)
+	{
+		// load and cache permissions for the current user on the given object
+		//
+		$aTest = @$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$oObject->GetKey][$iActionCode];
+		if (is_array($aTest)) return $aTest;
+
 		$sAction = self::$m_aActionCodes[$iActionCode];
 
-		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ActionGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'"));
-		if ($oSet->Count() < 1)
+		$iInstancePermission = UR_ALLOWED_NO;
+		$aAttributes = array();
+		foreach($this->GetMatchingProfiles($oUser, $oObject) as $iProfile)
 		{
-			return UR_ALLOWED_NO;
+			$oGrantRecord = $this->GetClassActionGrant($iProfile, $sClass, $sAction);
+			if (is_null($oGrantRecord))
+			{
+				continue; // loop to the next profile
+			}
+			elseif ($oGrantRecord->Get('permission') == 'yes')
+			{
+				$iInstancePermission = UR_ALLOWED_YES;
+
+				// merge the list of attributes allowed for this profile
+				$oSearch = DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid");
+				$oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $oGrantRecord->GetKey()));
+				$aAttributes = array_merge($aAttributes, $oSet->GetColumnAsArray('attcode', false));
+			}
 		}
 
-		$oGrantRecord = $oSet->Fetch();
-		switch ($oGrantRecord->Get('permission'))
+		$aRes = array(
+			'permission' => $iInstancePermission,
+			'attributes' => $aAttributes,
+		);
+		$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$oObject->GetKey()][$iActionCode] = $aRes;
+		return $aRes;
+	}
+	
+	public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances)
+	{
+		$oUser = $this->m_aUsers[$iUserId];
+
+		$oInstances->Rewind();
+		while($oObject = $oInstances->Fetch())
+		{
+			$aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject);
+
+			$iInstancePermission = $aObjectPermissions['permission'];
+			if (isset($iGlobalPermission))
+			{
+				if ($iInstancePermission != $iGlobalPermission)
+				{
+					$iGlobalPermission = UR_ALLOWED_DEPENDS;
+				}
+			}
+			else
+			{
+				$iGlobalPermission = $iInstancePermission;
+			}
+		}
+		if (isset($iGlobalPermission))
+		{
+			return $iGlobalPermission;
+		}
+		else
 		{
-			case 'yes':
-				$iRetCode = UR_ALLOWED_YES;
-				break;
-			case 'no':
-			default:
-				$iRetCode = UR_ALLOWED_NO;
-				break;
+			return UR_ALLOWED_NO;
 		}
-		return $iRetCode;
 	}
 
-	public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances)
+	public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances)
 	{
-		// #@# temporary
-		return true;
-		if (!array_key_exists($iActionCode, self::$m_aActionCodes))
+		$oUser = $this->m_aUsers[$iUserId];
+
+		$oInstances->Rewind();
+		while($oObject = $oInstances->Fetch())
+		{
+			$aObjectPermissions = $this->GetObjectActionGrant($oUser, $sClass, $iActionCode, $oObject);
+
+			$aAttributes = $aObjectPermissions['attributes'];
+			if (in_array($sAttCode, $aAttributes))
+			{
+				$iInstancePermission = $aObjectPermissions['permission'];
+			}
+			else
+			{
+				$iInstancePermission = UR_ALLOWED_NO; 
+			}
+
+			if (isset($iGlobalPermission))
+			{
+				if ($iInstancePermission != $iGlobalPermission)
+				{
+					$iGlobalPermission = UR_ALLOWED_DEPENDS;
+				}
+			}
+			else
+			{
+				$iGlobalPermission = $iInstancePermission;
+			}
+		}
+		if (isset($iGlobalPermission))
+		{
+			return $iGlobalPermission;
+		}
+		else
 		{
 			return UR_ALLOWED_NO;
 		}
-		$sAction = self::$m_aActionCodes[$iActionCode];
+	}
 
-		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_AttributeGrant WHERE URP_AttributeGrant.class = '$sClass' AND URP_AttributeGrant.attcode = '$sAttCode' AND URP_AttributeGrant.action = '$sAction' AND URP_AttributeGrant.login = '$sUserName'"));
-		if ($oSet->Count() < 1)
+	public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances)
+	{
+		$oUser = $this->m_aUsers[$iUserId];
+
+		// Note: this code is VERY close to the code of IsActionAllowed()
+
+		$oInstances->Rewind();
+		while($oObject = $oInstances->Fetch())
+		{
+			$iInstancePermission = UR_ALLOWED_NO;
+			foreach($this->GetMatchingProfiles($oUser, $oObject) as $iProfile)
+			{
+				// Get the permission for this profile/class/stimulus
+				$oSearch = DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = :class AND stimulus = :stimulus AND profileid = :profile");
+				$oSet = new DBObjectSet($oSearch, array(), array('class'=>$sClass, 'stimulus'=>$sStimulusCode, 'profile'=>$iProfile));
+				if ($oSet->Count() < 1)
+				{
+					return UR_ALLOWED_NO;
+				}
+		
+				$oGrantRecord = $oSet->Fetch();
+				$sPermission = $oGrantRecord->Get('permission');
+				if ($sPermission == 'yes')
+				{
+					$iInstancePermission = UR_ALLOWED_YES;
+				}
+			}
+			if (isset($iGlobalPermission))
+			{
+				if ($iInstancePermission != $iGlobalPermission)
+				{
+					$iGlobalPermission = UR_ALLOWED_DEPENDS;
+				}
+			}
+			else
+			{
+				$iGlobalPermission = $iInstancePermission;
+			}
+		}
+		if (isset($iGlobalPermission))
+		{
+			return $iGlobalPermission;
+		}
+		else
 		{
 			return UR_ALLOWED_NO;
 		}
+	}
 
-		$oGrantRecord = $oSet->Fetch();
-		switch ($oGrantRecord->Get('permission'))
+	protected function GetMatchingProfilesByDim($oUser, $oObject, $oDimension)
+	{
+		//
+		// List profiles for which the user projection overlaps the object projection in the given dimension
+		//
+		$iUser = $oUser->GetKey();
+		$sClass = get_class($oObject);
+		$iPKey = $oObject->GetKey();
+		$iDimension = $oDimension->GetKey();
+
+		$aObjectProjection = $this->m_aClassProjs[$sClass][$iDimension]->ProjectObject($oObject);
+
+		$aRes = array();
+		foreach ($this->m_aUserProfiles[$iUser] as $iProfile => $oProfile)
 		{
-			case 'yes':
-				$iRetCode = UR_ALLOWED_YES;
-				break;
-			case 'no':
-			default:
-				$iRetCode = UR_ALLOWED_NO;
-				break;
+			if (is_null($aObjectProjection))
+			{
+				$aRes[] = $iProfile;
+			}
+			else
+			{
+				// user projection to be cached on a given page !
+				$aUserProjection = $this->m_aProPros[$iProfile][$iDimension]->ProjectUser($oUser);
+				
+				if (is_null($aUserProjection))
+				{
+					$aRes[] = $iProfile;
+				}
+				else
+				{
+					$aMatchingValues = array_intersect($aObjectProjection, $aUserProjection);
+					if (count($aMatchingValues) > 0)
+					{
+						$aRes[] = $iProfile;
+					}
+				}
+			}
 		}
-		return $iRetCode;
+		return $aRes;
 	}
 
-	public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances)
+	protected $m_aMatchingProfiles = array(); // cache of the matching profiles for a given user/object
+	
+	protected function GetMatchingProfiles($oUser, $oObject)
 	{
-		// #@# temporary
-		return true;
-		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_StimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND login = '$sUserName'"));
-		if ($oSet->Count() < 1)
+		$iUser = $oUser->GetKey();
+		$sClass = get_class($oObject);
+		$iObject = $oObject->GetKey();
+		//
+		// List profiles for which the user projection overlaps the object projection in each and every dimension
+		// Caches the result
+		//
+		$aTest = @$this->m_aMatchingProfiles[$iUser][$sClass][$iObject];
+		if (is_array($aTest))
 		{
-			return UR_ALLOWED_NO;
+			return $aTest;
 		}
 
-		$oGrantRecord = $oSet->Fetch();
-		switch ($oGrantRecord->Get('permission'))
+		$aProfileRes = array();
+		foreach ($this->m_aDimensions as $iDimension => $oDimension)
 		{
-			case 'yes':
-				$iRetCode = UR_ALLOWED_YES;
-				break;
-			case 'no':
-			default:
-				$iRetCode = UR_ALLOWED_NO;
-				break;
+			foreach ($this->GetMatchingProfilesByDim($oUser, $oObject, $oDimension) as $iProfile)
+			{
+				@$aProfileRes[$iProfile] += 1;
+			}
+		}
+
+		$aRes = array();
+		$iDimCount = count($this->m_aDimensions);
+		foreach ($aProfileRes as $iProfile => $iMatches)
+		{
+			if ($iMatches == $iDimCount)
+			{
+				$aRes[] = $iProfile;
+			}
 		}
-		return $iRetCode;
+		$this->m_aMatchingProfiles[$iUser][$sClass][$iObject] = $aRes;
+		return $aRes; 
 	}
 }
 

+ 34 - 8
core/dbobjectsearch.class.php

@@ -48,6 +48,7 @@ class DBObjectSearch
 	private $m_sClassAlias;
 	private $m_aClasses; // queried classes (alias => class name)
 	private $m_oSearchCondition;
+	private $m_aParams;
 	private $m_aFullText;
 	private $m_aPointingTo;
 	private $m_aReferencedBy;
@@ -64,6 +65,7 @@ class DBObjectSearch
 		$this->m_sClassAlias = $sClassAlias;
 		$this->m_aClasses = array($sClassAlias => $sClass);
 		$this->m_oSearchCondition = new TrueExpression;
+		$this->m_aParams = array();
 		$this->m_aFullText = array();
 		$this->m_aPointingTo = array();
 		$this->m_aReferencedBy = array();
@@ -174,6 +176,8 @@ class DBObjectSearch
 	{
 		$oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false);
 		$this->AddConditionExpression($oTranslated);
+		// #@# what about collisions in parameter names ???
+		$this->m_aParams = array_merge($this->m_aParams, $oFilter->m_aParams);
 	}
 
 	public function ResetCondition()
@@ -232,22 +236,22 @@ class DBObjectSearch
 			break;
 
 		case 'Contains':
-			$oRightExpr = new ScalarExpression("%$value%");
+			$this->m_aParams[$sFilterCode] = "%$value%";
 			$sOperator = 'LIKE';
 			break;
 
 		case 'Begins with':
-			$oRightExpr = new ScalarExpression("$value%");
+			$this->m_aParams[$sFilterCode] = "$value%";
 			$sOperator = 'LIKE';
 			break;
 
 		case 'Finishes with':
-			$oRightExpr = new ScalarExpression("%$value");
+			$this->m_aParams[$sFilterCode] = "%$value";
 			$sOperator = 'LIKE';
 			break;
 
 		default:
-			$oRightExpr = new ScalarExpression($value);
+			$this->m_aParams[$sFilterCode] = $value;
 			$sOperator = $sOpCode;
 		}
 
@@ -262,6 +266,7 @@ class DBObjectSearch
 		case 'Begins with':
 		case 'Finishes with':
 		default:
+			$oRightExpr = new VariableExpression($sFilterCode);
 			$oNewCondition = new BinaryExpression($oField, $sOperator, $oRightExpr);
 		}
 
@@ -453,10 +458,14 @@ class DBObjectSearch
 	{
 		return $this->m_aRelatedTo;
 	}
+	public function GetInternalParams()
+	{
+		return $this->m_aParams;
+	}
 
 	public function RenderCondition()
 	{
-		return $this->m_oSearchCondition->Render();
+		return $this->m_oSearchCondition->Render($this->m_aParams);
 	}
 
 	public function serialize()
@@ -588,11 +597,13 @@ class DBObjectSearch
 		return $retValue;
 	}
 
-	public function ToOQL()
+	public function ToOQL(&$aParams = null)
 	{
+		$bRetrofitParams = (!is_null($aParams));
+
 		$sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias();
 		$sRes .= $this->ToOQL_Joins();
-		$sRes .= " WHERE ".$this->m_oSearchCondition->Render();
+		$sRes .= " WHERE ".$this->m_oSearchCondition->Render($aParams, $bRetrofitParams);
 		return $sRes;
 	}
 
@@ -762,10 +773,20 @@ class DBObjectSearch
 		}
 	}
 
-	static public function FromOQL($sQuery, array $aParams = array(), $oObject = null)
+	static protected $m_aOQLQueries = array();
+
+	static public function FromOQL($sQuery)
 	{
 		if (empty($sQuery)) return null;
 
+		// Query caching
+		$bOQLCacheEnabled = true;
+		if ($bOQLCacheEnabled && array_key_exists($sQuery, self::$m_aOQLQueries))
+		{
+			// hit!
+			return clone self::$m_aOQLQueries[$sQuery];
+		}
+
 		$oOql = new OqlInterpreter($sQuery);
 		$oOqlQuery = $oOql->ParseObjectQuery();
 		
@@ -856,6 +877,11 @@ class DBObjectSearch
 			$oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases);
 		}
 
+		if ($bOQLCacheEnabled)
+		{
+			self::$m_aOQLQueries[$sQuery] = clone $oResultFilter;
+		}
+
 		return $oResultFilter;
 	}
 

+ 18 - 0
core/dbobjectset.class.php

@@ -92,6 +92,24 @@ class DBObjectSet
 		return $aRet;
 	} 
 
+	public function GetColumnAsArray($sAttCode, $bWithId = true)
+	{
+		$aRet = array();
+		$this->Rewind();
+		while ($oObject = $this->Fetch())
+		{
+			if ($bWithId)
+			{
+				$aRet[$oObject->GetKey()] = $oObject->Get($sAttCode);
+			}
+			else
+			{
+				$aRet[] = $oObject->Get($sAttCode);
+			}
+		}
+		return $aRet;
+	}
+
 	public function GetFilter()
 	{
 		return $this->m_oFilter;

+ 30 - 21
core/expression.class.inc.php

@@ -16,8 +16,8 @@ abstract class Expression
 	// recursive translation of identifiers
 	abstract public function Translate($aTranslationData, $bMatchAll = true);
 
-	// recursive rendering
-	abstract public function Render($aArgs = array());
+	// recursive rendering (aArgs used as input by default, or used as output if bRetrofitParams set to True
+	abstract public function Render(&$aArgs = null, $bRetrofitParams = false);
 
 	// recursively builds an array of class => fieldname
 	abstract public function ListRequiredFields();
@@ -119,11 +119,11 @@ class BinaryExpression extends Expression
 	}
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
 		$sOperator = $this->GetOperator();
-		$sLeft = $this->GetLeftExpr()->Render($aArgs);
-		$sRight = $this->GetRightExpr()->Render($aArgs);
+		$sLeft = $this->GetLeftExpr()->Render($aArgs, $bRetrofitParams);
+		$sRight = $this->GetRightExpr()->Render($aArgs, $bRetrofitParams);
 		return "($sLeft $sOperator $sRight)";
 	}
 
@@ -164,9 +164,18 @@ class UnaryExpression extends Expression
 	} 
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
-		return CMDBSource::Quote($this->m_value);
+		if ($bRetrofitParams)
+		{
+			$iParamIndex = count($aArgs) + 1; // 1-based indexation
+			$aArgs['param'.$iParamIndex] = $this->m_value;
+			return ':param'.$iParamIndex;
+		}
+		else
+		{
+			return CMDBSource::Quote($this->m_value);
+		}
 	}
 
 	public function Translate($aTranslationData, $bMatchAll = true)
@@ -228,7 +237,7 @@ class FieldExpression extends UnaryExpression
 	public function GetName() {return $this->m_sName;}
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
 		if (empty($this->m_sParent))
 		{
@@ -290,15 +299,15 @@ class VariableExpression extends UnaryExpression
 	public function GetName() {return $this->m_sName;}
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
-		if (array_key_exists($this->m_sName, $aArgs))
+		if (is_null($aArgs) || $bRetrofitParams)
 		{
-			return $aArgs[$this->m_sName];
+			return ':'.$this->m_sName;
 		}
-		elseif (is_null($aArgs))
+		elseif (array_key_exists($this->m_sName, $aArgs))
 		{
-			return ':'.$this->m_sName;
+			return CMDBSource::Quote($aArgs[$this->m_sName]);
 		}
 		else
 		{
@@ -330,12 +339,12 @@ class ListExpression extends Expression
 	}
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
 		$aRes = array();
 		foreach ($this->m_aExpressions as $oExpr)
 		{
-			$aRes[] = $oExpr->Render($aArgs);
+			$aRes[] = $oExpr->Render($aArgs, $bRetrofitParams);
 		}
 		return '('.implode(', ', $aRes).')';
 	}
@@ -390,12 +399,12 @@ class FunctionExpression extends Expression
 	}
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
 		$aRes = array();
 		foreach ($this->m_aArgs as $oExpr)
 		{
-			$aRes[] = $oExpr->Render($aArgs);
+			$aRes[] = $oExpr->Render($aArgs, $bRetrofitParams);
 		}
 		return $this->m_sVerb.'('.implode(', ', $aRes).')';
 	}
@@ -449,9 +458,9 @@ class IntervalExpression extends Expression
 	}
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
-		return 'INTERVAL '.$this->m_oValue->Render($aArgs).' '.$this->m_sUnit;
+		return 'INTERVAL '.$this->m_oValue->Render($aArgs, $bRetrofitParams).' '.$this->m_sUnit;
 	}
 
 	public function Translate($aTranslationData, $bMatchAll = true)
@@ -486,12 +495,12 @@ class CharConcatExpression extends Expression
 	}
 
 	// recursive rendering
-	public function Render($aArgs = array())
+	public function Render(&$aArgs = null, $bRetrofitParams = false)
 	{
 		$aRes = array();
 		foreach ($this->m_aExpressions as $oExpr)
 		{
-			$sCol = $oExpr->Render($aArgs);
+			$sCol = $oExpr->Render($aArgs, $bRetrofitParams);
 			// Concat will be globally NULL if one single argument is null ! 
 			$aRes[] = "COALESCE($sCol, '')";
 		}

+ 93 - 9
core/metamodel.class.php

@@ -164,6 +164,10 @@ abstract class MetaModel
 		}
 	}
 
+	private static $m_bTraceQueries = true;
+	private static $m_aQueriesLog = array();
+	
+
 	private static $m_sDBName = "";
 	private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB)
 	private static $m_Category2Class = array();
@@ -1167,15 +1171,36 @@ abstract class MetaModel
 		return false;
 	}
 
+	protected static $m_aQueryStructCache = array();
+
 	public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array(), $aArgs = array())
 	{
-		$aTranslation = array();
-		$aClassAliases = array();
-		$aTableAliases = array();
-		$oConditionTree = $oFilter->GetCriteria();
-		$oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter);
+		// Query caching
+		//
+		$bQueryCacheEnabled = true;
+		$sOqlQuery = $oFilter->ToOql();
+		if ($bQueryCacheEnabled)
+		{
+			if (array_key_exists($sOqlQuery, self::$m_aQueryStructCache))
+			{
+				// hit!
+				$oSelect = clone self::$m_aQueryStructCache[$sOqlQuery];
+			}
+		}
+
+		if (!isset($oSelect))
+		{
+			$aTranslation = array();
+			$aClassAliases = array();
+			$aTableAliases = array();
+			$oConditionTree = $oFilter->GetCriteria();
+			$oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter);
+
+			self::$m_aQueryStructCache[$sOqlQuery] = clone $oSelect;
+		}
 
 		// Check the order by specification
+		//
 		foreach ($aOrderBy as $sFieldAlias => $bAscending)
 		{
 			MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass()));
@@ -1196,7 +1221,7 @@ abstract class MetaModel
 		
 		// Prepare arguments (translate any object into scalars)
 		//
-		$aScalarArgs = array();
+		$aScalarArgs = $oFilter->GetInternalParams();
 		foreach($aArgs as $sArgName => $value)
 		{
 			if (self::IsValidObject($value))
@@ -1215,9 +1240,41 @@ abstract class MetaModel
 				$aScalarArgs[$sArgName] = (string) $value;
 			}
 		}
-		
-		//MyHelpers::var_dump_html($oSelect->RenderSelect($aOrderBy));
-		return $oSelect->RenderSelect($aOrderBy, $aScalarArgs);
+
+		// Go
+		//
+		$sRes = $oSelect->RenderSelect($aOrderBy, $aScalarArgs);
+
+		if (self::$m_bTraceQueries)
+		{
+			$aParams = array();
+			if (!array_key_exists($sOqlQuery, self::$m_aQueriesLog))
+			{
+				self::$m_aQueriesLog[$sOqlQuery] = array(
+					'sql' => array(),
+					'count' => 0,
+				);
+			}
+			self::$m_aQueriesLog[$sOqlQuery]['count']++;
+			self::$m_aQueriesLog[$sOqlQuery]['sql'][] = $sRes;
+		}
+
+		return $sRes;
+	}
+
+	public static function ShowQueryTrace()
+	{
+		$iTotal = 0;
+		foreach (self::$m_aQueriesLog as $sOql => $aQueryData)
+		{
+			echo "<h2>$sOql</h2>\n";
+			$iTotal += $aQueryData['count'];
+			echo '<p>'.$aQueryData['count'].'</p>';
+			echo '<p>Example: '.$aQueryData['sql'][0].'</p>';
+		}
+		echo "<h2>Total</h2>\n";
+		echo "<p>Count of executed queries: $iTotal</p>";
+		echo "<p>Count of built queries: ".count(self::$m_aQueriesLog)."</p>";
 	}
 
 	public static function MakeDeleteQuery(DBObjectSearch $oFilter)
@@ -2390,6 +2447,8 @@ abstract class MetaModel
 				throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName));
 			}
 		}
+		// Some of the init could not be done earlier (requiring classes to be declared and DB to be accessible)
+		self::InitPlugins();
 	}
 
 	public static function LoadConfig($sConfigFile)
@@ -2425,6 +2484,15 @@ abstract class MetaModel
 		CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist)
 	}
 
+	protected static $m_aPlugins = array();
+	public static function RegisterPlugin($sType, $sName, $aInitCallSpec = array())
+	{
+		self::$m_aPlugins[$sName] = array(
+			'type' => $sType,
+			'init' => $aInitCallSpec,
+		);
+	}
+
 	protected static function Plugin($sConfigFile, $sModuleType, $sToInclude)
 	{
 		if (!file_exists($sToInclude))
@@ -2434,6 +2502,22 @@ abstract class MetaModel
 		require_once($sToInclude);
 	}
 
+	protected static function InitPlugins()
+	{
+		foreach(self::$m_aPlugins as $sName => $aData)
+		{
+			$aCallSpec = @$aData['init'];
+			if (count($aCallSpec) == 2)
+			{
+				if (!is_callable($aCallSpec))
+				{
+					throw new CoreException('Wrong declaration in plugin', array('plugin' => $aData['name'], 'type' => $aData['type'], 'class' => $aData['class'], 'init' => $aData['init'])); 
+				}
+				call_user_func($aCallSpec);
+			}
+		}
+	}
+
 	// Building an object
 	//
 	//

+ 44 - 13
core/userrights.class.inc.php

@@ -46,11 +46,12 @@ abstract class UserRightsAddOnAPI
 {
 	abstract public function Setup(); // initial installation
 	abstract public function Init(); // loads data (possible optimizations)
-	abstract public function CheckCredentials($iUserId, $sPassword); // returns the id of the user or false
-	abstract public function GetFilter($iUserId, $sClass); // returns a filter object
-	abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances);
-	abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances);
-	abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances);
+	abstract public function CheckCredentials($sLogin, $sPassword); // returns the id of the user or false
+	abstract public function GetUserId($sLogin); // returns the id of the user or false
+	abstract public function GetFilter($sLogin, $sClass); // returns a filter object
+	abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $oInstances);
+	abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $oInstances);
+	abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances);
 }
 
 
@@ -148,9 +149,18 @@ class UserRights
 		return self::$m_sUser;
 	}
 
-	public static function GetUserId()
+	public static function GetUserId($sName = '')
 	{
-		return self::$m_iUserId;
+		if (empty($sName))
+		{
+			// return current user id
+			return self::$m_iUserId;
+		}
+		else
+		{
+			// find the id out of the login string
+			return self::$m_oAddOn->GetUserId($sName);
+		}
 	}
 
 	public static function GetRealUser()
@@ -182,28 +192,49 @@ class UserRights
 		return self::$m_oAddOn->GetFilter(self::$m_iUserId, $sClass);
 	}
 
-	public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $aInstances)
+	public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $oInstances, $iUserId = null)
 	{
 		if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true;
 		if (!self::CheckLogin()) return false;
 
-		return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $aInstances);
+		if (is_null($iUserId))
+		{
+			return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $oInstances);
+		}
+		else
+		{
+			return self::$m_oAddOn->IsActionAllowed($iUserId, $sClass, $iActionCode, $oInstances);
+		}
 	}
 
-	public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $aInstances)
+	public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $oInstances, $iUserId = null)
 	{
 		if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true;
 		if (!self::CheckLogin()) return false;
 
-		return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $aInstances);
+		if (is_null($iUserId))
+		{
+			return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $oInstances);
+		}
+		else
+		{
+			return self::$m_oAddOn->IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, $oInstances);
+		}
 	}
 
-	public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances)
+	public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $oInstances, $iUserId = null)
 	{
 		if (!MetaModel::HasCategory($sClass, 'bizmodel')) return true;
 		if (!self::CheckLogin()) return false;
 
-		return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $aInstances);
+		if (is_null($iUserId))
+		{
+			return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $oInstances);
+		}
+		else
+		{
+			return self::$m_oAddOn->IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, $oInstances);
+		}
 	}
 }
 

+ 8 - 1
pages/usermanagement_classproj.php

@@ -55,7 +55,14 @@ function ComputeProjections($oPage, $sScope)
 			$oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension]);
 
 			$aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject);
-			$sValues = implode(', ', $aValues);
+			if (is_null($aValues))
+			{
+				$sValues = '<any>';
+			}
+			else
+			{
+				$sValues = implode(', ', $aValues);
+			}
 			$oObjectProj['dim'.$oDimension->GetKey()] = htmlentities($sValues);
 		}
 	

+ 8 - 1
pages/usermanagement_profileproj.php

@@ -65,7 +65,14 @@ function ComputeProjections($oPage)
 				$oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension]);
 	
 				$aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser);
-				$sValues = implode(', ', $aValues);
+				if (is_null($aValues))
+				{
+					$sValues = '<any>';
+				}
+				else
+				{
+					$sValues = implode(', ', $aValues);
+				}
 				$aUserProfileProj['dim'.$oDimension->GetKey()] = htmlentities($sValues);
 			}
 		

+ 285 - 0
pages/usermanagement_userstatus.php

@@ -0,0 +1,285 @@
+<?php
+require_once('../application/application.inc.php');
+require_once('../application/itopwebpage.class.inc.php');
+
+require_once('../application/startup.inc.php');
+
+
+function ComputeObjectProjections($oPage, $oObject)
+{
+	// Load the classes for a further usage
+	//
+	$aClasses = MetaModel::GetClasses();
+	
+	// Load the dimensions for a further usage
+	//
+	$aDimensions = array();
+	$oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions"));
+	while ($oDimension = $oDimensionSet->Fetch())
+	{
+		$aDimensions[$oDimension->GetKey()] = $oDimension; 
+	}
+	
+	// Load the class projections for a further usage
+	//
+	$aClassProj = array();
+	$oClassProjSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ClassProjection"));
+	while ($oClassProj = $oClassProjSet->Fetch())
+	{
+		$aClassProjs[$oClassProj->Get('class')][$oClassProj->Get('dimensionid')] = $oClassProj; 
+	}
+	
+	// Setup display structure
+	//
+	$aDisplayConfig = array();
+	foreach ($aDimensions as $iDimension => $oDimension)
+	{
+		$aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description'));
+	}
+	
+	// Load objects
+	//
+	$aDisplayData = array();
+	$sClass = get_class($oObject);
+	$aObjectProj = array();
+	foreach ($aDimensions as $iDimension => $oDimension)
+	{
+		// #@# to be moved, may be time consuming
+		$oDimension->CheckProjectionSpec($aClassProjs[$sClass][$iDimension]);
+
+		$aValues = $aClassProjs[$sClass][$iDimension]->ProjectObject($oObject);
+		if (is_null($aValues))
+		{
+			$sValues = '<any>';
+		}
+		else
+		{
+			$sValues = implode(', ', $aValues);
+		}
+		$oObjectProj['dim'.$oDimension->GetKey()] = htmlentities($sValues);
+	}
+
+	$aDisplayData[] = $oObjectProj;
+
+	$oPage->table($aDisplayConfig, $aDisplayData);
+}
+
+
+function ComputeUserProjections($oPage, $oUser)
+{
+	// Load the profiles for a further usage
+	//
+	$aProfiles = array();
+	$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Profiles"));
+	while ($oProfile = $oProfileSet->Fetch())
+	{
+		$aProfiles[$oProfile->GetKey()] = $oProfile; 
+	}
+	
+	// Load the dimensions for a further usage
+	//
+	$aDimensions = array();
+	$oDimensionSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_Dimensions"));
+	while ($oDimension = $oDimensionSet->Fetch())
+	{
+		$aDimensions[$oDimension->GetKey()] = $oDimension; 
+	}
+	
+	// Load the profile projections for a further usage
+	//
+	$aProPro = array();
+	$oProProSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_ProfileProjection"));
+	while ($oProPro = $oProProSet->Fetch())
+	{
+		$aProPros[$oProPro->Get('profileid')][$oProPro->Get('dimensionid')] = $oProPro; 
+	}
+	
+	// Setup display structure
+	//
+	$aDisplayConfig = array();
+	$aDisplayConfig['profile'] = array('label' => 'Profile', 'description' => 'Profile in which the projection is specified');
+	foreach ($aDimensions as $iDimension => $oDimension)
+	{
+		$aDisplayConfig['dim'.$oDimension->GetKey()] = array('label' => $oDimension->GetName(), 'description' => $oDimension->Get('description'));
+	}
+	
+	// Create a record per profile
+	//
+	$aDisplayData = array();
+	$oUserProfileSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT URP_UserProfile WHERE userid = :user->id"), array(), array('user' => $oUser));
+	while ($oUserProfile = $oUserProfileSet->Fetch())
+	{
+		$iProfile = $oUserProfile->Get('profileid');
+		$oProfile = $aProfiles[$iProfile];
+
+		$aUserProfileProj = array();
+		$aUserProfileProj['profile'] = $oProfile->GetName();
+		foreach ($aDimensions as $iDimension => $oDimension)
+		{
+			// #@# to be moved, may be time consuming
+			$oDimension->CheckProjectionSpec($aProPros[$iProfile][$iDimension]);
+
+			$aValues = $aProPros[$iProfile][$iDimension]->ProjectUser($oUser);
+			if (is_null($aValues))
+			{
+				$sValues = '<any>';
+			}
+			else
+			{
+				$sValues = implode(', ', $aValues);
+			}
+			$aUserProfileProj['dim'.$oDimension->GetKey()] = htmlentities($sValues);
+		}
+	
+		$aDisplayData[] = $aUserProfileProj;
+	}
+
+	$oPage->table($aDisplayConfig, $aDisplayData);
+}
+
+
+function ComputeUserRights($oPage, $oUser, $oObject)
+{
+	// Set the stage
+	//
+	$iUser = $oUser->GetKey();
+	$sClass = get_class($oObject);
+	$iPKey = $oObject->GetKey();
+	$oInstances = DBObjectSet::FromArray($sClass, array($oObject));
+	$aPermissions = array(
+		UR_ALLOWED_NO => '<span style="background-color: #ffdddd;">UR_ALLOWED_NO</span>',
+		UR_ALLOWED_YES => '<span style="background-color: #ddffdd;">UR_ALLOWED_YES</span>',
+		UR_ALLOWED_DEPENDS => '<span style="">UR_ALLOWED_DEPENDS</span>',
+	);
+	$aActions = array(
+		UR_ACTION_READ => 'Read',
+		UR_ACTION_MODIFY => 'Modify',
+		UR_ACTION_DELETE => 'Delete',
+		UR_ACTION_BULK_READ => 'Bulk Read',
+		UR_ACTION_BULK_MODIFY => 'Bulk Modify',
+		UR_ACTION_BULK_DELETE => 'Bulk Delete',
+	);
+	$aAttributeActions = array(
+		UR_ACTION_READ => 'Read',
+		UR_ACTION_MODIFY => 'Modify',
+		UR_ACTION_BULK_READ => 'Bulk Read',
+		UR_ACTION_BULK_MODIFY => 'Bulk Modify',
+	);
+
+	// Determine allowed actions for the object
+	//
+	$aDisplayData = array();
+	foreach($aActions as $iActionCode => $sActionDesc)
+	{
+		$iPermission = UserRights::IsActionAllowed($sClass, $iActionCode, $oInstances, $iUser);
+		$aDisplayData[] = array(
+			'action' => $sActionDesc,
+			'permission' => $aPermissions[$iPermission],
+		);
+	}	
+	$aDisplayConfig = array();
+	$aDisplayConfig['action'] = array('label' => 'Action', 'description' => '');
+	$aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => '');
+	$oPage->p('<h3>Actions</h3>');
+	$oPage->table($aDisplayConfig, $aDisplayData);
+
+
+	// Determine allowed actions for the object
+	//
+	$aDisplayData = array();
+	foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+	{
+		if (!$oAttDef->IsDirectField()) continue;
+
+		foreach($aAttributeActions as $iActionCode => $sActionDesc)
+		{
+			$iPermission = UserRights::IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, $oInstances, $iUser);
+			$aDisplayData[] = array(
+				'attribute' => $sAttCode,
+				'action' => $sActionDesc,
+				'permission' => $aPermissions[$iPermission],
+			);
+		}
+	}
+	$oPage->p('<h3>Attributes</h3>');
+	if (count($aDisplayData) > 0)
+	{
+		$aDisplayConfig = array();
+		$aDisplayConfig['attribute'] = array('label' => 'Attribute', 'description' => '');
+		$aDisplayConfig['action'] = array('label' => 'Action', 'description' => '');
+		$aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => '');
+		$oPage->table($aDisplayConfig, $aDisplayData);
+	}
+	else
+	{
+		$oPage->p('<em>none</em>');
+	}
+
+	// Determine allowed stimuli
+	//
+	$aDisplayData = array();
+	foreach(MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus)
+	{
+		$iPermission = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oInstances, $iUser);
+		$aDisplayData[] = array(
+			'stimulus' => $sStimulusCode,
+			'permission' => $aPermissions[$iPermission],
+		);
+	}
+	$oPage->p('<h3>Stimuli</h3>');
+	if (count($aDisplayData) > 0)
+	{
+		$aDisplayConfig = array();
+		$aDisplayConfig['stimulus'] = array('label' => 'Stimulus', 'description' => '');
+		$aDisplayConfig['permission'] = array('label' => 'Permission', 'description' => '');
+		$oPage->table($aDisplayConfig, $aDisplayData);
+	}
+	else
+	{
+		$oPage->p('<em>none</em>');
+	}
+}
+
+
+require_once('../application/loginwebpage.class.inc.php');
+login_web_page::DoLogin(); // Check user rights and prompt if needed
+
+// Display the menu on the left
+$oContext = new UserContext();
+$oAppContext = new ApplicationContext();
+$iActiveNodeId = utils::ReadParam('menu', -1);
+$currentOrganization = utils::ReadParam('org_id', 1);
+$iUser = utils::ReadParam('user_id', -1);
+$sObjectClass = utils::ReadParam('object_class', '');
+$iObjectId = utils::ReadParam('object_id', 0);
+
+$oPage = new iTopWebPage("iTop user management - user status", $currentOrganization);
+$oPage->no_cache();
+
+
+if ($iUser == -1)
+{
+	$oPage->p('Missing parameter "user_id" - current user is '.UserRights::GetUserId());
+}
+else
+{
+	$oUser = MetaModel::GetObject('URP_Users', $iUser);
+
+	$oPage->p('<h2>Projections for user '.$oUser->GetName().'</h2>');
+	ComputeUserProjections($oPage, $oUser);
+
+	if (strlen($sObjectClass) != 0)
+	{
+		$oObject = MetaModel::GetObject($sObjectClass, $iObjectId);
+
+		$oPage->p('<h2>Projections for object '.$oObject->GetName().'</h2>');
+		ComputeObjectProjections($oPage, $oObject);
+
+		$oPage->p('<h2>Resulting rights</h2>');
+		ComputeUserRights($oPage, $oUser, $oObject);
+	}
+}
+
+$oPage->output();
+
+?>