Selaa lähdekoodia

Friendly names: improved the behavior. Now fully compliant with end users expectations (e.g. a list of contacts shows the friendly name of the persons and team, not only the attribute 'name', the search can be performed on the friendly name as well)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2165 a333f486-631f-4898-b8df-5754b55c2be0
romainq 12 vuotta sitten
vanhempi
commit
439d02667e
3 muutettua tiedostoa jossa 170 lisäystä ja 26 poistoa
  1. 23 0
      core/attributedef.class.inc.php
  2. 6 0
      core/expression.class.inc.php
  3. 141 26
      core/metamodel.class.php

+ 23 - 0
core/attributedef.class.inc.php

@@ -3988,6 +3988,29 @@ class AttributeFriendlyName extends AttributeComputedFieldVoid
 	{
 		return Str::pure2html((string)$sValue);
 	}
+
+	public function GetBasicFilterLooseOperator()
+	{
+		return "Contains";
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		$sQValue = CMDBSource::Quote($value);
+		switch ($sOpCode)
+		{
+		case '=':
+		case '!=':
+			return $this->GetSQLExpr()." $sOpCode $sQValue";
+		case 'Contains':
+			return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%");
+		case 'NotLike':
+			return $this->GetSQLExpr()." NOT LIKE $sQValue";
+		case 'Like':
+		default:
+			return $this->GetSQLExpr()." LIKE $sQValue";
+		}
+	} 
 }
 
 ?>

+ 6 - 0
core/expression.class.inc.php

@@ -458,6 +458,12 @@ class FieldExpression extends UnaryExpression
 			// Add a reference to the field
 			$aUnresolved[$this->m_sName] = $this;
 		}
+		elseif ($sAlias == '')
+		{
+			// An empty alias means "any alias"
+			// In such a case, the results are indexed differently
+			$aUnresolved[$this->m_sParent][$this->m_sName] = $this;
+		}
 	}
 
 	public function Translate($aTranslationData, $bMatchAll = true, $bMarkFieldsAsResolved = true)

+ 141 - 26
core/metamodel.class.php

@@ -426,16 +426,17 @@ abstract class MetaModel
 			return array('%1$s', array($nameRawSpec));
 		}
 	}
-	final static public function GetNameExpression($sClass, $sClassAlias)
+
+	/**
+	 * Get the friendly name expression for a given class
+	 */	 	
+	final static public function GetNameExpression($sClass)
 	{
 		$aNameSpec = self::GetNameSpec($sClass);
 		$sFormat = $aNameSpec[0];
 		$aAttributes = $aNameSpec[1];                                                     
 
 		$aPieces = preg_split('/%([0-9])\\$s/', $sFormat, -1, PREG_SPLIT_DELIM_CAPTURE);
-		//echo "<pre>\n";
-		//print_r($aPieces);
-		//echo "</pre>\n";
 		$aExpressions = array();
 		foreach($aPieces as $i => $sPiece)
 		{
@@ -447,8 +448,18 @@ abstract class MetaModel
 
 		      if (isset($aAttributes[$iReplacement]))
 		      {
-		      	$sAtt = $aAttributes[$iReplacement];
-					$aExpressions[] = new FieldExpression($sAtt, $sClassAlias);
+		      	$sAttCode = $aAttributes[$iReplacement];
+					$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
+					if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
+					{
+						$sKeyAttCode = $oAttDef->GetKeyAttCode();
+						$sClassOfAttribute = self::GetAttributeOrigin($sClass, $sKeyAttCode);
+					}
+					else
+					{
+			      	$sClassOfAttribute = self::GetAttributeOrigin($sClass, $sAttCode);
+			      }
+					$aExpressions[] = new FieldExpression($sAttCode, $sClassOfAttribute);
 				}
 			}
 			else
@@ -461,14 +472,67 @@ abstract class MetaModel
 				}
 			}
 		}
-		//echo "<pre>\n";
-		//print_r($aExpressions);
-		//echo "</pre>\n";
 
 		$oNameExpr = new CharConcatExpression($aExpressions);
 		return $oNameExpr;
 	}
 
+	/**
+	 *	Get the friendly name for the class and its subclasses (if finalclass = 'subclass' ...)
+	 *	Simplifies the final expression by grouping classes having the same name expression	 
+	 *	Used when querying a parent class 	 
+	*/
+	final static protected function GetExtendedNameExpression($sClass)
+	{
+		// 1st step - get all of the required expressions (instantiable classes)
+		//            and group them using their OQL representation
+		//
+		$aFNExpressions = array(); // signature => array('expression' => oExp, 'classes' => array of classes)
+		foreach (self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sSubClass)
+		{
+			if (self::IsAbstract($sSubClass)) continue;
+
+			$oSubClassName = self::GetNameExpression($sSubClass);
+			$sSignature = $oSubClassName->Render();
+			if (!array_key_exists($sSignature, $aFNExpressions))
+			{
+				$aFNExpressions[$sSignature] = array(
+					'expression' => $oSubClassName,
+					'classes' => array(),
+				);
+			}
+			$aFNExpressions[$sSignature]['classes'][] = $sSubClass;
+		}
+
+		// 2nd step - build the final name expression depending on the finalclass
+		//
+		if (count($aFNExpressions) == 1)
+		{
+			$aExpData = reset($aFNExpressions);
+			$oNameExpression = $aExpData['expression'];
+		}
+		else
+		{
+			$oNameExpression = null;
+			foreach ($aFNExpressions as $sSignature => $aExpData)
+			{
+				$oClassListExpr = ListExpression::FromScalars($aExpData['classes']);
+				$oClassExpr = new FieldExpression('finalclass', $sClass);
+				$oClassInList = new BinaryExpression($oClassExpr, 'IN', $oClassListExpr);
+
+				if (is_null($oNameExpression))
+				{
+					$oNameExpression = $aExpData['expression'];
+				}
+				else
+				{
+					$oNameExpression = new FunctionExpression('IF', array($oClassInList, $aExpData['expression'], $oNameExpression));
+				}
+			}
+		}
+		return $oNameExpression;
+	}
+
 	final static public function GetStateAttributeCode($sClass)
 	{
 		self::_check_subclass($sClass);	
@@ -1998,6 +2062,10 @@ abstract class MetaModel
 		try
 		{
 			$sRes = $oSelect->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount);
+			if ($oFilter->GetClassAlias() == 'itop')
+			{
+				echo $sRes."<br/>\n";
+			}
 		}
 		catch (MissingQueryArgument $e)
 		{
@@ -2360,31 +2428,69 @@ abstract class MetaModel
 			}
 		}
 
