Ver Fonte

Profiles defined in XML (setup + runtime), beta version (stable, upgrade required)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2149 a333f486-631f-4898-b8df-5754b55c2be0
romainq há 13 anos atrás
pai
commit
3988ebc5be

+ 77 - 358
addons/userrights/userrightsprofile.class.inc.php

@@ -47,28 +47,6 @@ class UserRightsBaseClassGUI extends cmdbAbstractObject
 	}
 }
 
-class UserRightsBaseClass extends DBObject
-{
-	// Whenever something changes, reload the privileges
-	
-	protected function AfterInsert()
-	{
-		UserRights::FlushPrivileges();
-	}
-
-	protected function AfterUpdate()
-	{
-		UserRights::FlushPrivileges();
-	}
-
-	protected function AfterDelete()
-	{
-		UserRights::FlushPrivileges();
-	}
-}
-
-
-
 
 class URP_Profiles extends UserRightsBaseClassGUI
 {
@@ -101,27 +79,9 @@ class URP_Profiles extends UserRightsBaseClassGUI
 		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
 	}
 
-	protected $m_bCheckReservedNames = true;
-	protected function DisableCheckOnReservedNames()
-	{
-		$this->m_bCheckReservedNames = false;
-	}
-
-	
-	protected static $m_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',
-	);
-
-	protected static $m_aCacheActionGrants = null;
-	protected static $m_aCacheStimulusGrants = null;
 	protected static $m_aCacheProfiles = null;
 	
-	public static function DoCreateProfile($sName, $sDescription, $bReservedName = false)
+	public static function DoCreateProfile($sName, $sDescription)
 	{
 		if (is_null(self::$m_aCacheProfiles))
 		{
@@ -142,118 +102,19 @@ class URP_Profiles extends UserRightsBaseClassGUI
 		$oNewObj = MetaModel::NewObject("URP_Profiles");
 		$oNewObj->Set('name', $sName);
 		$oNewObj->Set('description', $sDescription);
-		if ($bReservedName)
-		{
-			$oNewObj->DisableCheckOnReservedNames();			
-		}
 		$iId = $oNewObj->DBInsertNoReload();
 		self::$m_aCacheProfiles[$sCacheKey] = $iId;	
 		return $iId;
 	}
 	
-	public static function DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true)
-	{
-		$sAction = self::$m_aActions[$iAction];
-	
-		if (is_null(self::$m_aCacheActionGrants))
-		{
-			self::$m_aCacheActionGrants = array();
-			$oFilterAll = new DBObjectSearch('URP_ActionGrant');
-			$oSet = new DBObjectSet($oFilterAll);
-			while ($oGrant = $oSet->Fetch())
-			{
-				self::$m_aCacheActionGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('action').'-'.$oGrant->Get('class')] = $oGrant->GetKey();
-			}
-		}	
-
-		$sCacheKey = "$iProfile-$sAction-$sClass";
-		if (isset(self::$m_aCacheActionGrants[$sCacheKey]))
-		{
-			return self::$m_aCacheActionGrants[$sCacheKey];
-		}
-
-		$oNewObj = MetaModel::NewObject("URP_ActionGrant");
-		$oNewObj->Set('profileid', $iProfile);
-		$oNewObj->Set('permission', $bPermission ? 'yes' : 'no');
-		$oNewObj->Set('class', $sClass);
-		$oNewObj->Set('action', $sAction);
-		$iId = $oNewObj->DBInsertNoReload();
-		self::$m_aCacheActionGrants[$sCacheKey] = $iId;	
-		return $iId;
-	}
-	
-	public static function DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass)
-	{
-		if (is_null(self::$m_aCacheStimulusGrants))
-		{
-			self::$m_aCacheStimulusGrants = array();
-			$oFilterAll = new DBObjectSearch('URP_StimulusGrant');
-			$oSet = new DBObjectSet($oFilterAll);
-			while ($oGrant = $oSet->Fetch())
-			{
-				self::$m_aCacheStimulusGrants[$oGrant->Get('profileid').'-'.$oGrant->Get('stimulus').'-'.$oGrant->Get('class')] = $oGrant->GetKey();
-			}
-		}	
-
-		$sCacheKey = "$iProfile-$sStimulusCode-$sClass";
-		if (isset(self::$m_aCacheStimulusGrants[$sCacheKey]))
-		{
-			return self::$m_aCacheStimulusGrants[$sCacheKey];
-		}
-		$oNewObj = MetaModel::NewObject("URP_StimulusGrant");
-		$oNewObj->Set('profileid', $iProfile);
-		$oNewObj->Set('permission', 'yes');
-		$oNewObj->Set('class', $sClass);
-		$oNewObj->Set('stimulus', $sStimulusCode);
-		$iId = $oNewObj->DBInsertNoReload();
-		self::$m_aCacheStimulusGrants[$sCacheKey] = $iId;	
-		return $iId;
-	}
-	
-	/*
-	* Create the built-in Administrator profile with its reserved name
-	*/	
-	public static function DoCreateAdminProfile()
-	{
-		self::DoCreateProfile(ADMIN_PROFILE_NAME, 'Has the rights on everything (bypassing any control)', true /* reserved name */);
-	}
-
-	/*
-	* Overload the standard behavior to preserve reserved names
-	*/	
-	public function DoCheckToWrite()
+	function GetGrantAsHtml($oUserRights, $sClass, $sAction)
 	{
-		parent::DoCheckToWrite();
-
-		if ($this->m_bCheckReservedNames)
+		$bGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction);
+		if (is_null($bGrant))
 		{
-			$aChanges = $this->ListChanges();
-			if (array_key_exists('name', $aChanges))
-			{
-				if ($this->GetOriginal('name') == ADMIN_PROFILE_NAME)
-				{
-					$this->m_aCheckIssues[] = "The name of the Administrator profile must not be changed";
-				}
-				elseif ($this->Get('name') == ADMIN_PROFILE_NAME)
-				{
-					$this->m_aCheckIssues[] = ADMIN_PROFILE_NAME." is a reserved to the built-in Administrator profile";
-				}
-				elseif ($this->GetOriginal('name') == PORTAL_PROFILE_NAME)
-				{
-					$this->m_aCheckIssues[] = "The name of the User Portal profile must not be changed";
-				}
-				elseif ($this->Get('name') == PORTAL_PROFILE_NAME)
-				{
-					$this->m_aCheckIssues[] = PORTAL_PROFILE_NAME." is a reserved to the built-in User Portal profile";
-				}
-			}
+			return '<span style="background-color: #ffdddd;">'.Dict::S('UI:UserManagement:ActionAllowed:No').'</span>';
 		}
-	}
-
-	function GetGrantAsHtml($oUserRights, $sClass, $sAction)
-	{
-		$iGrant = $oUserRights->GetProfileActionGrant($this->GetKey(), $sClass, $sAction);
-		if (!is_null($iGrant))
+		elseif ($bGrant)
 		{
 			return '<span style="background-color: #ddffdd;">'.Dict::S('UI:UserManagement:ActionAllowed:Yes').'</span>';
 		}
@@ -284,8 +145,8 @@ class URP_Profiles extends UserRightsBaseClassGUI
 			$aStimuli = array();
 			foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus)
 			{
-				$oGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode);
-				if (is_object($oGrant) && ($oGrant->Get('permission') == 'yes'))
+				$bGrant = $oUserRights->GetClassStimulusGrant($this->GetKey(), $sClass, $sStimulusCode);
+				if ($bGrant === true)
 				{ 
 					$aStimuli[] = '<span title="'.$sStimulusCode.': '.htmlentities($oStimulus->GetDescription(), ENT_QUOTES, 'UTF-8').'">'.htmlentities($oStimulus->GetLabel(), ENT_QUOTES, 'UTF-8').'</span>';
 				}
