Ver Fonte

Implemented the capability to modify queries by the mean of a plugin (reintegrated from branch 1.2, release candidate)

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

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

@@ -303,5 +303,58 @@ class ApplicationContext
 			return '';
 		}	
 	}
+
+	protected static $m_aPluginProperties = null;
+
+	/**
+	 * Load plugin properties for the current session
+	 * @return void
+	 */
+	protected static function LoadPluginProperties()
+	{
+		if (isset($_SESSION['PluginProperties']))
+		{
+			self::$m_aPluginProperties = $_SESSION['PluginProperties'];
+		}
+		else
+		{
+			self::$m_aPluginProperties = array();
+		}
+	}
+
+	/**
+	 * Set plugin properties
+	 * @param sPluginClass string Class implementing any plugin interface
+	 * @param sProperty string Name of the property
+	 * @param value scalar Value (numeric or string)
+	 * @return void
+	 */
+	public static function SetPluginProperty($sPluginClass, $sProperty, $value)
+	{
+		if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
+
+		self::$m_aPluginProperties[$sPluginClass][$sProperty] = $value;
+		$_SESSION['PluginProperties'][$sPluginClass][$sProperty] = $value;
+	}
+
+	/**
+	 * Get plugin properties
+	 * @param sPluginClass string Class implementing any plugin interface
+	 * @return array of sProperty=>value pairs
+	 */
+	public static function GetPluginProperties($sPluginClass)
+	{
+		if (is_null(self::$m_aPluginProperties)) self::LoadPluginProperties();
+
+		if (array_key_exists($sPluginClass, self::$m_aPluginProperties))
+		{
+			return self::$m_aPluginProperties[$sPluginClass];
+		}
+		else
+		{
+			return array();
+		}
+	}
+
 }
 ?>

+ 17 - 0
core/cmdbobject.class.inc.php

@@ -93,6 +93,23 @@ abstract class CMDBObject extends DBObject
 	protected static $m_oCurrChange = null;
 
 
+	public static function SetCurrentChange(CMDBChange $oChange)
+	{
+		self::$m_oCurrChange = $oChange;
+	}
+
+	//
+	// Todo: simplify the APIs and do not pass the current change as an argument anymore
+	//       SetCurrentChange to be invoked in very few cases (UI.php, CSV import, Data synchro)
+	//			GetCurrentChange to be called ONCE (!) by CMDBChangeOp::OnInsert ($this->Set('change', ..GetCurrentChange())
+	//			GetCurrentChange to create a default change if not already done in the current context
+	//
+	public static function GetCurrentChange()
+	{
+		return self::$m_oCurrChange;
+	}
+
+
 	private function RecordObjCreation(CMDBChange $oChange)
 	{
 		$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate");

+ 5 - 0
core/dbobject.class.php

@@ -1302,6 +1302,11 @@ abstract class DBObject
 		return $this->DBInsert();
 	}
 
+	public function DBInsertTrackedNoReload(CMDBChange $oVoid)
+	{
+		return $this->DBInsertNoReload();
+	}
+
 	// Creates a copy of the current object into the database
 	// Returns the id of the newly created object
 	public function DBClone($iNewKey = null)

+ 19 - 0
core/dbobjectsearch.class.php

@@ -66,6 +66,8 @@ class DBObjectSearch
 		$this->m_aRelatedTo = array();
 		$this->m_bDataFiltered = false;
 		$this->m_aParentConditions = array();
+
+		$this->m_aModifierProperties = array();
 	}
 
 	public function AllowAllData() {$this->m_bAllowAllData = true;}
@@ -126,6 +128,23 @@ class DBObjectSearch
 	}
 
 
