Browse Source

Reintegrated from branch 1.2: capability to develop a module to share objects between organizations (beta)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1859 a333f486-631f-4898-b8df-5754b55c2be0
romainq 13 năm trước cách đây
mục cha
commit
77935669d9

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

@@ -274,7 +274,7 @@ class UserRightsMatrix extends UserRightsAddOnAPI
 		return true;
 	}
 
-	public function GetSelectFilter($oUser, $sClass)
+	public function GetSelectFilter($oUser, $sClass, $aSettings = array())
 	{
 		$oNullFilter  = new DBObjectSearch($sClass);
 		return $oNullFilter;

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

@@ -47,7 +47,7 @@ class UserRightsNull extends UserRightsAddOnAPI
 		return true;
 	}
 
-	public function GetSelectFilter($oUser, $sClass)
+	public function GetSelectFilter($oUser, $sClass, $aSettings = array())
 	{
 		$oNullFilter  = new DBObjectSearch($sClass);
 		return $oNullFilter;

+ 191 - 67
addons/userrights/userrightsprofile.class.inc.php

@@ -659,6 +659,11 @@ class UserRightsProfile extends UserRightsAddOnAPI
 
 		$oKPI = new ExecutionKPI();
 
+		if (self::HasSharing())
+		{
+			SharedObject::InitSharedClassProperties();
+		}
+
 		$oProfileSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_Profiles"));
 		$this->m_aProfiles = array(); 
 		while ($oProfile = $oProfileSet->Fetch())
@@ -683,11 +688,27 @@ class UserRightsProfile extends UserRightsAddOnAPI
 			}
 		}
 
-		$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserOrg"));
 		$this->m_aUserOrgs = array();
-		while ($oUserOrg = $oUserOrgSet->Fetch())
+
+		$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
+		if ($sHierarchicalKeyCode !== false)
 		{
-			$this->m_aUserOrgs[$oUserOrg->Get('userid')][] = $oUserOrg->Get('allowed_org_id');
+			$sUserOrgQuery = 'SELECT UserOrg, Org FROM Organization AS Org JOIN Organization AS Root ON Org.parent_id BELOW Root.id JOIN URP_UserOrg AS UserOrg ON UserOrg.allowed_org_id = Root.id';
+			$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData($sUserOrgQuery));
+			while ($aRow = $oUserOrgSet->FetchAssoc())
+			{
+				$oUserOrg = $aRow['UserOrg'];
+				$oOrg = $aRow['Org'];
+				$this->m_aUserOrgs[$oUserOrg->Get('userid')][] = $oOrg->GetKey();
+			}
+		}
+		else
+		{
+			$oUserOrgSet = new DBObjectSet(DBObjectSearch::FromOQL_AllData("SELECT URP_UserOrg"));
+			while ($oUserOrg = $oUserOrgSet->Fetch())
+			{
+				$this->m_aUserOrgs[$oUserOrg->Get('userid')][] = $oUserOrg->Get('allowed_org_id');
+			}
 		}
 
 		$this->m_aClassStimulusGrants = array();
@@ -742,7 +763,7 @@ exit;
 		}
 	}
 
-	public function GetSelectFilter($oUser, $sClass)
+	public function GetSelectFilter($oUser, $sClass, $aSettings = array())
 	{
 		$this->LoadCache();
 
@@ -754,33 +775,10 @@ exit;
 
 		// Determine how to position the objects of this class
 		//
-		$aCallSpec = array($sClass, 'MapContextParam');
-		if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization'))
+		$sAttCode = self::GetOwnerOrganizationAttCode($sClass);
+		if (is_null($sAttCode))
 		{
-			$sAttCode = 'id';
-		}
-		elseif (is_callable($aCallSpec))
-		{
-			$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
-
-			if ($sAttCode == null)
-			{
-				return true;
-			}
-			if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
-			{
-				// Skip silently. The data model checker will tell you something about this...
-				return true;
-			}
-		}
-		elseif(MetaModel::IsValidAttCode($sClass, 'org_id'))
-		{
-			$sAttCode = 'org_id';
-		}
-		else
-		{
-			// The objects of this class are not positioned in this dimension
-			// All of them are visible
+			// No filtering for this object
 			return true;
 		}
 		// Position the user
@@ -796,48 +794,65 @@ exit;
 		$oFilter  = new DBObjectSearch($sClass);
 		$oListExpr = ListExpression::FromScalars($aUserOrgs);
 		
-		// Check if the condition points to a hierarchical key
-		$bConditionAdded = false;
+		$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
+		$oFilter->AddConditionExpression($oCondition);
 
-		if ($sAttCode == 'id')
+		if (self::HasSharing())
 		{
-			// Filtering on the objects themselves
-			$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($sClass);
-			
-			if ($sHierarchicalKeyCode !== false)
+			if (($sAttCode == 'id') && isset($aSettings['bSearchMode']) && $aSettings['bSearchMode'])
 			{
-				$oRootFilter = new DBObjectSearch($sClass);
-				$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
-				$oRootFilter->AddConditionExpression($oCondition);
-				$oFilter->AddCondition_PointingTo($oRootFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default
-				$bConditionAdded = true;
+				// Querying organizations (or derived)
+				// and the expected list of organizations will be used as a search criteria
+				// Therefore the query can also return organization having objects shared with the allowed organizations
+				//
+				// 1) build the list of organizations sharing something with the allowed organizations
+				// Organization <== sharing_org_id == SharedObject having org_id IN {user orgs}
+				$oShareSearch = new DBObjectSearch('SharedObject');
+				$oOrgField = new FieldExpression('org_id', 'SharedObject');
+				$oShareSearch->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
+	
+				$oSearchSharers = new DBObjectSearch('Organization');
+				$oSearchSharers->AllowAllData();
+				$oSearchSharers->AddCondition_ReferencedBy($oShareSearch, 'sharing_org_id');
+				$aSharers = array();
+				foreach($oSearchSharers->ToDataArray(array('id')) as $aRow)
+				{
+					$aSharers[] = $aRow['id'];
+				}
+				// 2) Enlarge the overall results: ... OR id IN(id1, id2, id3)
+				if (count($aSharers) > 0)
+				{
+					$oSharersList = ListExpression::FromScalars($aSharers);
+					$oFilter->MergeConditionExpression(new BinaryExpression($oExpression, 'IN', $oSharersList));
+				}
 			}
-		}
-		else
-		{
-			$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
-			if ($oAttDef->IsExternalKey())
+	
+			$aShareProperties = SharedObject::GetSharedClassProperties($sClass);
+			if ($aShareProperties)
 			{
-				$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass($oAttDef->GetTargetClass());
-				
-				if ($sHierarchicalKeyCode !== false)
+				$sShareClass = $aShareProperties['share_class'];
+				$sShareAttCode = $aShareProperties['attcode'];
+	
+				$oSearchShares = new DBObjectSearch($sShareClass);
+				$oSearchShares->AllowAllData();
+	
+				$sHierarchicalKeyCode = MetaModel::IsHierarchicalClass('Organization');
+				$oOrgField = new FieldExpression('org_id', $sShareClass);
+				$oSearchShares->AddConditionExpression(new BinaryExpression($oOrgField, 'IN', $oListExpr));
+				$aShared = array();
+				foreach($oSearchShares->ToDataArray(array($sShareAttCode)) as $aRow)
+				{
+					$aShared[] = $aRow[$sShareAttCode];
+				}
+				if (count($aShared) > 0)
 				{
-					$oRootFilter = new DBObjectSearch($oAttDef->GetTargetClass());
-					$oExpression = new FieldExpression('id', $oAttDef->GetTargetClass());
-					$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
-					$oRootFilter->AddConditionExpression($oCondition);
-					$oHKFilter = new DBObjectSearch($oAttDef->GetTargetClass());
-					$oHKFilter->AddCondition_PointingTo($oRootFilter, $sHierarchicalKeyCode, TREE_OPERATOR_BELOW); // Use the 'below' operator by default
-					$oFilter->AddCondition_PointingTo($oHKFilter, $sAttCode);
-					$bConditionAdded = true;
+					$oObjId = new FieldExpression('id', $sClass);
+					$oSharedIdList = ListExpression::FromScalars($aShared);
+					$oFilter->MergeConditionExpression(new BinaryExpression($oObjId, 'IN', $oSharedIdList));
 				}
 			}
-		}
-		if (!$bConditionAdded)
-		{
-			$oCondition = new BinaryExpression($oExpression, 'IN', $oListExpr);
-			$oFilter->AddConditionExpression($oCondition);
-		}
+		} // if HasSharing
+
 		return $oFilter;
 	}
 