+		$aFNJoinAlias = array(); // array of (subclass => alias)
 		if (array_key_exists('friendlyname', $aExpectedAtts))
 		{
-			$aTranslateNow = array();
-			$aTranslateNow[$sClassAlias]['friendlyname'] = self::GetNameExpression($sClass, $sClassAlias);
-//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";
+			// To optimize: detect a restriction on child classes in the condition expression
+			//    e.g. SELECT FunctionalCI WHERE finalclass IN ('Server', 'VirtualMachine')
+			$oNameExpression = self::GetExtendedNameExpression($sClass);
 
-			$aNameSpec = self::GetNameSpec($sClass);
-			foreach($aNameSpec[1] as $i => $sAttCode)
+			$aNameFields = array();
+			$oNameExpression->GetUnresolvedFields('', $aNameFields);
+			$aTranslateNameFields = array();
+			foreach($aNameFields as $sSubClass => $aFields)
 			{
-				$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
-				if ($oAttDef->IsExternalKey())
+				foreach($aFields as $sAttCode => $oField)
 				{
-					$sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sAttCode];
-					$aExtKeys[$sKeyTableClass][$sAttCode] = array();
-				}				
-				elseif ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
-				{
-					$sKeyAttCode = $oAttDef->GetKeyAttCode();
-					$sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode];
-					$aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef;
+					$oAttDef = self::GetAttributeDef($sSubClass, $sAttCode);
+					if ($oAttDef->IsExternalKey())
+					{
+						$sClassOfAttribute = self::$m_aAttribOrigins[$sSubClass][$sAttCode];
+						$aExtKeys[$sClassOfAttribute][$sAttCode] = array();
+					}				
+					elseif ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
+					{
+						$sKeyAttCode = $oAttDef->GetKeyAttCode();
+						$sClassOfAttribute = self::$m_aAttribOrigins[$sSubClass][$sKeyAttCode];
+						$aExtKeys[$sClassOfAttribute][$sKeyAttCode][$sAttCode] = $oAttDef;
+					}
+					else
+					{
+						$sClassOfAttribute = self::GetAttributeOrigin($sSubClass, $sAttCode);
+					}
+
+					if (self::IsParentClass($sClassOfAttribute, $sClass))
+					{
+						// The attribute is part of the standard query
+						//
+						$sAliasForAttribute = $sClassAlias;
+					}
+					else
+					{
+						// The attribute will be available from an additional outer join
+						// For each subclass (table) one single join is enough
+						//
+						if (!array_key_exists($sClassOfAttribute, $aFNJoinAlias))
+						{
+							$sAliasForAttribute = $oBuild->GenerateClassAlias($sClassAlias.'_fn_'.$sClassOfAttribute, $sClassOfAttribute);
+							$aFNJoinAlias[$sClassOfAttribute] = $sAliasForAttribute;
+						}
+						else
+						{
+							$sAliasForAttribute = $aFNJoinAlias[$sClassOfAttribute];
+						}
+					}
+
+					$aTranslateNameFields[$sSubClass][$sAttCode] = new FieldExpression($sAttCode, $sAliasForAttribute);
 				}
 			}
+			$oNameExpression = $oNameExpression->Translate($aTranslateNameFields, false);
+
+			$aTranslateNow = array();
+			$aTranslateNow[$sClassAlias]['friendlyname'] = $oNameExpression;
+			$oBuild->m_oQBExpressions->Translate($aTranslateNow, false);
 		}
+
 		// Add the ext fields used in the select (eventually adds an external key)
 		foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
 		{
@@ -2490,6 +2596,15 @@ abstract class MetaModel
 			}
 		}
 
+		// Additional JOINS for Friendly names
+		//
+		foreach ($aFNJoinAlias as $sSubClass => $sSubClassAlias)
+		{
+			$oSubClassFilter = new DBObjectSearch($sSubClass, $sSubClassAlias);
+			$oSelectFN = self::MakeQuerySingleTable($oBuild, $oSubClassFilter, $sSubClass, $aExtKeys, array());
+			$oSelectBase->AddLeftJoin($oSelectFN, $sKeyField, self::DBGetKey($sSubClass));
+		}
+
 		// That's all... cross fingers and we'll get some working query
 
 		//MyHelpers::var_dump_html($oSelectBase, true);