+	public function SetModifierProperty($sPluginClass, $sProperty, $value)
+	{
+		$this->m_aModifierProperties[$sPluginClass][$sProperty] = $value;
+	}
+
+	public function GetModifierProperties($sPluginClass)
+	{
+		if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
+		{
+			return $this->m_aModifierProperties[$sPluginClass];
+		}
+		else
+		{
+			return array();
+		}
+	}
+
 	public function IsAny()
 	{
 		// #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false;

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

@@ -953,10 +953,10 @@ class QueryBuilderExpressions
 	protected $m_aSelectExpr;
 	protected $m_aJoinFields;
 
-	public function __construct($aSelect, $oCondition)
+	public function __construct($oCondition)
 	{
 		$this->m_oConditionExpr = $oCondition;
-		$this->m_aSelectExpr = $aSelect;
+		$this->m_aSelectExpr = array();
 		$this->m_aJoinFields = array();
 	}
 

+ 104 - 64
core/metamodel.class.php

@@ -15,6 +15,8 @@
 //   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 require_once(APPROOT.'core/modulehandler.class.inc.php');
+require_once(APPROOT.'core/querybuildercontext.class.inc.php');
+require_once(APPROOT.'core/querymodifier.class.inc.php');
 
 /**
  * Metamodel
@@ -25,8 +27,6 @@ require_once(APPROOT.'core/modulehandler.class.inc.php');
  * @license     http://www.opensource.org/licenses/gpl-3.0.html LGPL
  */
 
-
-
 // #@# todo: change into class const (see Doctrine)
 // Doctrine example
 // class toto
@@ -1404,7 +1404,7 @@ abstract class MetaModel
 
 		// Build the list of available extensions
 		//
-		$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension');
+		$aInterfaces = array('iApplicationUIExtension', 'iApplicationObjectExtension', 'iQueryModifier');
 		foreach($aInterfaces as $sInterface)
 		{
 			self::$m_aExtensionClasses[$sInterface] = array();
@@ -1924,12 +1924,26 @@ abstract class MetaModel
 			}
 		}
 
+		// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
+		//
+		$aModifierProperties = self::MakeModifierProperties($oFilter);
+
 		if (self::$m_bQueryCacheEnabled || self::$m_bTraceQueries)
 		{
 			// Need to identify the query
 			$sOqlQuery = $oFilter->ToOql();
 
-			$sRawId = $sOqlQuery;
+			if (count($aModifierProperties))
+			{
+				array_multisort($aModifierProperties);
+				$sModifierProperties = json_encode($aModifierProperties);
+			}
+			else
+			{
+				$sModifierProperties = '';
+			}
+
+			$sRawId = $sOqlQuery.$sModifierProperties;
 			if (!is_null($aAttToLoad))
 			{
 				foreach($aAttToLoad as $sAlias => $aAttributes)
@@ -2024,12 +2038,10 @@ abstract class MetaModel
 
 		if (!isset($oSelect))
 		{
-			$aClassAliases = array();
-			$aTableAliases = array();
-			$oQBExpr = new QueryBuilderExpressions(array(), $oFilter->GetCriteria());
+			$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
 
 			$oKPI = new ExecutionKPI();
-			$oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, $aAttToLoad, array(), true /* main query */);
+			$oSelect = self::MakeQuery($oBuild, $oFilter, $aAttToLoad, array(), true /* main query */);
 			$oSelect->SetSourceOQL($sOqlQuery);
 			$oKPI->ComputeStats('MakeQuery (select)', $sOqlQuery);
 
@@ -2138,12 +2150,33 @@ abstract class MetaModel
 		}
 	}
 
+	protected static function MakeModifierProperties($oFilter)
+	{
+		// Compute query modifiers properties (can be set in the search itself, by the context, etc.)
+		//
+		$aModifierProperties = array();
+		foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
+		{
+			// Lowest precedence: the application context
+			$aPluginProps = ApplicationContext::GetPluginProperties($sPluginClass);
+			// Highest precedence: programmatically specified (or OQL)
+			foreach($oFilter->GetModifierProperties($sPluginClass) as $sProp => $value)
+			{
+				$aPluginProps[$sProp] = $value;
+			}
+			if (count($aPluginProps) > 0)
+			{
+				$aModifierProperties[$sPluginClass] = $aPluginProps;
+			}
+		}
+		return $aModifierProperties;
+	}
+	
 	public static function MakeDeleteQuery(DBObjectSearch $oFilter, $aArgs = array())
 	{
-		$aClassAliases = array();
-		$aTableAliases = array();
-		$oQBExpr = new QueryBuilderExpressions(array(), $oFilter->GetCriteria());
-		$oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, null, array(), true /* main query */);
+		$aModifierProperties = self::MakeModifierProperties($oFilter);
+		$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
+		$oSelect = self::MakeQuery($oBuild, $oFilter, null, array(), true /* main query */);
 		$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
 		return $oSelect->RenderDelete($aScalarArgs);
 	}