@@ -926,10 +941,76 @@ exit;
 	{
 		$this->LoadCache();
 
-		// 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
 		$aObjectPermissions = $this->GetUserActionGrant($oUser, $sClass, $iActionCode);
-		return $aObjectPermissions['permission'];
+		$iPermission = $aObjectPermissions['permission'];
+
+		// Note: In most cases 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
+
+		if ($iPermission != UR_ALLOWED_YES)
+		{
+			// It is already NO for everyone... that's the final word!
+		}
+		elseif ($iActionCode == UR_ACTION_READ)
+		{
+			// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
+		}
+		elseif ($iActionCode == UR_ACTION_BULK_READ)
+		{
+			// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
+		}
+		elseif ($oInstanceSet)
+		{
+			// We are protected by GetSelectFilter: the object set contains objects allowed or shared for reading
+			// We have to answer NO for objects shared for reading purposes
+			if (self::HasSharing())
+			{
+				$aClassProps = SharedObject::GetSharedClassProperties($sClass);
+				if ($aClassProps)
+				{
+					// This class is shared, GetSelectFilter may allow some objects for read only
+					// But currently we are checking wether the objects might be written...
+					// Let's exclude the objects based on the relevant criteria
+
+					$sOrgAttCode = self::GetOwnerOrganizationAttCode($sClass);
+					if (!is_null($sOrgAttCode))
+					{
+						$aUserOrgs = $this->GetUserOrgs($oUser, $sClass);
+						if (!is_null($aUserOrgs) && count($aUserOrgs) > 0)
+						{
+							$iCountNO = 0;
+							$iCountYES = 0;
+							$oInstanceSet->Rewind();
+							while($oObject = $oInstanceSet->Fetch())
+							{
+								$iOrg = $oObject->Get($sOrgAttCode);
+								if (in_array($iOrg, $aUserOrgs))
+								{
+									$iCountYES++;
+								}
+								else
+								{
+									$iCountNO++;
+								}
+							}
+							if ($iCountNO == 0)
+							{
+								$iPermission = UR_ALLOWED_YES;
+							}
+							elseif ($iCountYES == 0)
+							{
+								$iPermission = UR_ALLOWED_NO;
+							}
+							else
+							{
+								$iPermission = UR_ALLOWED_DEPENDS;
+							}
+						}
+					}
+				}
+			}
+		}
+		return $iPermission;
 	}
 
 	public function IsActionAllowedOnAttribute($oUser, $sClass, $sAttCode, $iActionCode, $oInstanceSet = null)
@@ -993,6 +1074,49 @@ exit;
 	{
 		$this->ResetCache();
 	}