@@ -294,12 +155,12 @@ class URP_Profiles extends UserRightsBaseClassGUI
 			
 			$aDisplayData[] = array(
 				'class' => MetaModel::GetName($sClass),
-				'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Read'),
-				'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Read'),
-				'write' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Modify'),
-				'bulkwrite' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Modify'),
-				'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Delete'),
-				'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'Bulk Delete'),
+				'read' => $this->GetGrantAsHtml($oUserRights, $sClass, 'r'),
+				'bulkread' => $this->GetGrantAsHtml($oUserRights, $sClass, 'br'),
+				'write' => $this->GetGrantAsHtml($oUserRights, $sClass, 'w'),
+				'bulkwrite' => $this->GetGrantAsHtml($oUserRights, $sClass, 'bw'),
+				'delete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'd'),
+				'bulkdelete' => $this->GetGrantAsHtml($oUserRights, $sClass, 'bd'),
 				'stimuli' => $sStimuli,
 			);
 		}
@@ -325,6 +186,30 @@ class URP_Profiles extends UserRightsBaseClassGUI
 			$this->DoShowGrantSumary($oPage);		
 		}
 	}
+
+	public static function GetConstantColumns()
+	{
+		return array('name', 'description');
+	}
+
+
+	// returns an array of id => array of column => php value(so-called "real value")
+	public static function GetConstantValues()
+	{
+		return ProfilesConfig::GetProfilesValues();
+	}
+
+	// Before deleting a profile,
+	// preserve DB integrity by deleting links to users
+	protected function OnDelete()
+	{
+		// Note: this may break the rule that says: "a user must have at least ONE profile" !
+		$oLnkSet = $this->Get('user_list');
+		while($oLnk = $oLnkSet->Fetch())
+		{
+			$oLnk->DBDelete();
+		}
+	}
 }
 
 
@@ -410,123 +295,17 @@ class URP_UserOrg extends UserRightsBaseClassGUI
 }
 
 