@@ -2151,26 +2184,21 @@ abstract class MetaModel
 	public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues, $aArgs = array())
 	{
 		// $aValues is an array of $sAttCode => $value
-		$aClassAliases = array();
-		$aTableAliases = array();
-		$oQBExpr = new QueryBuilderExpressions(array(), $oFilter->GetCriteria());
-		$oSelect = self::MakeQuery($oFilter->GetSelectedClasses(), $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, null, $aValues, true /* main query */);
+		$aModifierProperties = self::MakeModifierProperties($oFilter);
+		$oBuild = new QueryBuilderContext($oFilter, $aModifierProperties);
+		$oSelect = self::MakeQuery($oBuild, $oFilter, null, $aValues, true /* main query */);
 		$aScalarArgs = array_merge(self::PrepareQueryArguments($aArgs), $oFilter->GetInternalParams());
 		return $oSelect->RenderUpdate($aScalarArgs);
 	}
 
-	private static function MakeQuery($aSelectedClasses, &$oQBExpr, &$aClassAliases, &$aTableAliases, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQuery = false)
+	private static function MakeQuery(&$oBuild, DBObjectSearch $oFilter, $aAttToLoad = null, $aValues = array(), $bIsMainQuery = false)
 	{
 		// Note: query class might be different than the class of the filter
 		// -> this occurs when we are linking our class to an external class (referenced by, or pointing to)
 		$sClass = $oFilter->GetFirstJoinedClass();
 		$sClassAlias = $oFilter->GetFirstJoinedClassAlias();
 
-		$bIsOnQueriedClass = array_key_exists($sClassAlias, $aSelectedClasses);
-		if ($bIsOnQueriedClass)
-		{
-			$aClassAliases = array_merge($aClassAliases, $oFilter->GetJoinedClasses());
-		}
+		$bIsOnQueriedClass = array_key_exists($sClassAlias, $oBuild->GetRootFilter()->GetSelectedClasses());
 
 		self::DbgTrace("Entering: ".$oFilter->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY"));
 
@@ -2180,7 +2208,7 @@ abstract class MetaModel
 		if ($bIsOnQueriedClass)
 		{
 			// default to the whole list of attributes + the very std id/finalclass
-			$oQBExpr->AddSelect($sClassAlias.'id', new FieldExpression('id', $sClassAlias));
+			$oBuild->m_oQBExpressions->AddSelect($sClassAlias.'id', new FieldExpression('id', $sClassAlias));
 
 			if (is_null($aAttToLoad) || !array_key_exists($sClassAlias, $aAttToLoad))
 			{
@@ -2196,7 +2224,7 @@ abstract class MetaModel
 				
 				foreach ($oAttDef->GetSQLExpressions() as $sColId => $sSQLExpr)
 				{
-					$oQBExpr->AddSelect($sClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias));
+					$oBuild->m_oQBExpressions->AddSelect($sClassAlias.$sAttCode.$sColId, new FieldExpression($sAttCode.$sColId, $sClassAlias));
 				}
 			}
 
@@ -2216,14 +2244,14 @@ abstract class MetaModel
 				foreach($aFullText as $sFTNeedle)
 				{
 					$oNewCond = new BinaryExpression($oTextFields, 'LIKE', new ScalarExpression("%$sFTNeedle%"));
-					$oQBExpr->AddCondition($oNewCond);
+					$oBuild->m_oQBExpressions->AddCondition($oNewCond);
 				}
 			}
 		}
-//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oQBExpr, true)."</pre></p>\n";
+//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."</pre></p>\n";
 		$aExpectedAtts = array(); // array of (attcode => fieldexpression)
 //echo "<p>".__LINE__.": GetUnresolvedFields($sClassAlias, ...)</p>\n";
-		$oQBExpr->GetUnresolvedFields($sClassAlias, $aExpectedAtts);
+		$oBuild->m_oQBExpressions->GetUnresolvedFields($sClassAlias, $aExpectedAtts);
 
 		// Compute a clear view of required joins (from the current class)
 		// Build the list of external keys:
@@ -2260,9 +2288,9 @@ abstract class MetaModel
 		{
 			$aTranslateNow = array();
 			$aTranslateNow[$sClassAlias]['friendlyname'] = self::GetNameExpression($sClass, $sClassAlias);
-//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oQBExpr, true)."</pre></p>\n";
-			$oQBExpr->Translate($aTranslateNow, false);
-//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oQBExpr, true)."</pre></p>\n";
+//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."</pre></p>\n";
+			$oBuild->m_oQBExpressions->Translate($aTranslateNow, false);
+//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."</pre></p>\n";
 
 			$aNameSpec = self::GetNameSpec($sClass);
 			foreach($aNameSpec[1] as $i => $sAttCode)
@@ -2304,7 +2332,7 @@ abstract class MetaModel
 		self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()");
 		if (self::HasTable($sClass))
 		{
-			$oSelectBase = self::MakeQuerySingleTable($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, $sClass, $aExtKeys, $aValues);
+			$oSelectBase = self::MakeQuerySingleTable($oBuild, $oFilter, $sClass, $aExtKeys, $aValues);
 		}
 		else
 		{
@@ -2313,7 +2341,7 @@ abstract class MetaModel
 			// As the join will not filter on the expected classes, we have to specify it explicitely
 			$sExpectedClasses = implode("', '", self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL));
 			$oFinalClassRestriction = Expression::FromOQL("`$sClassAlias`.finalclass IN ('$sExpectedClasses')");
-			$oQBExpr->AddCondition($oFinalClassRestriction);
+			$oBuild->m_oQBExpressions->AddCondition($oFinalClassRestriction);
 		}
 
 		// Then we join the queries of the eventual parent classes (compound model)
@@ -2322,7 +2350,7 @@ abstract class MetaModel
 			if (!self::HasTable($sParentClass)) continue;
 //echo "<p>Parent class: $sParentClass... let's call MakeQuerySingleTable()</p>";
 			self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()");
-			$oSelectParentTable = self::MakeQuerySingleTable($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oFilter, $sParentClass, $aExtKeys, $aValues);
+			$oSelectParentTable = self::MakeQuerySingleTable($oBuild, $oFilter, $sParentClass, $aExtKeys, $aValues);
 			if (is_null($oSelectBase))
 			{
 				$oSelectBase = $oSelectParentTable;
@@ -2347,11 +2375,11 @@ abstract class MetaModel
 				//self::DbgTrace($oSelectForeign->RenderSelect(array()));
 
 				$sForeignClassAlias = $oForeignFilter->GetFirstJoinedClassAlias();
-				$oQBExpr->PushJoinField(new FieldExpression($sForeignKeyAttCode, $sForeignClassAlias));
+				$oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sForeignKeyAttCode, $sForeignClassAlias));
 
-				$oSelectForeign = self::MakeQuery($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oForeignFilter, $aAttToLoad);
+				$oSelectForeign = self::MakeQuery($oBuild, $oForeignFilter, $aAttToLoad);
 
-				$oJoinExpr = $oQBExpr->PopJoinField();
+				$oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField();
 				$sForeignKeyTable = $oJoinExpr->GetParent();
 				$sForeignKeyColumn = $oJoinExpr->GetName();
 				$oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable);
@@ -2390,8 +2418,8 @@ abstract class MetaModel
 		//
 		if ($bIsMainQuery)
 		{
-			$oSelectBase->SetCondition($oQBExpr->GetCondition());
-			$oSelectBase->SetSelect($oQBExpr->GetSelect());
+			$oSelectBase->SetCondition($oBuild->m_oQBExpressions->GetCondition());
+			$oSelectBase->SetSelect($oBuild->m_oQBExpressions->GetSelect());
 		}
 
 		// That's all... cross fingers and we'll get some working query
@@ -2402,7 +2430,7 @@ abstract class MetaModel
 		return $oSelectBase;
 	}
 
-	protected static function MakeQuerySingleTable($aSelectedClasses, &$oQBExpr, &$aClassAliases, &$aTableAliases, $oFilter, $sTableClass, $aExtKeys, $aValues)
+	protected static function MakeQuerySingleTable(&$oBuild, $oFilter, $sTableClass, $aExtKeys, $aValues)
 	{
 		// $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields))
 //echo "MAKEQUERY($sTableClass)-liste des clefs externes($sTableClass): <pre>".print_r($aExtKeys, true)."</pre><br/>\n";
@@ -2416,13 +2444,13 @@ abstract class MetaModel
 		$sTargetClass = $oFilter->GetFirstJoinedClass();
 		$sTargetAlias = $oFilter->GetFirstJoinedClassAlias();
 		$sTable = self::DBGetTable($sTableClass);
-		$sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable);
+		$sTableAlias = $oBuild->GenerateTableAlias($sTargetAlias.'_'.$sTable, $sTable);
 
 		$aTranslation = array();
 		$aExpectedAtts = array();