+
+	/**
+	 * Find out which attribute is corresponding the the dimension 'owner org'
+	 * returns null if no such attribute has been found (no filtering should occur)	 
+	 */	 	
+	public static function GetOwnerOrganizationAttCode($sClass)
+	{
+		$sAttCode = null;
+
+		$aCallSpec = array($sClass, 'MapContextParam');
+		if (($sClass == 'Organization') || is_subclass_of($sClass, 'Organization'))
+		{
+			$sAttCode = 'id';
+		}
+		elseif (is_callable($aCallSpec))
+		{
+			$sAttCode = call_user_func($aCallSpec, 'org_id'); // Returns null when there is no mapping for this parameter
+			if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
+			{
+				// Skip silently. The data model checker will tell you something about this...
+				$sAttCode = null;
+			}
+		}
+		elseif(MetaModel::IsValidAttCode($sClass, 'org_id'))
+		{
+			$sAttCode = 'org_id';
+		}
+
+		return $sAttCode;
+	}
+
+	/**
+	 * Determine wether the objects can be shared by the mean of a class SharedObject
+	 **/
+	protected static function HasSharing()
+	{
+		static $bHasSharing;
+		if (!isset($bHasSharing))
+		{
+			$bHasSharing = class_exists('SharedObject');
+		}
+		return $bHasSharing;
+	}
 }
 
 

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

@@ -734,7 +734,7 @@ exit;
 		return true;
 	}
 