-class URP_ActionGrant extends UserRightsBaseClass
-{
-	public static function Init()
-	{
-		$aParams = array
-		(
-			"category" => "addon/userrights",
-			"key_type" => "autoincrement",
-			"name_attcode" => "profileid",
-			"state_attcode" => "",
-			"reconc_keys" => array(),
-			"db_table" => "priv_urp_grant_actions",
-			"db_key_field" => "id",
-			"db_finalclass_field" => "",
-			"display_template" => "",
-		);
-		MetaModel::Init_Params($aParams);
-		//MetaModel::Init_InheritAttributes();
-
-		// Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked)
-		MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name")));
-		MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array())));
-
-		MetaModel::Init_AddAttribute(new AttributeString("action", array("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
-
-		// Display lists
-		MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'action')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('class', 'permission', 'action')); // Attributes to be displayed for a list
-		// Search criteria
-		MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the std search form
-		MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'action')); // Criteria of the advanced search form
-	}
-}
-
-
-class URP_StimulusGrant extends UserRightsBaseClass
-{
-	public static function Init()
-	{
-		$aParams = array
-		(
-			"category" => "addon/userrights",
-			"key_type" => "autoincrement",
-			"name_attcode" => "profileid",
-			"state_attcode" => "",
-			"reconc_keys" => array(),
-			"db_table" => "priv_urp_grant_stimulus",
-			"db_key_field" => "id",
-			"db_finalclass_field" => "",
-			"display_template" => "",
-		);
-		MetaModel::Init_Params($aParams);
-		//MetaModel::Init_InheritAttributes();
-
-		// Common to all grant classes (could be factorized by class inheritence, but this has to be benchmarked)
-		MetaModel::Init_AddAttribute(new AttributeExternalKey("profileid", array("targetclass"=>"URP_Profiles", "jointype"=> "", "allowed_values"=>null, "sql"=>"profileid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeExternalField("profile", array("allowed_values"=>null, "extkey_attcode"=> 'profileid', "target_attcode"=>"name")));
-		MetaModel::Init_AddAttribute(new AttributeClass("class", array("class_category"=>"", "more_values"=>"", "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("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("allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
-
-		// Display lists
-		MetaModel::Init_SetZListItems('details', array('profileid', 'class', 'permission', 'stimulus')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('class', 'permission', 'stimulus')); // Attributes to be displayed for a list
-		// Search criteria
-		MetaModel::Init_SetZListItems('standard_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the std search form
-		MetaModel::Init_SetZListItems('advanced_search', array('profileid', 'class', 'permission', 'stimulus')); // Criteria of the advanced search form
-	}
-}
-
-
-class URP_AttributeGrant extends UserRightsBaseClass
-{
-	public static function Init()
-	{
-		$aParams = array
-		(
-			"category" => "addon/userrights",
-			"key_type" => "autoincrement",
-			"name_attcode" => "actiongrantid",
-			"state_attcode" => "",
-			"reconc_keys" => array(),
-			"db_table" => "priv_urp_grant_attributes",
-			"db_key_field" => "id",
-			"db_finalclass_field" => "",
-			"display_template" => "",
-		);
-		MetaModel::Init_Params($aParams);
-		//MetaModel::Init_InheritAttributes();
-
-		MetaModel::Init_AddAttribute(new AttributeExternalKey("actiongrantid", array("targetclass"=>"URP_ActionGrant", "jointype"=> "", "allowed_values"=>null, "sql"=>"actiongrantid", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeString("attcode", array("allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-
-		// Display lists
-		MetaModel::Init_SetZListItems('details', array('actiongrantid', 'attcode')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('attcode')); // Attributes to be displayed for a list
-		// Search criteria
-		MetaModel::Init_SetZListItems('standard_search', array('actiongrantid', 'attcode')); // Criteria of the std search form
-		MetaModel::Init_SetZListItems('advanced_search', array('actiongrantid', 'attcode')); // Criteria of the advanced search form
-	}
-}
-
-
 
 
 class UserRightsProfile extends UserRightsAddOnAPI
 {
 	static public $m_aActionCodes = 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',
+		UR_ACTION_READ => 'r',
+		UR_ACTION_MODIFY => 'w',
+		UR_ACTION_DELETE => 'd',
+		UR_ACTION_BULK_READ => 'br',
+		UR_ACTION_BULK_MODIFY => 'bw',
+		UR_ACTION_BULK_DELETE => 'bd',
 	);
 
 	// Installation: create the very first user
@@ -604,10 +383,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
 	protected $m_aUserProfiles = array(); // userid,profileid -> object
 	protected $m_aUserOrgs = array(); // userid -> array of orgid
 
-	// Those arrays could be completed on demand (inheriting parent permissions)
-	protected $m_aClassActionGrants = null; // profile, class, action -> actiongrantid (or false if NO, or null/missing if undefined)
-	protected $m_aClassStimulusGrants = array(); // profile, class, stimulus -> permission
-
 	// Built on demand, could be optimized if necessary (doing a query for each attribute that needs to be read)
 	protected $m_aObjectActionGrants = array();
 
@@ -686,32 +461,10 @@ class UserRightsProfile extends UserRightsAddOnAPI
 		$this->m_aAdmins = array();
 		$this->m_aPortalUsers = array();
 
-		// Loaded on demand (time consuming as compared to the others)
-		$this->m_aClassActionGrants = null;
-		$this->m_aClassStimulusGrants = null;
-		
+		// Cache
 		$this->m_aObjectActionGrants = array();
 	}
 
-	// Separate load: this cache is much more time consuming while loading
-	// Thus it is loaded iif required
-	// Could be improved by specifying the profile id
-	public function LoadActionGrantCache()
-	{
-		if (!is_null($this->m_aClassActionGrants)) return;
-
-		$oKPI = new ExecutionKPI();
-
-		$oFilter = DBObjectSearch::FromOQL_AllData("SELECT URP_ActionGrant AS p WHERE p.permission = 'yes'");
-		$aGrants = $oFilter->ToDataArray();
-		foreach($aGrants as $aGrant)
-		{
-			$this->m_aClassActionGrants[$aGrant['profileid']][$aGrant['class']][strtolower($aGrant['action'])] = $aGrant['id'];
-		}
-
-		$oKPI->ComputeAndReport('Load of action grants');
-	}
-
 	public function LoadCache()
 	{
 		if (!is_null($this->m_aProfiles)) return;
@@ -731,14 +484,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
 			$this->m_aProfiles[$oProfile->GetKey()] = $oProfile; 
 		}
 
-		$this->m_aClassStimulusGrants = array();
-		$oStimGrantSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_StimulusGrant"));
-		$this->m_aStimGrants = array();
-		while ($oStimGrant = $oStimGrantSet->Fetch())
-		{
-			$this->m_aClassStimulusGrants[$oStimGrant->Get('profileid')][$oStimGrant->Get('class')][$oStimGrant->Get('stimulus')] = $oStimGrant;
-		}
-
 		$oKPI->ComputeAndReport('Load of user management cache (excepted Action Grants)');
 
 /*
@@ -746,8 +491,6 @@ class UserRightsProfile extends UserRightsAddOnAPI
 		print_r($this->m_aProfiles);
 		print_r($this->m_aUserProfiles);
 		print_r($this->m_aUserOrgs);
-		print_r($this->m_aClassActionGrants);
-		print_r($this->m_aClassStimulusGrants);
 		echo "</pre>\n";
 exit;
 */
@@ -891,29 +634,10 @@ exit;
 	// This verb has been made public to allow the development of an accurate feedback for the current configuration
 	public function GetProfileActionGrant($iProfile, $sClass, $sAction)
 	{
-		$this->LoadActionGrantCache();
-
 		// Note: action is forced lowercase to be more flexible (historical bug)
 		$sAction = strtolower($sAction);
-		if (isset($this->m_aClassActionGrants[$iProfile][$sClass][$sAction]))
-		{
-			return $this->m_aClassActionGrants[$iProfile][$sClass][$sAction];
-		}
-
-		// Recursively look for the grant record in the class hierarchy
-		$sParentClass = MetaModel::GetParentPersistentClass($sClass);
-		if (empty($sParentClass))
-		{
-			$iGrant = null;
-		}
-		else
-		{
-			// Recursively look for the grant record in the class hierarchy
-			$iGrant = $this->GetProfileActionGrant($iProfile, $sParentClass, $sAction);
-		}
 
-		$this->m_aClassActionGrants[$iProfile][$sClass][$sAction] = $iGrant;
-		return $iGrant;
+		return ProfilesConfig::GetProfileActionGrant($iProfile, $sClass, $sAction);
 	}
 
 	protected function GetUserActionGrant($oUser, $sClass, $iActionCode)
@@ -928,39 +652,32 @@ exit;
 
 		$sAction = self::$m_aActionCodes[$iActionCode];
 
-		$iPermission = UR_ALLOWED_NO;
+		$bStatus = null;
 		$aAttributes = array();
 		foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
 		{
-				$iGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
-				if (is_null($iGrant) || !$iGrant)
+			$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
+			if (!is_null($bGrant))
+			{
+				if ($bGrant)
 				{
-					continue; // loop to the next profile
+					if (is_null($bStatus))
+					{
+						$bStatus = true;
+					}
 				}
 				else
 				{
-					$iPermission = UR_ALLOWED_YES;
-
-					// update the list of attributes with those allowed for this profile
-					//
-					$oSearch = DBObjectSearch::FromOQL_AllData("SELECT URP_AttributeGrant WHERE actiongrantid = :actiongrantid");
-					$oSet = new DBObjectSet($oSearch, array(), array('actiongrantid' => $iGrant));
-					$aProfileAttributes = $oSet->GetColumnAsArray('attcode', false);
-					if (count($aProfileAttributes) == 0)
-					{
-						$aAllAttributes = array_keys(MetaModel::ListAttributeDefs($sClass));
-						$aAttributes = array_merge($aAttributes, $aAllAttributes);
-					}
-					else
-					{
-						$aAttributes = array_merge($aAttributes, $aProfileAttributes);
-					}
+					$bStatus = false;
 				}
 			}
+		}
+
+		$iPermission = $bStatus ? UR_ALLOWED_YES : UR_ALLOWED_NO;
 
 		$aRes = array(
 			'permission' => $iPermission,
-			'attributes' => $aAttributes,
+//			'attributes' => $aAttributes,
 		);
 		$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
 		return $aRes;
@@ -1063,16 +780,7 @@ exit;
 	// This verb has been made public to allow the development of an accurate feedback for the current configuration
 	public function GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode)
 	{
-		$this->LoadCache();
-
-		if (isset($this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode]))
-		{
-			return $this->m_aClassStimulusGrants[$iProfile][$sClass][$sStimulusCode];
-		}
-		else
-		{
-			return null;
-		}
+		return ProfilesConfig::GetProfileStimulusGrant($iProfile, $sClass, $sStimulusCode);
 	}
 
 	public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, $oInstanceSet = null)
@@ -1083,16 +791,27 @@ exit;
 
 		// Note: The object set is ignored because it was interesting to optimize for huge data sets
 		//       and acceptable to consider only the root class of the object set
-		$iPermission = UR_ALLOWED_NO;
+		$bStatus = null;
 		foreach($this->GetUserProfiles($iUser) as $iProfile => $oProfile)
 		{
-				$oGrantRecord = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
-				if (!is_null($oGrantRecord))
+			$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
+			if (!is_null($bGrant))
+			{
+				if ($bGrant)
 				{
-					// no need to fetch the record, we've requested the records having permission = 'yes'
-					$iPermission = UR_ALLOWED_YES;
+					if (is_null($bStatus))
+					{
+						$bStatus = true;
+					}
+				}
+				else
+				{
+					$bStatus = false;
 				}
 			}
+		}
+
+		$iPermission = $bStatus ? UR_ALLOWED_YES : UR_ALLOWED_NO;
 		return $iPermission;
 	}
 

+ 1 - 1
application/displayblock.class.inc.php

@@ -1179,7 +1179,7 @@ class MenuBlock extends DisplayBlock
 				$sDefault.= "&default[$sKey]=$sValue";
 			}
 		}
-		$bIsCreationAllowed =  (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES);
+		$bIsCreationAllowed =  (UserRights::IsActionAllowed($sClass, UR_ACTION_CREATE) == UR_ALLOWED_YES);
 		switch($oSet->Count())
 		{
 			case 0:

+ 9 - 0
core/dbobject.class.php

@@ -789,6 +789,15 @@ abstract class DBObject
 	public function GetAttributeFlags($sAttCode, &$aReasons = array(), $sTargetState = '')
 	{
 		$iFlags = 0; // By default (if no life cycle) no flag at all
+
+		if (method_exists(get_class($this), 'GetConstantColumns'))
+		{
+			if (in_array($sAttCode, $this->GetConstantColumns()))
+			{
+				return OPT_ATT_READONLY;
+			}
+		}
+
 		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
 		if (!empty($sStateAttCode))
 		{

+ 20 - 1
core/userrights.class.inc.php

@@ -41,6 +41,8 @@ define('UR_ACTION_BULK_READ', 4); // Export multiple objects
 define('UR_ACTION_BULK_MODIFY', 5); // Create/modify multiple objects
 define('UR_ACTION_BULK_DELETE', 6); // Delete multiple objects
 
+define('UR_ACTION_CREATE', 7); // Instantiate an object
+
 define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions (CSV import, View schema...)
 
 /**
@@ -671,9 +673,20 @@ class UserRights
 
 		if (MetaModel::DBIsReadOnly())
 		{
+			if ($iActionCode == UR_ACTION_CREATE) return false;
 			if ($iActionCode == UR_ACTION_MODIFY) return false;
-			if ($iActionCode == UR_ACTION_DELETE) return false;
 			if ($iActionCode == UR_ACTION_BULK_MODIFY) return false;
+			if ($iActionCode == UR_ACTION_DELETE) return false;
+			if ($iActionCode == UR_ACTION_BULK_DELETE) return false;
+		}
+
+		if (method_exists($sClass, 'GetConstantColumns'))
+		{
+			// As opposed to the read-only DB, modifying an object is allowed
+			// (the constant columns will be marked as read-only)
+			//
+			if ($iActionCode == UR_ACTION_CREATE) return false;
+			if ($iActionCode == UR_ACTION_DELETE) return false;
 			if ($iActionCode == UR_ACTION_BULK_DELETE) return false;
 		}
 
@@ -685,6 +698,12 @@ class UserRights
 			{
 				$oUser = self::$m_oUser;
 			}
+			if ($iActionCode == UR_ACTION_CREATE)
+			{
+				// The addons currently DO NOT handle the case "CREATE"
+				// Therefore it is considered to be equivalent to "MODIFY"
+				$iActionCode = UR_ACTION_MODIFY;
+			}
 			return self::$m_oAddOn->IsActionAllowed($oUser, $sClass, $iActionCode, $oInstanceSet);
 		}
 		elseif(($iActionCode == UR_ACTION_READ) && MetaModel::HasCategory($sClass, 'view_in_gui'))

+ 576 - 0
datamodel/itop-profiles-itil/datamodel.itop-profiles-itil.xml

@@ -0,0 +1,576 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <classes/>
+  <user_rights>
+    <groups>
+      <group id="General" _delta="define">
+        <classes>
+          <class id="Organization"/>
+          <class id="Location"/>
+          <class id="Contact"/>
+          <class id="Person"/>
+          <class id="Team"/>
+          <class id="lnkTeamToContact"/>
+        </classes>
+      </group>
+      <group id="Documentation" _delta="define">
+        <classes>
+          <class id="Document"/>
+          <class id="WebDoc"/>
+          <class id="Note"/>
+          <class id="FileDoc"/>
+        </classes>
+      </group>
+      <group id="Configuration" _delta="define">
+        <classes>
+          <class id="Licence"/>
+          <class id="Subnet"/>
+          <class id="Patch"/>
+          <class id="Software"/>
+          <class id="Application"/>
+          <class id="DBServer"/>
+          <class id="lnkPatchToCI"/>
+          <class id="FunctionalCI"/>
+          <class id="SoftwareInstance"/>
+          <class id="DBServerInstance"/>
+          <class id="ApplicationInstance"/>
+          <class id="DatabaseInstance"/>
+          <class id="ApplicationSolution"/>
+          <class id="BusinessProcess"/>
+          <class id="ConnectableCI"/>
+          <class id="NetworkInterface"/>
+          <class id="Device"/>
+          <class id="PC"/>
+          <class id="MobileCI"/>
+          <class id="MobilePhone"/>
+          <class id="InfrastructureCI"/>
+          <class id="NetworkDevice"/>
+          <class id="Server"/>
+          <class id="Printer"/>
+          <class id="Group"/>
+          <class id="lnkGroupToCI"/>
+          <class id="lnkCIToDoc"/>
+          <class id="lnkCIToContact"/>
+          <class id="lnkSolutionToCI"/>
+          <class id="lnkProcessToSolution"/>
+        </classes>
+      </group>
+      <group id="Incident" _delta="define">
+        <classes>
+          <class id="lnkTicketToDoc"/>
+          <class id="lnkTicketToContact"/>
+          <class id="lnkTicketToCI"/>
+          <class id="Incident"/>
+          <class id="lnkTicketToIncident"/>
+        </classes>
+      </group>
+      <group id="Problem" _delta="define">
+        <classes>
+          <class id="lnkTicketToDoc"/>
+          <class id="lnkTicketToContact"/>
+          <class id="lnkTicketToCI"/>
+          <class id="Problem"/>
+        </classes>
+      </group>
+      <group id="Change" _delta="define">
+        <classes>
+          <class id="lnkTicketToDoc"/>
+          <class id="lnkTicketToContact"/>
+          <class id="lnkTicketToCI"/>
+          <class id="Change"/>
+          <class id="RoutineChange"/>
+          <class id="ApprovedChange"/>
+          <class id="NormalChange"/>
+          <class id="EmergencyChange"/>
+        </classes>
+      </group>
+      <group id="Service" _delta="define">
+        <classes>
+          <class id="Contract"/>
+          <class id="ProviderContract"/>
+          <class id="CustomerContract"/>
+          <class id="lnkCustomerContractToProviderContract"/>
+          <class id="lnkContractToSLA"/>
+          <class id="lnkContractToDoc"/>
+          <class id="lnkContractToContact"/>
+          <class id="lnkContractToCI"/>
+          <class id="Service"/>
+          <class id="ServiceSubcategory"/>
+          <class id="SLA"/>
+          <class id="SLT"/>
+          <class id="lnkSLTToSLA"/>
+          <class id="lnkServiceToDoc"/>
+          <class id="lnkServiceToContact"/>
+          <class id="lnkServiceToCI"/>
+        </classes>
+      </group>
+      <group id="Call" _delta="define">
+        <classes>
+          <class id="lnkTicketToDoc"/>
+          <class id="lnkTicketToContact"/>
+          <class id="lnkTicketToCI"/>
+          <class id="lnkTicketToIncident"/>
+          <class id="UserRequest"/>
+        </classes>
+      </group>
+      <group id="KnownError" _delta="define">
+        <classes>
+          <class id="KnownError"/>
+          <class id="lnkInfraError"/>
+          <class id="lnkDocumentError"/>
+        </classes>
+      </group>
+      <group id="LnkTickets" _delta="define">
+        <classes>
+          <class id="lnkTicketToDoc"/>
+          <class id="lnkTicketToContact"/>
+          <class id="lnkTicketToCI"/>
+        </classes>
+      </group>
+      <group id="LnkIncidents" _delta="define">
+        <classes>
+          <class id="lnkTicketToIncident"/>
+        </classes>
+      </group>
+      <group id="LnkServices" _delta="define">
+        <classes>
+          <class id="lnkCustomerContractToProviderContract"/>
+          <class id="lnkContractToSLA"/>
+          <class id="lnkContractToDoc"/>
+          <class id="lnkContractToContact"/>
+          <class id="lnkContractToCI"/>
+          <class id="lnkSLTToSLA"/>
+          <class id="lnkServiceToDoc"/>
+          <class id="lnkServiceToContact"/>
+          <class id="lnkServiceToCI"/>
+        </classes>
+      </group>
+      <group id="LnkKnownErrors" _delta="define">
+        <classes>
+          <class id="lnkInfraError"/>
+          <class id="lnkDocumentError"/>
+        </classes>
+      </group>
+      <group id="Portal user - write" _delta="define">
+        <classes>
+          <class id="FileDoc"/>
+          <class id="lnkTicketToDoc"/>
+          <class id="UserRequest"/>
+        </classes>
+      </group>
+      <group id="Portal user - delete" _delta="define">
+        <classes>
+          <class id="lnkTicketToDoc"/>
+        </classes>
+      </group>
+      <group id="class:UserRequest" _delta="define">
+        <classes>
+          <class id="UserRequest"/>
+        </classes>
+      </group>
+      <group id="class:Incident" _delta="define">
+        <classes>
+          <class id="Incident"/>
+        </classes>
+      </group>
+      <group id="class:Problem" _delta="define">
+        <classes>
+          <class id="Problem"/>
+        </classes>
+      </group>
+      <group id="class:NormalChange" _delta="define">
+        <classes>
+          <class id="NormalChange"/>
+        </classes>
+      </group>
+      <group id="class:EmergencyChange" _delta="define">
+        <classes>
+          <class id="EmergencyChange"/>
+        </classes>
+      </group>
+      <group id="class:RoutineChange" _delta="define">
+        <classes>
+          <class id="RoutineChange"/>
+        </classes>
+      </group>
+    </groups>
+    <profiles>
+      <profile id="3" _delta="define">
+        <name>Configuration Manager</name>
+        <description>Person in charge of the documentation of the managed CIs</description>
+        <groups>
+          <group id="General">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="Documentation">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="Configuration">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="4" _delta="define">
+        <name>Service Desk Agent</name>
+        <description>Person in charge of creating incident reports</description>
+        <groups>
+          <group id="Incident">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="Call">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="LnkTickets">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="LnkIncidents">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="class:Incident">
+            <actions>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:UserRequest">
+            <actions>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="5" _delta="define">
+        <name>Support Agent</name>
+        <description>Person analyzing and solving the current incidents</description>
+        <groups>
+          <group id="Incident">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="Call">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="LnkTickets">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="LnkIncidents">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="class:Incident">
+            <actions>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+              <action id="ev_reassign" xsi:type="stimulus">allow</action>
+              <action id="ev_resolve" xsi:type="stimulus">allow</action>
+              <action id="ev_close" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:UserRequest">
+            <actions>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+              <action id="ev_reassign" xsi:type="stimulus">allow</action>
+              <action id="ev_resolve" xsi:type="stimulus">allow</action>
+              <action id="ev_close" xsi:type="stimulus">allow</action>
+              <action id="ev_freeze" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="6" _delta="define">
+        <name>Problem Manager</name>
+        <description>Person analyzing and solving the current problems</description>
+        <groups>
+          <group id="Problem">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="KnownError">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="LnkTickets">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="LnkKnownErrors">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="class:Problem">
+            <actions>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+              <action id="ev_reassign" xsi:type="stimulus">allow</action>
+              <action id="ev_resolve" xsi:type="stimulus">allow</action>
+              <action id="ev_close" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="7" _delta="define">
+        <name>Change Implementor</name>
+        <description>Person executing the changes</description>
+        <groups>
+          <group id="Change">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="LnkTickets">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="class:NormalChange">
+            <actions>
+              <action id="ev_plan" xsi:type="stimulus">allow</action>
+              <action id="ev_replan" xsi:type="stimulus">allow</action>
+              <action id="ev_implement" xsi:type="stimulus">allow</action>
+              <action id="ev_monitor" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:EmergencyChange">
+            <actions>
+              <action id="ev_plan" xsi:type="stimulus">allow</action>
+              <action id="ev_replan" xsi:type="stimulus">allow</action>
+              <action id="ev_implement" xsi:type="stimulus">allow</action>
+              <action id="ev_monitor" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:RoutineChange">
+            <actions>
+              <action id="ev_plan" xsi:type="stimulus">allow</action>
+              <action id="ev_replan" xsi:type="stimulus">allow</action>
+              <action id="ev_implement" xsi:type="stimulus">allow</action>
+              <action id="ev_monitor" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="8" _delta="define">
+        <name>Change Supervisor</name>
+        <description>Person responsible for the overall change execution</description>
+        <groups>
+          <group id="Change">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="LnkTickets">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="class:NormalChange">
+            <actions>
+              <action id="ev_validate" xsi:type="stimulus">allow</action>
+              <action id="ev_reject" xsi:type="stimulus">allow</action>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+              <action id="ev_reopen" xsi:type="stimulus">allow</action>
+              <action id="ev_finish" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:EmergencyChange">
+            <actions>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+              <action id="ev_reopen" xsi:type="stimulus">allow</action>
+              <action id="ev_finish" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:RoutineChange">
+            <actions>
+              <action id="ev_assign" xsi:type="stimulus">allow</action>
+              <action id="ev_reopen" xsi:type="stimulus">allow</action>
+              <action id="ev_finish" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="9" _delta="define">
+        <name>Change Approver</name>
+        <description>Person who could be impacted by some changes</description>
+        <groups>
+          <group id="Change">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="LnkTickets">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="class:NormalChange">
+            <actions>
+              <action id="ev_approve" xsi:type="stimulus">allow</action>
+              <action id="ev_notapprove" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:EmergencyChange">
+            <actions>
+              <action id="ev_approve" xsi:type="stimulus">allow</action>
+              <action id="ev_notapprove" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="class:RoutineChange">
+            <actions/>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="10" _delta="define">
+        <name>Service Manager</name>
+        <description>Person responsible for the service delivered to the [internal] customer</description>
+        <groups>
+          <group id="Service">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="LnkServices">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="11" _delta="define">
+        <name>Document author</name>
+        <description>Any person who could contribute to documentation</description>
+        <groups>
+          <group id="Documentation">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="LnkTickets">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+      <profile id="2" _delta="define">
+        <name>Portal user</name>
+        <description>Has the rights to access to the user portal. People having this profile will not be allowed to access the standard application, they will be automatically redirected to the user portal.</description>
+        <groups>
+          <group id="Portal user - write">
+            <actions>
+              <action xsi:type="write">allow</action>
+              <action xsi:type="bulk write">allow</action>
+            </actions>
+          </group>
+          <group id="Portal user - delete">
+            <actions>
+              <action xsi:type="delete">allow</action>
+            </actions>
+          </group>
+          <group id="class:UserRequest">
+            <actions>
+              <action id="ev_close" xsi:type="stimulus">allow</action>
+            </actions>
+          </group>
+          <group id="*">
+            <actions>
+              <action xsi:type="read">allow</action>
+              <action xsi:type="bulk read">allow</action>
+            </actions>
+          </group>
+        </groups>
+      </profile>
+    </profiles>
+  </user_rights>
+</itop_design>

+ 1 - 319
datamodel/itop-profiles-itil/module.itop-profiles-itil.php

@@ -29,12 +29,11 @@ SetupWebPage::AddModule(
 		),
 		'mandatory' => true,
 		'visible' => false,
-		'installer' => 'CreateITILProfilesInstaller',
 
 		// Components
 		//
 		'datamodel' => array(
-			//'model.itop-profiles-itil.php',
+			'model.itop-profiles-itil.php',
 		),
 		'webservice' => array(
 			//'webservices.itop-profiles-itil.php',
@@ -59,321 +58,4 @@ SetupWebPage::AddModule(
 	)
 );
 
-
-// Module installation handler
-//
-class CreateITILProfilesInstaller extends ModuleInstallerAPI
-{
-	public static function BeforeWritingConfig(Config $oConfiguration)
-	{
-		//$oConfiguration->SetModuleSetting('user-rigths-profile', 'myoption', 'myvalue');
-		return $oConfiguration;
-	}
-
-	public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
-	{
-		self::ComputeITILProfiles();
-		//self::ComputeBasicProfiles();
-		$bFirstInstall = empty($sPreviousVersion);
-		self::DoCreateProfiles($bFirstInstall);
-		UserRights::FlushPrivileges(true /* reset admin cache */);
-	}
-
-	// Note: It is possible to specify the same class in several modules
-	//
-	protected static $m_aModules = array();
-	protected static $m_aProfiles = array();
-	
-	protected static function DoSetupProfile($sName, $aProfileData)
-	{
-		$sDescription = $aProfileData['description'];
-		if (strlen(trim($aProfileData['write_modules'])) == 0)
-		{
-			$aWriteModules = array(); 
-		}
-		else
-		{
-			$aWriteModules = explode(',', trim($aProfileData['write_modules']));
-		}
-		if (strlen(trim($aProfileData['delete_modules'])) == 0)
-		{
-			$aDeleteModules = array(); 
-		}
-		else
-		{
-			$aDeleteModules = explode(',', trim($aProfileData['delete_modules']));
-		}
-		$aStimuli = $aProfileData['stimuli'];
-		
-		$iProfile = URP_Profiles::DoCreateProfile($sName, $sDescription);
-	
-		// Warning: BulkInsert is working because we will load one single class
-		//          having one single table !
-		//          the benefit is: 10 queries (1 per profile) instead of 1500
-		//          which divides the overall user rights setup process by 5
-		DBObject::BulkInsertStart();
-
-		// Grant read rights for everything
-		//
-		foreach (MetaModel::GetClasses('bizmodel') as $sClass)
-		{
-			URP_Profiles::DoCreateActionGrant($iProfile, UR_ACTION_READ, $sClass);
-			URP_Profiles::DoCreateActionGrant($iProfile, UR_ACTION_BULK_READ, $sClass);
-		}
-	
-		// Grant write for given modules
-		// Start by compiling the information, because some modules may overlap
-		$aWriteableClasses = array();
-		foreach ($aWriteModules as $sModule)
-		{
-			//$oPage->p('Granting write access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes');
-			foreach (self::$m_aModules[$sModule] as $sClass)
-			{
-				$aWriteableClasses[$sClass] = true;
-			}
-		}
-		foreach ($aWriteableClasses as $sClass => $foo)
-		{
-			if (!MetaModel::IsValidClass($sClass))
-			{
-				throw new CoreException("Invalid class name '$sClass'");
-			}
-			URP_Profiles::DoCreateActionGrant($iProfile, UR_ACTION_MODIFY, $sClass);
-			URP_Profiles::DoCreateActionGrant($iProfile, UR_ACTION_BULK_MODIFY, $sClass);
-		}
-		
-		// Grant delete for given modules
-		// Start by compiling the information, because some modules may overlap
-		$aDeletableClasses = array();
-		foreach ($aDeleteModules as $sModule)
-		{
-			//$oPage->p('Granting delete access for the module"'.$sModule.'" - '.count(self::$m_aModules[$sModule]).' classes');
-			foreach (self::$m_aModules[$sModule] as $sClass)
-			{
-				$aDeletableClasses[$sClass] = true;
-			}
-		}
-		foreach ($aDeletableClasses as $sClass => $foo)
-		{
-			if (!MetaModel::IsValidClass($sClass))
-			{
-				throw new CoreException("Invalid class name '$sClass'");
-			}
-			URP_Profiles::DoCreateActionGrant($iProfile, UR_ACTION_DELETE, $sClass);
-			// By default, do not allow bulk deletion operations for standard users
-			// URP_Profiles::DoCreateActionGrant($iProfile, UR_ACTION_BULK_DELETE, $sClass);
-		}
-		
-		// Grant stimuli for given classes
-		foreach ($aStimuli as $sClass => $sAllowedStimuli)
-		{
-			if (!MetaModel::IsValidClass($sClass))
-			{
-				// Could be a class defined in a module that wasn't installed
-				continue;
-				//throw new CoreException("Invalid class name '$sClass'");
-			}
-
-			if ($sAllowedStimuli == 'any')
-			{
-				$aAllowedStimuli = array_keys(MetaModel::EnumStimuli($sClass));
-			}
-			elseif ($sAllowedStimuli == 'none')
-			{
-				$aAllowedStimuli = array();
-			}
-			else
-			{
-				$aAllowedStimuli = explode(',', $sAllowedStimuli);
-			}
-			foreach ($aAllowedStimuli as $sStimulusCode)
-			{
-				URP_Profiles::DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass);
-			}
-		}
-		// Again: this is working only because action/stimulus grant are classes made of a single table!
-		DBObject::BulkInsertFlush();
-	}
-	
-	/*
-	* Create the built-in User Portal profile with its reserved name
-	*/	
-	public static function DoCreateUserPortalProfile()
-	{
-		// Do not attempt to create this profile if the module 'User Request Management' is not installed
-		// Note: ideally, the creation of this profile should be moved to the 'User Request Management' module
-		if (!MetaModel::IsValidClass('UserRequest')) return;
-
-		$iNewId =  URP_Profiles::DoCreateProfile(PORTAL_PROFILE_NAME, 'Has the rights to access to the user portal. People having this profile will not be allowed to access the standard application, they will be automatically redirected to the user portal.', true /* reserved name */);
-		
-		// Grant read rights for everything
-		//
-		foreach (MetaModel::GetClasses('bizmodel') as $sClass)
-		{
-			URP_Profiles::DoCreateActionGrant($iNewId, UR_ACTION_READ, $sClass);
-			URP_Profiles::DoCreateActionGrant($iNewId, UR_ACTION_BULK_READ, $sClass);
-		}
-		// Can create UserRequests and attach Documents to it
-		self::SafeCreateActionGrant($iNewId, UR_ACTION_MODIFY, 'UserRequest');
-		self::SafeCreateActionGrant($iNewId, UR_ACTION_MODIFY, 'lnkTicketToDoc');
-		self::SafeCreateActionGrant($iNewId, UR_ACTION_DELETE, 'lnkTicketToDoc');
-		self::SafeCreateActionGrant($iNewId, UR_ACTION_MODIFY, 'FileDoc');
-		// Can close user requests
-		self::SafeCreateStimulusGrant($iNewId, 'ev_close', 'UserRequest');
-	}
-	protected static function SafeCreateActionGrant($iProfile, $iAction, $sClass, $bPermission = true)
-	{
-		if (MetaModel::IsValidClass($sClass)) URP_Profiles::DoCreateActionGrant($iProfile, $iAction, $sClass, $bPermission);
-	}
-
-	protected static function SafeCreateStimulusGrant($iProfile, $sStimulusCode, $sClass)
-	{
-		if (MetaModel::IsValidClass($sClass)) URP_Profiles::DoCreateStimulusGrant($iProfile, $sStimulusCode, $sClass);
-	}
-
-	public static function DoCreateProfiles($bFirstInstall = true)
-	{
-		URP_Profiles::DoCreateAdminProfile(); // Will be created only if it does not exist
-		self::DoCreateUserPortalProfile(); // Will be created only if it does not exist and updated otherwise
-
-		foreach(self::$m_aProfiles as $sName => $aProfileData)
-		{
-			self::DoSetupProfile($sName, $aProfileData);
-		}
-	}
-
-	public static function ComputeBasicProfiles()
-	{
-		// In this profiling scheme, one single module represents all the classes
-		//
-		self::$m_aModules = array(
-			'UserData' => MetaModel::GetClasses('bizmodel'),
-		);
-
-		self::$m_aProfiles = array(
-			'Reader' => array(
-				'description' => 'Person having a ready-only access to the data',
-				'write_modules' => '',
-				'delete_modules' => '',
-				'stimuli' => array(
-				),
-			),
-			'Writer' => array(
-				'description' => 'Contributor to the contents (read + write access)',
-				'write_modules' => 'UserData',
-				'delete_modules' => 'UserData',
-				'stimuli' => array(
-					// any class => 'any'
-				),
-			),
-		);
-	}
-
-	public static function ComputeITILProfiles()
-	{
-		// In this profiling scheme, modules are based on ITIL recommendations
-		//
-		self::$m_aModules = array(
-			'General' => MetaModel::GetClasses('structure'),
-			'Documentation' => MetaModel::GetClasses('documentation'),
-			'Configuration' => MetaModel::GetClasses('configmgmt'),
-			'Incident' => MetaModel::GetClasses('incidentmgmt'),
-			'Problem' => MetaModel::GetClasses('problemmgmt'),
-			'Change' => MetaModel::GetClasses('changemgmt'),
-			'Service' => MetaModel::GetClasses('servicemgmt'),
-			'Call' => MetaModel::GetClasses('requestmgmt'),
-			'KnownError' => MetaModel::GetClasses('knownerrormgmt'),
-			'LnkTickets' => MetaModel::GetClasses('lnkticket'),
-			'LnkIncidents' => MetaModel::GetClasses('lnkincident'),
-			'LnkServices' => MetaModel::GetClasses('lnkservice'),
-			'LnkKnownErrors' => MetaModel::GetClasses('lnkknownerror'),
-		);
-		
-		self::$m_aProfiles = array(
-			'Configuration Manager' => array(
-				'description' => 'Person in charge of the documentation of the managed CIs',
-				'write_modules' => 'General,Documentation,Configuration',
-				'delete_modules' => 'General,Documentation,Configuration',
-				'stimuli' => array(
-					//'Server' => 'none',
-					//'Contract' => 'none',
-					//'IncidentTicket' => 'none',
-					//'ChangeTicket' => 'any',
-				),
-			),
-			'Service Desk Agent' => array(
-				'description' => 'Person in charge of creating incident reports',
-				'write_modules' => 'Incident,Call',
-				'delete_modules' => 'LnkTickets,LnkIncidents',
-				'stimuli' => array(
-					'Incident' => 'ev_assign',
-					'UserRequest' => 'ev_assign',
-				),
-			),
-			'Support Agent' => array(
-				'description' => 'Person analyzing and solving the current incidents',
-				'write_modules' => 'Incident,Call',
-				'delete_modules' => 'LnkTickets,LnkIncidents',
-				'stimuli' => array(
-					'Incident' => 'ev_assign,ev_reassign,ev_resolve,ev_close',
-					'UserRequest' => 'ev_assign,ev_reassign,ev_resolve,ev_close,ev_freeze',
-				),
-			),
-			'Problem Manager' => array(
-				'description' => 'Person analyzing and solving the current problems',
-				'write_modules' => 'Problem,KnownError',
-				'delete_modules' => 'LnkTickets,LnkKnownErrors',
-				'stimuli' => array(
-					'Problem' => 'ev_assign,ev_reassign,ev_resolve,ev_close',
-				),
-			),
-
-			'Change Implementor' => array(
-				'description' => 'Person executing the changes',
-				'write_modules' => 'Change',
-				'delete_modules' => 'LnkTickets',
-				'stimuli' => array(
-					'NormalChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor',
-					'EmergencyChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor',
-					'RoutineChange' => 'ev_plan,ev_replan,ev_implement,ev_monitor',
-				),
-			),
-			'Change Supervisor' => array(
-				'description' => 'Person responsible for the overall change execution',
-				'write_modules' => 'Change',
-				'delete_modules' => 'LnkTickets',
-				'stimuli' => array(
-					'NormalChange' => 'ev_validate,ev_reject,ev_assign,ev_reopen,ev_finish',
-					'EmergencyChange' => 'ev_assign,ev_reopen,ev_finish',
-					'RoutineChange' => 'ev_assign,ev_reopen,ev_finish',
-				),
-			),
-			'Change Approver' => array(
-				'description' => 'Person who could be impacted by some changes',
-				'write_modules' => 'Change',
-				'delete_modules' => 'LnkTickets',
-				'stimuli' => array(
-					'NormalChange' => 'ev_approve,ev_notapprove',
-					'EmergencyChange' => 'ev_approve,ev_notapprove',
-					'RoutineChange' => 'none',
-				),
-			),
-			'Service Manager' => array(
-				'description' => 'Person responsible for the service delivered to the [internal] customer',
-				'write_modules' => 'Service',
-				'delete_modules' => 'LnkServices',
-				'stimuli' => array(
-				),
-			),
-			'Document author' => array(
-				'description' => 'Any person who could contribute to documentation',
-				'write_modules' => 'Documentation',
-				'delete_modules' => 'Documentation,LnkTickets',
-				'stimuli' => array(
-				),
-			),
-		);
-	}
-}
-
 ?>

+ 53 - 0
setup/ajax.dataloader.php

@@ -280,6 +280,59 @@ try
 			}
 		}
 
+		// Constant classes (e.g. User profiles)
+		//
+		foreach (MetaModel::GetClasses() as $sClass)
+		{
+			if (method_exists($sClass, 'GetConstantColumns'))
+			{
+				// Temporary... until this get really encapsulated as the default and transparent behavior
+				$oMyChange = MetaModel::NewObject("CMDBChange");
+				$oMyChange->Set("date", time());
+				$sUserString = CMDBChange::GetCurrentUserName();
+				$oMyChange->Set("userinfo", $sUserString);
+				$iChangeId = $oMyChange->DBInsert();
+
+				// Create/Delete/Update objects of this class,
+				// according to the given constant values
+				//
+				$aAttList = call_user_func(array($sClass, 'GetConstantColumns'));
+				$aRefValues = call_user_func(array($sClass, 'GetConstantValues'));
+				$aDBIds = array();
+				$oAll = new DBObjectSet(new DBObjectSearch($sClass));
+				while ($oObj = $oAll->Fetch())
+				{
+					if (array_key_exists($oObj->GetKey(), $aRefValues))
+					{
+						$aObjValues = $aRefValues[$oObj->GetKey()];
+						foreach ($aAttList as $sAttCode)
+						{
+							$oObj->Set($sAttCode, $aObjValues[$sAttCode]);
+						}
+						$oObj->DBUpdateTracked($oMyChange);
+						$aDBIds[$oObj->GetKey()] = true;
+					}
+					else
+					{
+						$oObj->DBDeleteTracked($oMyChange);
+					}
+				}
+				foreach ($aRefValues as $iRefId => $aObjValues)
+				{
+					if (!array_key_exists($iRefId, $aDBIds))
+					{
+						$oNewObj = MetaModel::NewObject($sClass);
+						$oNewObj->SetKey($iRefId);
+						foreach ($aAttList as $sAttCode)
+						{
+							$oNewObj->Set($sAttCode, $aObjValues[$sAttCode]);
+						}
+						$oNewObj->DBInsertTracked($oMyChange);
+					}
+				}
+			}
+		}
+
 		if (!$oProductionEnv->RecordInstallation($oConfig, $aSelectedModules, $sModuleDir))
 		{
 			throw(new Exception("Failed to record the installation information"));

+ 190 - 1
setup/compiler.class.inc.php

@@ -52,6 +52,10 @@ class MFCompiler
 
 	public function Compile($sTargetDir, $oP = null)
 	{
+		$aAllClasses = array(); // flat list of classes
+
+		// Determine the target modules for the MENUS
+		//
 		$aMenuNodes = array();
 		$aMenusByModule = array();
 		foreach ($this->oFactory->ListActiveChildNodes('menus', 'menu') as $oMenuNode)
@@ -63,6 +67,17 @@ class MFCompiler
 			$aMenusByModule[$sModuleMenu][] = $sMenuId;
 		}
 
+		// Determine the target module (exactly one!) for USER RIGHTS
+		//
+		$oUserRightsNode = $this->oFactory->GetNodes('user_rights')->item(0);
+		if (!$oUserRightsNode)
+		{
+			throw new Exception("Missing configuration for user rights");
+		}
+		$sUserRightsModule = $oUserRightsNode->getAttribute('_created_in');
+
+		// List root classes
+		//
 		$this->aRootClasses = array();
 		foreach ($this->oFactory->ListRootClasses() as $oClass)
 		{
@@ -70,6 +85,8 @@ class MFCompiler
 			$this->aRootClasses[$oClass->getAttribute('id')] = $oClass;
 		}
 
+		// Compile, module by module
+		//
 		$aModules = $this->oFactory->GetLoadedModules();
 		foreach($aModules as $foo => $oModule)
 		{
@@ -94,13 +111,14 @@ class MFCompiler
 			{
 				foreach($oClasses as $oClass)
 				{
+					$sClass = $oClass->getAttribute("id");
+					$aAllClasses[] = $sClass;
 					try
 					{
 						$sCompiledCode .= $this->CompileClass($oClass, $sRelativeDir, $oP);
 					}
 					catch (ssDOMFormatException $e)
 					{
-						$sClass = $oClass->getAttribute("id");
 						throw new Exception("Failed to process class '$sClass', from '$sModuleRootDir': ".$e->getMessage());
 					}
 				}
@@ -164,6 +182,13 @@ EOF;
 				}
 			}
 
+			// User rights
+			//
+			if ($sModuleName == $sUserRightsModule)
+			{
+				$sCompiledCode .= $this->CompileUserRights($oUserRightsNode);
+			}
+
 			// Create (overwrite if existing) the compiled file
 			//
 			if (strlen($sCompiledCode) > 0)
@@ -968,9 +993,173 @@ EOF;
 		}
 
 		return $sPHP;
+	} // function CompileMenu
+
+	protected function CompileUserRights($oUserRightsNode)
+	{
+		static $aActionsInShort = array(
+			'read' => 'r',
+			'bulk read' => 'br',
+			'write' => 'w',
+			'bulk write' => 'bw',
+			'delete' => 'd',
+			'bulk delete' => 'bd',
+		);
+
+		// Groups
+		//
+		$aGroupClasses = array();
+		$oGroups = $oUserRightsNode->GetUniqueElement('groups');
+		foreach($oGroups->getElementsByTagName('group') as $oGroup)
+		{
+			$sGroupId = $oGroup->getAttribute("id");
+
+			$aClasses = array();
+			$oClasses = $oGroup->GetUniqueElement('classes');
+			foreach($oClasses->getElementsByTagName('class') as $oClass)
+			{
+				
+				$sClass = $oClass->getAttribute("id");
+				$aClasses[] = $sClass;
+
+				//$bSubclasses = $this->GetPropBoolean($oClass, 'subclasses', true);
+				//if ($bSubclasses)...
+			}
+
+			$aGroupClasses[$sGroupId] = $aClasses;
+		}
+
+		// Profiles and grants
+		//
+		$aProfiles = array();
+		// Hardcode the administrator profile
+		$aProfiles[1] = array(
+			'name' => 'Administrator',
+			'description' => 'Has the rights on everything (bypassing any control)'
+		); 
+
+		$aGrants = array();
+		$oProfiles = $oUserRightsNode->GetUniqueElement('profiles');
+		foreach($oProfiles->getElementsByTagName('profile') as $oProfile)
+		{
+			$iProfile = $oProfile->getAttribute("id");
+			$sName = $oProfile->GetChildText('name');
+			$sDescription = $oProfile->GetChildText('description');
+
+			$oGroups = $oProfile->GetUniqueElement('groups');
+			foreach($oGroups->getElementsByTagName('group') as $oGroup)
+			{
+				$sGroupId = $oGroup->getAttribute("id");
+
+				$aActions = array();
+				$oActions = $oGroup->GetUniqueElement('actions');
+				foreach($oActions->getElementsByTagName('action') as $oAction)
+				{
+					$sAction = $oAction->getAttribute("id");
+					$sType = $oAction->getAttribute("xsi:type");
+					$sGrant = $oAction->GetText();
+					$bGrant = ($sGrant == 'allow');
+					
+					if ($sGroupId == '*')
+					{
+						$aGrantClasses = array('*');
+					}
+					else
+					{
+						$aGrantClasses = $aGroupClasses[$sGroupId];
+					}
+					foreach ($aGrantClasses as $sClass)
+					{
+						if ($sType == 'stimulus')
+						{
+							$sGrantKey = $iProfile.'_'.$sClass.'_s_'.$sAction;
+						}
+						else
+						{
+							$sAction = $aActionsInShort[$sType];
+							$sGrantKey = $iProfile.'_'.$sClass.'_'.$sAction;
+						}
+						if (isset($aGrants[$sGrantKey]))
+						{
+							if (!$bGrant)
+							{
+								$aGrants[$sGrantKey] = false;
+							}
+						}
+						else
+						{
+							$aGrants[$sGrantKey] = $bGrant;
+						}
+					}
+				}
+			}
+
+			$aProfiles[$iProfile] = array(
+				'name' => $sName,
+				'description' => $sDescription
+			);
+		}
+
+		$sProfiles = var_export($aProfiles, true);
+		$sGrants = var_export($aGrants, true);
+
+		$sPHP =
+<<<EOF
+
+//
+// List of constant profiles
+// - used by the class URP_Profiles at setup (create/update/delete records)
+// - used by the addon UserRightsProfile to determine user rights
+//
+class ProfilesConfig
+{
+	protected static \$aPROFILES = $sProfiles;
+
+	protected static \$aGRANTS = $sGrants;
+
+	public static function GetProfileActionGrant(\$iProfileId, \$sClass, \$sAction)
+	{
+		\$sGrantKey = \$iProfileId.'_'.\$sClass.'_'.\$sAction;
+		if (isset(self::\$aGRANTS[\$sGrantKey]))
+		{
+			return self::\$aGRANTS[\$sGrantKey];
+		}
+		\$sGrantKey = \$iProfileId.'_*_'.\$sAction;
+		if (isset(self::\$aGRANTS[\$sGrantKey]))
+		{
+			return self::\$aGRANTS[\$sGrantKey];
+		}
+		return null;
+	}	
+
+	public static function GetProfileStimulusGrant(\$iProfileId, \$sClass, \$sStimulus)
+	{
+		\$sGrantKey = \$iProfileId.'_'.\$sClass.'_s_'.\$sStimulus;
+		if (isset(self::\$aGRANTS[\$sGrantKey]))
+		{
+			return self::\$aGRANTS[\$sGrantKey];
+		}
+		\$sGrantKey = \$iProfileId.'_*_s_'.\$sStimulus;
+		if (isset(self::\$aGRANTS[\$sGrantKey]))
+		{
+			return self::\$aGRANTS[\$sGrantKey];
+		}
+		return null;
+	}
+
+	// returns an array of id => array of column => php value(so-called "real value")
+	public static function GetProfilesValues()
+	{
+		return self::\$aPROFILES;
 	}
 }
 
+EOF;
+	return $sPHP;
+	} // function CompileUserRights
+
+}
+
 
 
 ?>

+ 8 - 0
setup/modelfactory.class.inc.php

@@ -314,6 +314,14 @@ class ModelFactory
 					$oNode->SetAttribute('_created_in', $sModuleName);
 				}
 			}
+			$oUserRightsNode = $oXPath->query('/itop_design/user_rights')->item(0);
+			if ($oUserRightsNode)
+			{
+				if ($oUserRightsNode->getAttribute('_created_in') == '')
+				{
+					$oUserRightsNode->SetAttribute('_created_in', $sModuleName);
+				}
+			}
 
 			$oDeltaRoot = $oDocument->childNodes->item(0);
 			$this->LoadDelta($oDocument, $oDeltaRoot, $this->oDOMDocument);

+ 1 - 0
setup/modulediscovery.class.inc.php

@@ -64,6 +64,7 @@ class ModuleDiscovery
 		}
 
 		$aArgs['root_dir'] = dirname($sFilePath);
+		$aArgs['module_file'] = $sFilePath;
 
 		self::$m_aModules[$sId] = $aArgs;