-		$oQBExpr->GetUnresolvedFields($sTargetAlias, $aExpectedAtts);
+		$oBuild->m_oQBExpressions->GetUnresolvedFields($sTargetAlias, $aExpectedAtts);
 		
-		$bIsOnQueriedClass = array_key_exists($sTargetAlias, $aSelectedClasses);
+		$bIsOnQueriedClass = array_key_exists($sTargetAlias, $oBuild->GetRootFilter()->GetSelectedClasses());
 		
 		self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToOQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY"));
 
@@ -2471,6 +2499,18 @@ abstract class MetaModel
 					$aUpdateValues[$sColumn] = $sValue;
 				}
 			}
+		}
+
+		// 2 - The SQL query, for this table only
+		//
+		$oSelectBase = new SQLQuery($sTable, $sTableAlias, array(), $bIsOnQueriedClass, $aUpdateValues, $oSelectedIdField);
+
+		// 3 - Resolve expected expressions (translation table: alias.attcode => table.column)
+		//
+		foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef)
+		{
+			// Skip this attribute if not defined in this table
+			if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue;
 
 			// Select...
 			//
@@ -2487,16 +2527,17 @@ abstract class MetaModel
 				{
 					if (array_key_exists($sAttCode, $aExpectedAtts))
 					{
-						$aTranslation[$sTargetAlias][$sAttCode.$sColId] = new FieldExpressionResolved($sSQLExpr, $sTableAlias);
+						$oFieldSQLExp = new FieldExpressionResolved($sSQLExpr, $sTableAlias);
+						foreach (MetaModel::EnumPlugins('iQueryModifier') as $sPluginClass => $oQueryModifier)
+						{
+							$oFieldSQLExp = $oQueryModifier->GetFieldExpression($oBuild, $sTargetClass, $sAttCode, $sColId, $oFieldSQLExp, $oSelectBase);
+						}
+						$aTranslation[$sTargetAlias][$sAttCode.$sColId] = $oFieldSQLExp;
 					}
 				}
 			}
 		}
 
-		// 3 - The whole stuff, for this table only
-		//
-		$oSelectBase = new SQLQuery($sTable, $sTableAlias, array(), $bIsOnQueriedClass, $aUpdateValues, $oSelectedIdField);
-
 //echo "MAKEQUERY- Classe $sTableClass<br/>\n";
 		// 4 - The external keys -> joins...
 		//
@@ -2516,7 +2557,7 @@ abstract class MetaModel
 					// The join was not explicitely defined in the filter,
 					// we need to do it now
 					$sKeyClass =  $oKeyAttDef->GetTargetClass();
-					$sKeyClassAlias = self::GenerateUniqueAlias($aClassAliases, $sKeyClass.'_'.$sKeyAttCode, $sKeyClass);
+					$sKeyClassAlias = $oBuild->GenerateClassAlias($sKeyClass.'_'.$sKeyAttCode, $sKeyClass);
 					$oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias);
 
 					$aAllPointingTo[$sKeyAttCode][TREE_OPERATOR_EQUALS][$sKeyClassAlias] = $oExtFilter;
@@ -2573,18 +2614,18 @@ abstract class MetaModel
 							}
 							// Translate prior to recursing
 							//
-//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oQBExpr, true)."\n".print_r($aTranslateNow, true)."</pre></p>\n";
-							$oQBExpr->Translate($aTranslateNow, false);
-//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oQBExpr, true)."</pre></p>\n";
+//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."\n".print_r($aTranslateNow, true)."</pre></p>\n";
+							$oBuild->m_oQBExpressions->Translate($aTranslateNow, false);
+//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."</pre></p>\n";
 		
 //echo "<p>External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()/p>\n";
 							self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()");
-							$oQBExpr->PushJoinField(new FieldExpression('id', $sKeyClassAlias));
+							$oBuild->m_oQBExpressions->PushJoinField(new FieldExpression('id', $sKeyClassAlias));
 			
-//echo "<p>Recursive MakeQuery ".__LINE__.": <pre>\n".print_r($aSelectedClasses, true)."</pre></p>\n";
-							$oSelectExtKey = self::MakeQuery($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oExtFilter);
+//echo "<p>Recursive MakeQuery ".__LINE__.": <pre>\n".print_r($oBuild->GetRootFilter()->GetSelectedClasses(), true)."</pre></p>\n";
+							$oSelectExtKey = self::MakeQuery($oBuild, $oExtFilter);
 			