-	public function GetSelectFilter($oUser, $sClass)
+	public function GetSelectFilter($oUser, $sClass, $aSettings = array())
 	{
 		$aConditions = array();
 		foreach ($this->m_aDimensions as $iDimension => $oDimension)

+ 1 - 0
application/applicationcontext.class.inc.php

@@ -115,6 +115,7 @@ class ApplicationContext
 					if (MetaModel::IsValidClass('Organization'))
 					{
 						$oSearchFilter = new DBObjectSearch('Organization');
+						$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
 						$oSet = new CMDBObjectSet($oSearchFilter);
 						$iCount = $oSet->Count();
 						if ($iCount == 1)

+ 5 - 3
application/cmdbabstract.class.inc.php

@@ -1548,7 +1548,9 @@ EOF
 			if ($oAttDef->IsExternalKey())
 			{
 				$sTargetClass = $oAttDef->GetTargetClass();
-				$oAllowedValues = new DBObjectSet(new DBObjectSearch($sTargetClass));
+				$oSearch = new DBObjectSearch($sTargetClass);
+				$oSearch->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
+				$oAllowedValues = new DBObjectSet($oSearch);
 
 				$iFieldSize = $oAttDef->GetMaxSize();
 				$iMaxComboLength = $oAttDef->GetMaximumComboLength();
@@ -2382,7 +2384,7 @@ EOF
 		$current = HILIGHT_CLASS_NONE; // Not hilighted by default
 
 		// Invoke extensions before the deletion (the deletion will do some cleanup and we might loose some information
-		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
+		foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
 		{
 			$new = $oExtensionInstance->GetHilightClass($this);
 			@$current = self::$m_highlightComparison[$current][$new];
@@ -2667,7 +2669,7 @@ EOF
 		$this->UpdateObjectFromArray($aFinalValues);
 
 		// Invoke extensions after the update of the object from the form
-		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
+		foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
 		{
 			$oExtensionInstance->OnFormSubmit($this, $sFormPrefix);
 		}

+ 4 - 2
application/itopwebpage.class.inc.php

@@ -462,11 +462,13 @@ EOF
 			// Display the list of *favorite* organizations... but keeping in mind what is the real number of organizations
 			$aFavoriteOrgs = appUserPreferences::GetPref('favorite_orgs', null);
 			$oSearchFilter = new DBObjectSearch('Organization');
+			$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
 			$oSet = new CMDBObjectSet($oSearchFilter);
 			$iCount = $oSet->Count(); // total number of existing Orgs
 			
 			// Now get the list of Orgs to be displayed in the menu
 			$oSearchFilter = DBObjectSearch::FromOQL(ApplicationMenu::GetFavoriteSiloQuery());
+			$oSearchFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', true);
 			if (!empty($aFavoriteOrgs))
 			{
 				$oSearchFilter->AddCondition('id', $aFavoriteOrgs, 'IN');
@@ -513,8 +515,8 @@ EOF
 			$sHtml .= '</select>';
 */
 			$sFavoriteOrgs = '';
-			$oWidget = new UIExtKeyWidget('Organization', 'org_id');
-			$sHtml .= $oWidget->Display($this, 50, false, '', $oSet, $iCurrentOrganization, 'org_id', false, 'c[org_id]', '', array('iFieldSize' => 20, 'iMinChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'), 'sDefaultValue' => Dict::S('UI:AllOrganizations')), $bSearchMode = true);
+			$oWidget = new UIExtKeyWidget('Organization', 'org_id', '', true /* search mode */);
+			$sHtml .= $oWidget->Display($this, 50, false, '', $oSet, $iCurrentOrganization, 'org_id', false, 'c[org_id]', '', array('iFieldSize' => 20, 'iMinChars' => MetaModel::GetConfig()->Get('min_autocomplete_chars'), 'sDefaultValue' => Dict::S('UI:AllOrganizations')));
 			$this->add_ready_script('$("#org_id").bind("extkeychange", function() { $("#SiloSelection form").submit(); } )');
 			$this->add_ready_script("$('#label_org_id').click( function() { $(this).val(''); $('#org_id').val(''); return true; } );\n");
 			// Add other dimensions/context information to this form

+ 40 - 16
application/ui.extkeywidget.class.inc.php

@@ -66,6 +66,7 @@ class UIExtKeyWidget
 	protected $iId;
 	protected $sTargetClass;
 	protected $sAttCode;
+	protected $bSearchMode;
 	
 	//public function __construct($sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sNameSuffix = '', $sFieldPrefix = '', $sFormPrefix = '')
 	static public function DisplayFromAttCode($oPage, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false)
@@ -82,15 +83,16 @@ class UIExtKeyWidget
 		{
 			$sDisplayStyle = 'select'; // In search mode, always use a drop-down list
 		}
-		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode);
-		return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, $bSearchMode, $sDisplayStyle);
+		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
+		return $oWidget->Display($oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix, $aArgs, null, $sDisplayStyle);
 	}
 
-	public function __construct($sTargetClass, $iInputId, $sAttCode = '')
+	public function __construct($sTargetClass, $iInputId, $sAttCode = '', $bSearchMode = false)
 	{
 		$this->sTargetClass = $sTargetClass;
 		$this->iId = $iInputId;
 		$this->sAttCode = $sAttCode;
+		$this->bSearchMode = $bSearchMode;
 	}
 	
 	/**
@@ -99,28 +101,34 @@ class UIExtKeyWidget
 	 * @param Hash $aArgs Extra context arguments
 	 * @return string The HTML fragment to be inserted into the page
 	 */
-	public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = false, $sDisplayStyle = 'select')
+	public function Display(WebPage $oPage, $iMaxComboLength, $bAllowTargetCreation, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName, $sFormPrefix = '', $aArgs = array(), $bSearchMode = null, $sDisplayStyle = 'select')
 	{
+		if (!is_null($bSearchMode))
+		{
+			$this->bSearchMode = $bSearchMode;
+		}
 		$sTitle = addslashes($sTitle);	
 		$oPage->add_linked_script('../js/extkeywidget.js');
 		$oPage->add_linked_script('../js/forms-json-utils.js');
 		
-		$bCreate = (!$bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
+		$bCreate = (!$this->bSearchMode) && (!MetaModel::IsAbstract($this->sTargetClass)) && (UserRights::IsActionAllowed($this->sTargetClass, UR_ACTION_BULK_MODIFY) && $bAllowTargetCreation);
 		$bExtensions = true;
 		$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
-		$sAttrFieldPrefix = ($bSearchMode) ? '' : 'attr_';
+		$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
 
 		$sHTMLValue = "<span style=\"white-space:nowrap\">"; // no wrap
 		$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
-		if($bSearchMode)
+		if($this->bSearchMode)
 		{
 			$sWizHelper = 'null';
 			$sWizHelperJSON = "''";
+			$sJSSearchMode = 'true';
 		} 
 		else
 		{
 			$sWizHelper = 'oWizardHelper'.$sFormPrefix;
 			$sWizHelperJSON = $sWizHelper.'.ToJSON()';
+			$sJSSearchMode = 'false';
 		}
 		if (is_null($oAllowedValues))
 		{
@@ -155,7 +163,7 @@ class UIExtKeyWidget
 				$sHelpText = ''; //$this->oAttDef->GetHelpOnEdition();
 				
 				$sHTMLValue = "<select title=\"$sHelpText\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" id=\"$this->iId\">\n";
-				if ($bSearchMode)
+				if ($this->bSearchMode)
 				{
 					$sDisplayValue = isset($aArgs['sDefaultValue']) ? $aArgs['sDefaultValue'] : Dict::S('UI:SearchValue:Any');
 					$sHTMLValue .= "<option value=\"\">$sDisplayValue</option>\n";			
@@ -184,7 +192,7 @@ class UIExtKeyWidget
 				$sHTMLValue .= "</select>\n";
 				$oPage->add_ready_script(
 <<<EOF
-		oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}');
+		oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', true, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
 		oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
 		$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
 		$('#$this->iId').bind('change', function() { $(this).trigger('extkeychange') } );
@@ -215,13 +223,14 @@ EOF
 	
 			// another hidden input to store & pass the object's Id
 			$sHTMLValue .= "<input type=\"hidden\" id=\"$this->iId\" name=\"{$sAttrFieldPrefix}{$sFieldName}\" value=\"$value\" />\n";
-	
+
+			$JSSearchMode = $this->bSearchMode ? 'true' : 'false';	
 			// Scripts to start the autocomplete and bind some events to it
 			$oPage->add_ready_script(
 <<<EOF
-		oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}');
+		oACWidget_{$this->iId} = new ExtKeyWidget('{$this->iId}', '{$this->sTargetClass}', '$sFilter', '$sTitle', false, $sWizHelper, '{$this->sAttCode}', $sJSSearchMode);
 		oACWidget_{$this->iId}.emptyHtml = "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>$sMessage</p></div>";
-		$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter', json: function() { return $sWizHelperJSON; } }});
+		$('#label_$this->iId').autocomplete(GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', { scroll:true, minChars:{$iMinChars}, autoFill:false, matchContains:true, mustMatch: true, keyHolder:'#{$this->iId}', extraParams:{operation:'ac_extkey', sTargetClass:'{$this->sTargetClass}',sFilter:'$sFilter',bSearchMode:$JSSearchMode, json: function() { return $sWizHelperJSON; } }});
 		$('#label_$this->iId').keyup(function() { if ($(this).val() == '') { $('#$this->iId').val(''); } } ); // Useful for search forms: empty value in the "label", means no value, immediatly !
 		$('#label_$this->iId').result( function(event, data, formatted) { OnAutoComplete('{$this->iId}', event, data, formatted); } );
 		$('#$this->iId').bind('update', function() { oACWidget_{$this->iId}.Update(); } );
@@ -277,10 +286,10 @@ EOF
 		}
 		else
 		{
-			$aParam = array();
+			$aParams = array();
 			$oFilter = new DBObjectSearch($this->sTargetClass);
-			$oSet = new CMDBObjectSet($oFilter);
 		}
+		$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
 		$oBlock = new DisplayBlock($oFilter, 'search', false, $aParams);
 		$sHTML .= $oBlock->GetDisplay($oPage, $this->iId, array('open' => true, 'currentId' => $this->iId));
 		$sHTML .= "<form id=\"fr_{$this->iId}\" OnSubmit=\"return oACWidget_{$this->iId}.DoOk();\">\n";
@@ -319,6 +328,7 @@ EOF
 		try
 		{
 			$oFilter = DBObjectSearch::FromOQL($sFilter);
+			$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
 			$oBlock = new DisplayBlock($oFilter, 'list', false, array('query_params' => array('this' => $oObj)));
 			$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single')); // Don't display the 'Actions' menu on the results
 		}
@@ -328,6 +338,7 @@ EOF
 			// TODO check if we can improve this behavior...
 			$sOQL = 'SELECT '.$sRemoteClass;
 			$oFilter = DBObjectSearch::FromOQL($sOQL);
+			$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
 			//$oBlock = new DisplayBlock($oFilter, 'list', false);
 			//$oBlock->Display($oP, $this->iId.'_results', array('cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single')); // Don't display the 'Actions' menu on the results
 		}
@@ -347,6 +358,7 @@ EOF
 			throw new Exception('Implementation: null value for allowed values definition');
 		}
 		$oValuesSet = new ValueSetObjects($sFilter, 'friendlyname'); // Bypass GetName() to avoid the encoding by htmlentities
+		$oValuesSet->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
 		$aValues = $oValuesSet->GetValues(array('this' => $oObj), $sContains);
 		foreach($aValues as $sKey => $sFriendlyName)
 		{
@@ -359,8 +371,18 @@ EOF
 	 */
 	public function GetObjectName($iObjId)
 	{
-		$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId);
-		return $oObj->GetName();
+		$aModifierProps = array();
+		$aModifierProps['UserRightsGetSelectFilter']['bSearchMode'] = $this->bSearchMode;
+
+		$oObj = MetaModel::GetObject($this->sTargetClass, $iObjId, false, false, $aModifierProps);
+		if ($oObj)
+		{
+			return $oObj->GetName();
+		}
+		else
+		{
+			return '';
+		}
 	}
 	
 	/**
@@ -421,6 +443,7 @@ EOF
 		try
 		{
 			$oFilter = DBObjectSearch::FromOQL($sFilter);
+			$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
 			$oSet = new DBObjectSet($oFilter, array(), array('this' => $oObj));
 		}
 		catch(MissingQueryArgument $e)
@@ -429,6 +452,7 @@ EOF
 			// TODO check if we can improve this behavior...
 			$sOQL = 'SELECT '.$this->m_sTargetClass;
 			$oFilter = DBObjectSearch::FromOQL($sOQL);
+			$oFilter->SetModifierProperty('UserRightsGetSelectFilter', 'bSearchMode', $this->bSearchMode);
 			$oSet = new DBObjectSet($oFilter);
 		}
 

+ 2 - 1
application/ui.linkswidget.class.inc.php

@@ -62,8 +62,9 @@ class UILinksWidget
 		$this->m_aTableConfig = array();
 		$this->m_aTableConfig['form::checkbox'] = array( 'label' => "<input class=\"select_all\" type=\"checkbox\" value=\"1\" onClick=\"CheckAll('#linkedset_{$this->m_sAttCode}{$this->m_sNameSuffix} .selection', this.checked); oWidget".$this->m_iInputId.".OnSelectChange();\">", 'description' => Dict::S('UI:SelectAllToggle+'));
 
-		foreach(MetaModel::ListAttributeDefs($this->m_sLinkedClass) as $sAttCode=>$oAttDef)
+		foreach(MetaModel::FlattenZList(MetaModel::GetZListItems($this->m_sLinkedClass, 'list')) as $sAttCode)
 		{
+			$oAttDef = MetaModel::GetAttributeDef($this->m_sLinkedClass, $sAttCode);
 			if ($sStateAttCode == $sAttCode)
 			{
 				// State attribute is always hidden from the UI

+ 4 - 2
core/dbobjectsearch.class.php

@@ -807,7 +807,7 @@ class DBObjectSearch
 	public function serialize($bDevelopParams = false, $aContextParams = null)
 	{
 		$sOql = $this->ToOql($bDevelopParams, $aContextParams);
-		return base64_encode(serialize(array($sOql, $this->m_aParams)));
+		return base64_encode(serialize(array($sOql, $this->m_aParams, $this->m_aModifierProperties)));
 	}
 	
 	static public function unserialize($sValue)
@@ -818,7 +818,9 @@ class DBObjectSearch
 		// We've tried to use gzcompress/gzuncompress, but for some specific queries
 		// it was not working at all (See Trac #193)
 		// gzuncompress was issuing a warning "data error" and the return object was null
-		return self::FromOQL($sOql, $aParams);
+		$oRetFilter = self::FromOQL($sOql, $aParams);
+		$oRetFilter->m_aModifierProperties = $aData[2];
+		return $oRetFilter;
 	}
 
 	// SImple BUt Structured Query Languag - SubuSQL

+ 14 - 0
core/dict.class.inc.php

@@ -220,6 +220,20 @@ class Dict
 		}
 	}
 
+	/**
+	 * Clone a string in every language (if it exists in that language)
+	 */	 	
+	public static function CloneString($sSourceCode, $sDestCode)
+	{
+		foreach(self::$m_aLanguages as $sLanguageCode => $foo)
+		{
+			if (isset(self::$m_aData[$sLanguageCode][$sSourceCode]))
+			{
+				self::$m_aData[$sLanguageCode][$sDestCode] = self::$m_aData[$sLanguageCode][$sSourceCode];
+			}
+		}
+	}
+
 	public static function MakeStats($sLanguageCode, $sLanguageRef = 'EN US')
 	{
 		$aMissing = array(); // Strings missing for the target language

+ 78 - 41
core/metamodel.class.php

@@ -17,6 +17,7 @@
 require_once(APPROOT.'core/modulehandler.class.inc.php');
 require_once(APPROOT.'core/querybuildercontext.class.inc.php');
 require_once(APPROOT.'core/querymodifier.class.inc.php');
+require_once(APPROOT.'core/metamodelmodifier.inc.php');
 
 /**
  * Metamodel
@@ -1172,6 +1173,33 @@ abstract class MetaModel
 
 		self::$m_sTablePrefix = $sTablePrefix;
 
+		// Build the list of available extensions
+		//
+		$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iQueryModifier', 'iOnClassInitialization');
+		foreach($aInterfaces as $sInterface)
+		{
+			self::$m_aExtensionClasses[$sInterface] = array();
+		}
+
+		foreach(get_declared_classes() as $sPHPClass)
+		{
+			$oRefClass = new ReflectionClass($sPHPClass);
+			$oExtensionInstance = null;
+			foreach($aInterfaces as $sInterface)
+			{
+				if ($oRefClass->implementsInterface($sInterface))
+				{
+					if (is_null($oExtensionInstance))
+					{
+						$oExtensionInstance = new $sPHPClass;
+					}
+					self::$m_aExtensionClasses[$sInterface][$sPHPClass] = $oExtensionInstance;
+				}
+			}
+		}
+
+		// Initialize the classes (declared attributes, etc.)
+		//
 		foreach(get_declared_classes() as $sPHPClass) {
 			if (is_subclass_of($sPHPClass, 'DBObject'))
 			{
@@ -1184,6 +1212,10 @@ abstract class MetaModel
 				if (method_exists($sPHPClass, 'Init'))
 				{
 					call_user_func(array($sPHPClass, 'Init'));
+					foreach (MetaModel::EnumPlugins('iOnClassInitialization') as $sPluginClass => $oClassInit)
+					{
+						$oClassInit->OnAfterClassInitialization($sPHPClass);
+					}
 				}
 			}
 		}
@@ -1401,31 +1433,6 @@ abstract class MetaModel
 			//	}
 			//}
 		}
-
-		// Build the list of available extensions
-		//
-		$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iQueryModifier');
-		foreach($aInterfaces as $sInterface)
-		{
-			self::$m_aExtensionClasses[$sInterface] = array();
-		}
-
-		foreach(get_declared_classes() as $sPHPClass)
-		{
-			$oRefClass = new ReflectionClass($sPHPClass);
-			$oExtensionInstance = null;
-			foreach($aInterfaces as $sInterface)
-			{
-				if ($oRefClass->implementsInterface($sInterface))
-				{
-					if (is_null($oExtensionInstance))
-					{
-						$oExtensionInstance = new $sPHPClass;
-					}
-					self::$m_aExtensionClasses[$sInterface][$sPHPClass] = $oExtensionInstance;
-				}
-			}
-		}
 	}
 
 	// To be overriden, must be called for any object class (optimization)
@@ -1552,9 +1559,12 @@ abstract class MetaModel
 		return true;
 	}
 
-	public static function Init_AddAttribute(AttributeDefinition $oAtt)
+	public static function Init_AddAttribute(AttributeDefinition $oAtt, $sTargetClass = null)
 	{
-		$sTargetClass = self::GetCallersPHPClass("Init");
+		if (!$sTargetClass)
+		{
+			$sTargetClass = self::GetCallersPHPClass("Init");
+		}
 
 		$sAttCode = $oAtt->GetCode();
 		if ($sAttCode == 'finalclass')
@@ -1615,11 +1625,14 @@ abstract class MetaModel
 		// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used		
 	}
 
-	public static function Init_SetZListItems($sListCode, $aItems)
+	public static function Init_SetZListItems($sListCode, $aItems, $sTargetClass = null)
 	{
 		MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos);
 
-		$sTargetClass = self::GetCallersPHPClass("Init");
+		if (!$sTargetClass)
+		{
+			$sTargetClass = self::GetCallersPHPClass("Init");
+		}
 
 		// Discard attributes that do not make sense
 		// (missing classes in the current module combination, resulting in irrelevant ext key or link set)
@@ -1906,7 +1919,7 @@ abstract class MetaModel
 		//
 		if (!$oFilter->IsAllDataAllowed() && !$oFilter->IsDataFiltered())
 		{
-			$oVisibleObjects = UserRights::GetSelectFilter($oFilter->GetClass());
+			$oVisibleObjects = UserRights::GetSelectFilter($oFilter->GetClass(), $oFilter->GetModifierProperties('UserRightsGetSelectFilter'));
 			if ($oVisibleObjects === false)
 			{
 				// Make sure this is a valid search object, saying NO for all
@@ -4375,17 +4388,31 @@ abstract class MetaModel
 	{
 		$aRes = array();
 		$iTotalHits = 0;
-		foreach(self::$aQueryCacheGetObjectHits as $sClass => $iHits)
+		foreach(self::$aQueryCacheGetObjectHits as $sClassSign => $iHits)
 		{
-			$aRes[] = "$sClass: $iHits";
+			$aRes[] = "$sClassSign: $iHits";
 			$iTotalHits += $iHits;
 		}
 		return $iTotalHits.' ('.implode(', ', $aRes).')';
 	}
 
-	public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false)
+	public static function MakeSingleRow($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
 	{
-		if (!array_key_exists($sClass, self::$aQueryCacheGetObject))
+		// Build the query cache signature
+		//
+		$sQuerySign = $sClass;
+		if($bAllowAllData)
+		{
+			$sQuerySign .= '_all_';
+		}
+		if (count($aModifierProperties))
+		{
+			array_multisort($aModifierProperties);
+			$sModifierProperties = json_encode($aModifierProperties);
+			$sQuerySign .= '_all_'.md5($sModifierProperties);
+		}
+
+		if (!array_key_exists($sQuerySign, self::$aQueryCacheGetObject))
 		{
 			// NOTE: Quick and VERY dirty caching mechanism which relies on
 			//       the fact that the string '987654321' will never appear in the
@@ -4394,20 +4421,30 @@ abstract class MetaModel
 			//       but this would slow down -by how much time?- the application
 			$oFilter = new DBObjectSearch($sClass);
 			$oFilter->AddCondition('id', 987654321, '=');
+			if ($aModifierProperties)
+			{
+				foreach ($aModifierProperties as $sPluginClass => $aProperties)
+				{
+					foreach ($aProperties as $sProperty => $value)
+					{
+						$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
+					}
+				}
+			}
 			if ($bAllowAllData)
 			{
 				$oFilter->AllowAllData();
 			}
 	
 			$sSQL = self::MakeSelectQuery($oFilter);
-			self::$aQueryCacheGetObject[$sClass] = $sSQL;
-			self::$aQueryCacheGetObjectHits[$sClass] = 0;
+			self::$aQueryCacheGetObject[$sQuerySign] = $sSQL;
+			self::$aQueryCacheGetObjectHits[$sQuerySign] = 0;
 		}
 		else
 		{
-			$sSQL = self::$aQueryCacheGetObject[$sClass];
-			self::$aQueryCacheGetObjectHits[$sClass] += 1;
-//			echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sClass]."<br/>\n";
+			$sSQL = self::$aQueryCacheGetObject[$sQuerySign];
+			self::$aQueryCacheGetObjectHits[$sQuerySign] += 1;
+//			echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sQuerySign]."<br/>\n";
 		}
 		$sSQL = str_replace(CMDBSource::Quote(987654321), CMDBSource::Quote($iKey), $sSQL);
 		$res = CMDBSource::Query($sSQL);
@@ -4453,10 +4490,10 @@ abstract class MetaModel
 		return new $sClass($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
 	}
 
-	public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false)
+	public static function GetObject($sClass, $iKey, $bMustBeFound = true, $bAllowAllData = false, $aModifierProperties = null)
 	{
 		self::_check_subclass($sClass);	
-		$aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound, $bAllowAllData);
+		$aRow = self::MakeSingleRow($sClass, $iKey, $bMustBeFound, $bAllowAllData, $aModifierProperties);
 		if (empty($aRow))
 		{
 			return null;

+ 31 - 0
core/metamodelmodifier.inc.php

@@ -0,0 +1,31 @@
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * Any extension to hook the initialization of the metamodel 
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
+ */
+
+interface iOnClassInitialization
+{
+	public function OnAfterClassInitialization($sClass);
+}
+
+?>

+ 3 - 3
core/userrights.class.inc.php

@@ -55,7 +55,7 @@ abstract class UserRightsAddOnAPI
 	abstract public function Init(); // loads data (possible optimizations)
 
 	// Used to build select queries showing only objects visible for the given user
-	abstract public function GetSelectFilter($sLogin, $sClass); // returns a filter object
+	abstract public function GetSelectFilter($sLogin, $sClass, $aSettings = array()); // returns a filter object
 
 	abstract public function IsActionAllowed($oUser, $sClass, $iActionCode, /*dbObjectSet*/ $oInstanceSet = null);
 	abstract public function IsStimulusAllowed($oUser, $sClass, $sStimulusCode, /*dbObjectSet*/ $oInstanceSet = null);
@@ -647,7 +647,7 @@ class UserRights
 		return true;
 	}
 
-	public static function GetSelectFilter($sClass)
+	public static function GetSelectFilter($sClass, $aSettings = array())
 	{
 		// When initializing, we need to let everything pass trough
 		if (!self::CheckLogin()) return true;
@@ -656,7 +656,7 @@ class UserRights
 
 		if (MetaModel::HasCategory($sClass, 'bizmodel'))
 		{
-			return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass);
+			return self::$m_oAddOn->GetSelectFilter(self::$m_oUser, $sClass, $aSettings);
 		}
 		else
 		{

+ 22 - 1
core/valuesetdef.class.inc.php

@@ -97,17 +97,24 @@ class ValueSetObjects extends ValueSetDefinition
 	protected $m_aOrderBy;
 	protected $m_aExtraConditions;
 	private $m_bAllowAllData;
+	private $m_aModifierProperties;
 
-	public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false)
+	public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array(), $bAllowAllData = false, $aModifierProperties = array())
 	{
 		$this->m_sContains = '';
 		$this->m_sFilterExpr = $sFilterExp;
 		$this->m_sValueAttCode = $sValueAttCode;
 		$this->m_aOrderBy = $aOrderBy;
 		$this->m_bAllowAllData = $bAllowAllData;
+		$this->m_aModifierProperties = $aModifierProperties;
 		$this->m_aExtraConditions = array();
 	}
 
+	public function SetModifierProperty($sPluginClass, $sProperty, $value)
+	{
+		$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
+	}
+
 	public function AddCondition(DBObjectSearch $oFilter)
 	{
 		$this->m_aExtraConditions[] = $oFilter;		
@@ -127,6 +134,13 @@ class ValueSetObjects extends ValueSetDefinition
 		{
 			$oFilter->MergeWith($oExtraFilter);
 		}
+		foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
+		{
+			foreach ($aProperties as $sProperty => $value)
+			{
+				$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
+			}
+		}
 
 		return new DBObjectSet($oFilter, $this->m_aOrderBy, $aArgs);
 	}
@@ -162,6 +176,13 @@ class ValueSetObjects extends ValueSetDefinition
 		{
 			$oFilter->MergeWith($oExtraFilter);
 		}
+		foreach($this->m_aModifierProperties as $sPluginClass => $aProperties)
+		{
+			foreach ($aProperties as $sProperty => $value)
+			{
+				$oFilter->SetModifierProperty($sPluginClass, $sProperty, $value);
+			}
+		}
 
 		$oValueExpr = new ScalarExpression('%'.$sContains.'%');
 		$oNameExpr = new FieldExpression('friendlyname', $oFilter->GetClassAlias());

+ 8 - 2
js/extkeywidget.js

@@ -13,7 +13,7 @@
 //   along with this program; if not, write to the Free Software
 //   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper, sAttCode)
+function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper, sAttCode, bSearchMode)
 {
 	this.id = id;
 	this.sTargetClass = sTargetClass;
@@ -25,6 +25,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
 	this.oWizardHelper = oWizHelper;
 	this.ajax_request = null;
 	this.bSelectMode = bSelectMode; // true if the edited field is a SELECT, false if it's an autocomplete
+	this.bSearchMode = bSearchMode; // true if selecting a value in the context of a search form
 	this.v_html = '';
 	var me = this;
 	
@@ -64,6 +65,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
 				   sTitle: me.sTitle,
 				   sAttCode: me.sAttCode,
 				   sTargetClass: me.sTargetClass,
+				   bSearchMode: me.bSearchMode,
 				   operation: 'objectSearchForm'
 				 }
 	
