浏览代码

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 年之前
父节点
当前提交
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();
+
+?>