-							$oJoinExpr = $oQBExpr->PopJoinField();
+							$oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField();
 							$sExternalKeyTable = $oJoinExpr->GetParent();
 							$sExternalKeyField = $oJoinExpr->GetName();
 			
@@ -2604,9 +2645,9 @@ abstract class MetaModel
 					}
 					elseif(self::$m_aAttribOrigins[$sKeyClass][$sKeyAttCode] == $sTableClass)
 					{
-						$oQBExpr->PushJoinField(new FieldExpression($sKeyAttCode, $sKeyClassAlias));
-						$oSelectExtKey = self::MakeQuery($aSelectedClasses, $oQBExpr, $aClassAliases, $aTableAliases, $oExtFilter);
-						$oJoinExpr = $oQBExpr->PopJoinField();
+						$oBuild->m_oQBExpressions->PushJoinField(new FieldExpression($sKeyAttCode, $sKeyClassAlias));
+						$oSelectExtKey = self::MakeQuery($oBuild, $oExtFilter);
+						$oJoinExpr = $oBuild->m_oQBExpressions->PopJoinField();
 //echo "MAKEQUERY-PopJoinField pour $sKeyAttCode, $sKeyClassAlias: <pre>".print_r($oJoinExpr, true)."</pre><br/>\n";
 						$sExternalKeyTable = $oJoinExpr->GetParent();
 						$sExternalKeyField = $oJoinExpr->GetName();
@@ -2625,9 +2666,9 @@ abstract class MetaModel
 
 		// Translate the selected columns
 		//
-//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oQBExpr, true)."</pre></p>\n";
-		$oQBExpr->Translate($aTranslation, false);
-//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oQBExpr, true)."</pre></p>\n";
+//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."</pre></p>\n";
+		$oBuild->m_oQBExpressions->Translate($aTranslation, false);
+//echo "<p>oQBExpr ".__LINE__.": <pre>\n".print_r($oBuild->m_oQBExpressions, true)."</pre></p>\n";
 
 		//MyHelpers::var_dump_html($oSelectBase->RenderSelect());
 		return $oSelectBase;
@@ -4660,7 +4701,7 @@ abstract class MetaModel
 	}
 
 	/**
-	 * Returns an array of classes implementing the given interface
+	 * Returns an array of classes=>instance implementing the given interface
 	 */
 	public static function EnumPlugins($sInterface)
 	{
@@ -4743,5 +4784,4 @@ MetaModel::RegisterZList("preview", array("description"=>"All attributes visible
 MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters"));
 MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters"));
 
-
-?>
+?>

+ 74 - 0
core/querybuildercontext.class.inc.php

@@ -0,0 +1,74 @@
+<?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
+
+/**
+ * Associated with the metamodel -> MakeQuery/MakeQuerySingleTable
+ *
+ * @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
+ */
+
+class QueryBuilderContext
+{
+	protected $m_oRootFilter;
+	protected $m_aClassAliases;
+	protected $m_aTableAliases;
+	protected $m_aModifierProperties;
+
+	public $m_oQBExpressions;
+
+	public function __construct($oFilter, $aModifierProperties)
+	{
+		$this->m_oRootFilter = $oFilter;
+		$this->m_oQBExpressions = new QueryBuilderExpressions($oFilter->GetCriteria());
+
+		$this->m_aClassAliases = $oFilter->GetJoinedClasses();
+		$this->m_aTableAliases = array();
+
+		$this->m_aModifierProperties = $aModifierProperties;
+	}
+
+	public function GetRootFilter()
+	{
+		return $this->m_oRootFilter;
+	}
+
+	public function GenerateTableAlias($sNewName, $sRealName)
+	{
+		return MetaModel::GenerateUniqueAlias($this->m_aTableAliases, $sNewName, $sRealName);
+	}
+
+	public function GenerateClassAlias($sNewName, $sRealName)
+	{
+		return MetaModel::GenerateUniqueAlias($this->m_aClassAliases, $sNewName, $sRealName);
+	}
+
+	public function GetModifierProperties($sPluginClass)
+	{
+		if (array_key_exists($sPluginClass, $this->m_aModifierProperties))
+		{
+			return $this->m_aModifierProperties[$sPluginClass];
+		}
+		else
+		{
+			return array();
+		}
+	}
+}
+
+?>

+ 33 - 0
core/querymodifier.class.inc.php

@@ -0,0 +1,33 @@
+<?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
+
+/**
+ * Interface iQueryModifier
+ * Defines the API to tweak queries (e.g. translate data on the fly) 
+ *
+ * @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 iQueryModifier
+{
+	public function __construct();
+
+	public function GetFieldExpression(QueryBuilderContext &$oBuild, $sClass, $sAttCode, $sColId, Expression $oFieldSQLExp, SQLQuery &$oSelect);
+}
+?>

+ 44 - 10
core/sqlquery.class.inc.php

@@ -68,6 +68,11 @@ class SQLQuery
 		$this->m_oSelectedIdField = $oSelectedIdField;
 	}
 
+	public function GetTableAlias()
+	{
+		return $this->m_sTableAlias;
+	}
+
 	public function SetSourceOQL($sOQL)
 	{
 		$this->m_SourceOQL = $sOQL;
@@ -101,11 +106,20 @@ class SQLQuery
 			{
 				$sJoinType = $aJoinInfo["jointype"];
 				$oSQLQuery = $aJoinInfo["select"];
-				$sLeftField = $aJoinInfo["leftfield"];
-				$sRightField = $aJoinInfo["rightfield"];
-				$sRightTableAlias = $aJoinInfo["righttablealias"];
+				if (isset($aJoinInfo["on_expression"]))
+				{
+					$sOnCondition = $aJoinInfo["on_expression"]->Render();
 
-				echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
+					echo "<li>Join '$sJoinType', ON ($sOnCondition)".$oSQLQuery->DisplayHtml()."</li>\n";
+				}
+				else
+				{
+					$sLeftField = $aJoinInfo["leftfield"];
+					$sRightField = $aJoinInfo["rightfield"];
+					$sRightTableAlias = $aJoinInfo["righttablealias"];
+	
+					echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
+				}
 			}
 			echo "</ul>";
 		}
@@ -196,6 +210,24 @@ class SQLQuery
 	{
 		return $this->AddJoin("left", $oSQLQuery, $sLeftField, $sRightField);
 	}
+
+	public function AddInnerJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression)
+	{
+		$this->m_aJoinSelects[] = array(
+			"jointype" => 'inner',
+			"select" => $oSQLQuery,
+			"on_expression" => $oOnExpression
+		);
+	}
+
+	public function AddLeftJoinEx(SQLQuery $oSQLQuery, Expression $oOnExpression)
+	{
+		$this->m_aJoinSelects[] = array(
+			"jointype" => 'left',
+			"select" => $oSQLQuery,
+			"on_expression" => $oOnExpression
+		);
+	}
 	
 	// Interface, build the SQL query
 	public function RenderDelete($aArgs = array())
@@ -412,8 +444,14 @@ class SQLQuery
 				break;
 			case "inner":
 			case "left":
-			// table or tablealias ???
-				$sJoinCond = "`$sCallerAlias`.`{$aJoinData['leftfield']}` = `$sRightTableAlias`.`{$aJoinData['rightfield']}`";
+				if (isset($aJoinData["on_expression"]))
+				{
+					$sJoinCond = $aJoinData["on_expression"]->Render();
+				}
+				else
+				{
+					$sJoinCond = "`$sCallerAlias`.`{$aJoinData['leftfield']}` = `$sRightTableAlias`.`{$aJoinData['rightfield']}`";
+				}
 				$aFrom[$this->m_sTableAlias] = array("jointype"=>$aJoinData['jointype'], "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
 				break;
 			case "inner_tree":
@@ -488,10 +526,6 @@ class SQLQuery
 		foreach ($this->m_aJoinSelects as $aJoinData)
 		{
 			$oRightSelect = $aJoinData["select"];
-//			$sJoinType = $aJoinData["jointype"];
-//			$sLeftField = $aJoinData["leftfield"];
-//			$sRightField = $aJoinData["rightfield"];
-//			$sRightTableAlias = $aJoinData["righttablealias"];
 
 			$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $aSelectedIdFields, $this->m_sTableAlias, $aJoinData);
 		}

+ 3 - 0
pages/csvimport.php

@@ -304,6 +304,9 @@ try
 			$sUserString .= ' (CSV)';
 			$oMyChange->Set("userinfo", $sUserString);
 			$iChangeId = $oMyChange->DBInsert();		
+
+			// Todo - simplify that when reworking the change tracking
+			CMDBObject::SetCurrentChange($oMyChange);
 		}
 	
 		$oBulk = new BulkChange(