@@ -138,7 +140,8 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
 	{
 		var theMap = { sTargetClass: me.sTargetClass,
 					   iInputId: me.id,
-					   sFilter: me.sFilter
+					   sFilter: me.sFilter,
+					   bSearchMode: me.bSearchMode
 					 }
 		
 		// Gather the parameters from the search form
@@ -215,6 +218,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
 				   iInputId: me.id,
 				   iObjectId: iObjectId,
 				   sAttCode: me.sAttCode,
+				   bSearchMode: me.bSearchMode,
 				   operation: 'getObjectName'
 				 }
 	
@@ -420,6 +424,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
 		var theMap = { sTargetClass: me.sTargetClass,
 				   	   sInputId: me.id,
 				   	   sFilter: me.sFilter,
+				   	   bSearchMode: me.bSearchMode,
 				   	   sAttCode: me.sAttCode,
 				   	   value: $('#'+me.id).val()
 					};
@@ -503,6 +508,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
 				   iInputId: me.id,
 				   iObjectId: iObjectId,
 				   sAttCode: me.sAttCode,
+				   bSearchMode: me.bSearchMode,
 				   operation: 'getObjectName'
 				 }
 	

+ 25 - 17
pages/ajax.render.php

@@ -214,6 +214,7 @@ try
 		$sFilter = utils::ReadParam('sFilter', '', false, 'raw_data');
 		$sJson = utils::ReadParam('json', '', false, 'raw_data');
 		$sAttCode = utils::ReadParam('sAttCode', '');
+		$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
 		if (!empty($sJson))
 		{
 			$oWizardHelper = WizardHelper::FromJSON($sJson);
@@ -224,7 +225,7 @@ try
 			// Search form: no current object
 			$oObj = null;
 		}
-		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode);
+		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
 		$oWidget->SearchObjectsToSelect($oPage, $sFilter, $sRemoteClass, $oObj);	
 		break;
 	
@@ -235,18 +236,22 @@ try
 		$sFilter = utils::ReadParam('sFilter', '', false, 'raw_data');
 		$sJson = utils::ReadParam('json', '', false, 'raw_data');
 		$sContains = utils::ReadParam('q', '', false, 'raw_data');
-		if (!empty($sJson))
+		$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
+		if ($sContains !='')
 		{
-			$oWizardHelper = WizardHelper::FromJSON($sJson);
-			$oObj = $oWizardHelper->GetTargetObject();
-		}
-		else
-		{
-			// Search form: no current object
-			$oObj = null;
+			if (!empty($sJson))
+			{
+				$oWizardHelper = WizardHelper::FromJSON($sJson);
+				$oObj = $oWizardHelper->GetTargetObject();
+			}
+			else
+			{
+				// Search form: no current object
+				$oObj = null;
+			}
+			$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode);
+			$oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains);
 		}
-		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId);
-		$oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains);
 		break;
 	
 		// ui.extkeywidget
@@ -256,7 +261,8 @@ try
 		$iInputId = utils::ReadParam('iInputId', '');
 		$sTitle = utils::ReadParam('sTitle', '', false, 'raw_data');
 		$sAttCode = utils::ReadParam('sAttCode', '');
-		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode);
+		$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
+		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode);
 		$sJson = utils::ReadParam('json', '', false, 'raw_data');
 		if (!empty($sJson))
 		{
@@ -276,7 +282,7 @@ try
 		$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
 		$iInputId = utils::ReadParam('iInputId', '');
 		$sAttCode = utils::ReadParam('sAttCode', '');
-		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode);
+		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
 		$sJson = utils::ReadParam('json', '', false, 'raw_data');
 		if (!empty($sJson))
 		{
@@ -297,7 +303,7 @@ try
 		$iInputId = utils::ReadParam('iInputId', '');
 		$sFormPrefix = utils::ReadParam('sFormPrefix', '');
 		$sAttCode = utils::ReadParam('sAttCode', '');
-		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode);
+		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
 		$aResult = $oWidget->DoCreateObject($oPage);
 		echo json_encode($aResult);
 		break;
@@ -307,7 +313,8 @@ try
 		$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
 		$iInputId = utils::ReadParam('iInputId', '');
 		$iObjectId = utils::ReadParam('iObjectId', '');
-		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId);
+		$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
+		$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode);
 		$sName = $oWidget->GetObjectName($iObjectId);
 		echo json_encode(array('name' => $sName));	
 		break;
@@ -320,6 +327,7 @@ try
 		$sFilter = utils::ReadParam('sFilter', '', false, 'raw_data');
 		$sJson = utils::ReadParam('json', '', false, 'raw_data');
 		$currValue = utils::ReadParam('value', '');
+		$bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true');
 		if (!empty($sJson))
 		{
 			$oWizardHelper = WizardHelper::FromJSON($sJson);
@@ -330,7 +338,7 @@ try
 			// Search form: no current object
 			$oObj = null;
 		}
-		$oWidget = new UIExtKeyWidget($sTargetClass, $sInputId);
+		$oWidget = new UIExtKeyWidget($sTargetClass, $sInputId, '', $bSearchMode);
 		$oWidget->DisplayHierarchy($oPage, $sFilter, $currValue, $oObj);
 		break;
 		
@@ -619,7 +627,7 @@ try
 		// Let's take this opportunity to inform the plug-ins so that they can perform some cleanup
 		$iTransactionId = utils::ReadParam('transaction_id', 0);
 		$sTempId = session_id().'_'.$iTransactionId;
-		foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
+		foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance)
 		{
 			$oExtensionInstance->OnFormCancel($sTempId);
 		}