Browse Source

#803: template placeholders are now built on demand. Yes !!

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3498 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 10 years ago
parent
commit
3b81a3919d
3 changed files with 203 additions and 77 deletions
  1. 98 0
      core/attributedef.class.inc.php
  2. 71 74
      core/dbobject.class.php
  3. 34 3
      core/metamodel.class.php

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

@@ -487,6 +487,35 @@ abstract class AttributeDefinition
 	{
 		return $this->GetAsHTML($sValue, $oHostObject, $bLocalize);
 	}
+	
+	/**
+	 * Get various representations of the value, for insertion into a template (e.g. in Notifications)
+	 * @param $value mixed The current value of the field
+	 * @param $sVerb string The verb specifying the representation of the value
+	 * @param $oHostObject DBObject The object
+	 * @param $bLocalize bool Whether or not to localize the value
+	 */
+	public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
+	{
+		if ($this->IsScalar())
+		{
+			switch ($sVerb)
+			{
+				case '':
+				return $value;
+				
+				case 'html':
+				return $this->GetAsHtml($value, $oHostObject, $bLocalize);
+				
+				case 'label':
+				return $this->GetEditValue($value);
+				
+				default:
+				throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
+			}
+		}
+		return null;
+	}
 
 	public function GetAllowedValues($aArgs = array(), $sContains = '')
 	{
@@ -731,6 +760,46 @@ class AttributeLinkedSet extends AttributeDefinition
 		return $sRes;
 	}
 
+	/**
+	 * Get various representations of the value, for insertion into a template (e.g. in Notifications)
+	 * @param $value mixed The current value of the field
+	 * @param $sVerb string The verb specifying the representation of the value
+	 * @param $oHostObject DBObject The object
+	 * @param $bLocalize bool Whether or not to localize the value
+	 */
+	public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
+	{
+		$sRemoteName = $this->IsIndirect() ? $this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname';
+		
+		$oLinkSet = clone $value; // Workaround/Safety net for Trac #887
+		$iLimit = MetaModel::GetConfig()->Get('max_linkset_output');
+		if ($iLimit > 0)
+		{
+			$oLinkSet->SetLimit($iLimit);
+		}
+		$aNames = $oLinkSet->GetColumnAsArray($sRemoteName);
+		if ($iLimit > 0)
+		{
+			$iTotal = $oLinkSet->Count();
+			if ($iTotal > count($aNames))
+			{
+				$aNames[] = '... '.Dict::Format('UI:TruncatedResults', count($aNames), $iTotal);
+			}
+		}
+		
+		switch($sVerb)
+		{
+			case '':
+			return implode("\n", $aNames);
+					
+			case 'html':
+			return '<ul><li>'.implode("</li><li>", $aNames).'</li></ul>';
+			
+			default:
+			throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));	
+		}
+	}
+
 	public function DuplicatesAllowed() {return false;} // No duplicates for 1:n links, never
 
 	public function GetImportColumns()
@@ -2094,6 +2163,35 @@ class AttributeCaseLog extends AttributeLongText
 		}
 	}
 
+
+	/**
+	 * Get various representations of the value, for insertion into a template (e.g. in Notifications)
+	 * @param $value mixed The current value of the field
+	 * @param $sVerb string The verb specifying the representation of the value
+	 * @param $oHostObject DBObject The object
+	 * @param $bLocalize bool Whether or not to localize the value
+	 */
+	public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
+	{
+		switch($sVerb)
+		{
+			case '':
+			return $value->GetText();
+			
+			case 'head':
+			return $value->GetLatestEntry();
+
+			case 'head_html':
+			return '<div class="caselog_entry">'.str_replace( array( "\r\n", "\n", "\r"), "<br/>", htmlentities($value->GetLatestEntry(), ENT_QUOTES, 'UTF-8')).'</div>';
+			
+			case 'html':
+			return $value->GetAsEmailHtml();
+	
+			default:
+			throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));	
+		}
+	}
+	
 	/**
 	 * Helper to get a value that will be JSON encoded
 	 * The operation is the opposite to FromJSONToValue	 

+ 71 - 74
core/dbobject.class.php

@@ -89,8 +89,6 @@ abstract class DBObject implements iDisplay
 	protected $m_aCheckIssues = null;
 	protected $m_aDeleteIssues = null;
 
-	protected $m_aAsArgs = null; // The current object as a standard argument (cache)
-
 	private $m_bFullyLoaded = false; // Compound objects can be partially loaded
 	private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode
 	protected $m_aModifiedAtt = array(); // list of (potentially) modified sAttCodes
@@ -413,7 +411,6 @@ abstract class DBObject implements iDisplay
 
 		// The object has changed, reset caches
 		$this->m_bCheckStatus = null;
-		$this->m_aAsArgs = null;
 
 		// Make sure we do not reload it anymore... before saving it
 		$this->RegisterAsDirty();
@@ -844,8 +841,6 @@ abstract class DBObject implements iDisplay
 			throw new CoreException("Changing the key ({$this->m_iKey} to $iNewKey) on an object (class {".get_class($this).") wich already exists in the Database");
 		}
 		$this->m_iKey = $iNewKey;
-		// Invalidate the argument cache
-		$this->m_aAsArgs = null;
 	}
 	/**
 	 * Get the icon representing this object
@@ -1490,8 +1485,6 @@ abstract class DBObject implements iDisplay
 		{
 			// Take the autonumber
 			$this->m_iKey = $iNewKey;
-			// Invalidate the argument cache
-			$this->m_aAsArgs = null;
 		}
 		return $this->m_iKey;
 	}
@@ -1574,9 +1567,6 @@ abstract class DBObject implements iDisplay
 		$this->DBWriteLinks();
 		$this->m_bIsInDB = true;
 		$this->m_bDirty = false;
-		
-		// Arg cache invalidated (in particular, it needs the object key -could be improved later)
-		$this->m_aAsArgs = null;
 
 		$this->AfterInsert();
 
@@ -2274,87 +2264,94 @@ abstract class DBObject implements iDisplay
 
 
 
-	/*
-	* Create query parameters (SELECT ... WHERE service = :this->service_id)
-	* to be used with the APIs DBObjectSearch/DBObjectSet
-	* 		
-	* Starting 2.0.2 the parameters are computed on demand, at the lowest level,
-	* in VariableExpression::Render()		
-	*/	
+   	/**
+	 * Create query parameters (SELECT ... WHERE service = :this->service_id)
+	 * to be used with the APIs DBObjectSearch/DBObjectSet
+	 * 		
+	 * Starting 2.0.2 the parameters are computed on demand, at the lowest level,
+	 * in VariableExpression::Render()		
+	 */	
 	public function ToArgsForQuery($sArgName = 'this')
 	{
 		return array($sArgName.'->object()' => $this);
 	}
 
-	/*
-	* Create template placeholders
-	* An improvement could be to compute the values on demand
-	* (i.e. interpret the template to determine the placeholders)		
-	*/
+	/**
+ 	 * Create template placeholders: now equivalent to ToArgsForQuery since the actual
+	 * template placeholders are computed on demand.	
+	 */
 	public function ToArgs($sArgName = 'this')
 	{
-		if (is_null($this->m_aAsArgs))
+		return $this->ToArgsForQuery($sArgName);
+	}
+
+	public function GetForTemplate($sPlaceholderAttCode)
+	{
+		$ret = null;
+		if (($iPos = strpos($sPlaceholderAttCode, '->')) !== false)
 		{
-			$this->m_aAsArgs = array();
+			$sExtKeyAttCode = substr($sPlaceholderAttCode, 0, $iPos);
+			$sRemoteAttCode = substr($sPlaceholderAttCode, $iPos + 2);
+			if (!MetaModel::IsValidAttCode(get_class($this), $sExtKeyAttCode))
+			{
+				throw new CoreException("Unknown attribute '$sExtKeyAttCode' for the class ".get_class($this));
+			}
+			
+			$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
+			if (!$oKeyAttDef instanceof AttributeExternalKey)
+			{
+				throw new CoreException("'$sExtKeyAttCode' is not an external key of the class ".get_class($this));
+			}
+			$sRemoteClass = $oKeyAttDef->GetTargetClass();
+			$oRemoteObj = MetaModel::GetObject($sRemoteClass, $this->GetStrict($sExtKeyAttCode), false);
+			if (is_null($oRemoteObj))
+			{
+				$ret = Dict::S('UI:UndefinedObject');
+			}
+			else
+			{
+				// Recurse
+				$ret  = $oRemoteObj->GetForTemplate($sRemoteAttCode);
+			}
 		}
-		if (!array_key_exists($sArgName, $this->m_aAsArgs))
+		else 
 		{
-			$oKPI = new ExecutionKPI();
-			$aScalarArgs = $this->ToArgsForQuery($sArgName);
-			$aScalarArgs[$sArgName] = $this->GetKey();
-			$aScalarArgs[$sArgName.'->id'] = $this->GetKey();
-			$aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink('iTopStandardURLMaker', false);
-			$aScalarArgs[$sArgName.'->hyperlink(portal)'] = $this->GetHyperlink('PortalURLMaker', false);
-			$aScalarArgs[$sArgName.'->name()'] = $this->GetName();
-
-			$sClass = get_class($this);
-			foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+			switch($sPlaceholderAttCode)
 			{
-				if ($oAttDef instanceof AttributeCaseLog)
+				case 'id':
+				$ret = $this->GetKey();
+				break;
+				
+				case 'hyperlink()':
+				$ret = $this->GetHyperlink('iTopStandardURLMaker', false);
+				break;
+
+				case 'hyperlink(portal)':
+				$ret = $this->GetHyperlink('PortalURLMaker', false);
+				break;
+				
+				case 'name()':
+				$ret = $this->GetName();
+				break;
+
+				default:
+				if (preg_match('/^([^(]+)\\((.+)\\)$/', $sPlaceholderAttCode, $aMatches))
 				{
-					$oCaseLog = $this->Get($sAttCode);
-					$aScalarArgs[$sArgName.'->'.$sAttCode] = $oCaseLog->GetText();
-					$sHead = $oCaseLog->GetLatestEntry();
-					$aScalarArgs[$sArgName.'->head('.$sAttCode.')'] = $sHead;
-					$aScalarArgs[$sArgName.'->head_html('.$sAttCode.')'] = '<div class="caselog_entry">'.str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sHead, ENT_QUOTES, 'UTF-8')).'</div>';
-					$aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $oCaseLog->GetAsEmailHtml();
+					$sVerb = $aMatches[1];
+					$sAttCode = $aMatches[2];
 				}
-				elseif ($oAttDef->IsScalar())
+				else
 				{
-					$aScalarArgs[$sArgName.'->'.$sAttCode] = $this->Get($sAttCode);
-					// #@# Note: This has been proven to be quite slow, this can slow down bulk load
-					$sAsHtml = $this->GetAsHtml($sAttCode);
-					$aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $sAsHtml;
-					$aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = $this->GetEditValue($sAttCode); // "Nice" display value, but without HTML tags and entities
-				}
-				elseif ($oAttDef->IsLinkSet())
-				{
-					$sRemoteName = $oAttDef->IsIndirect() ? $oAttDef->GetExtKeyToRemote().'_friendlyname' : 'friendlyname';
-
-					$oLinkSet = clone $this->Get($sAttCode); // Workaround/Safety net for Trac #887
-					$iLimit = MetaModel::GetConfig()->Get('max_linkset_output');
-					if ($iLimit > 0)
-					{
-						$oLinkSet->SetLimit($iLimit);
-					}
-					$aNames = $oLinkSet->GetColumnAsArray($sRemoteName);
-					if ($iLimit > 0)
-					{
-						$iTotal = $oLinkSet->Count();
-						if ($iTotal > count($aNames))
-						{
-							$aNames[] = '... '.Dict::Format('UI:TruncatedResults', count($aNames), $iTotal);
-						}
-					}
-					$sNames = implode("\n", $aNames);
-					$aScalarArgs[$sArgName.'->'.$sAttCode] = $sNames;
-					$aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = '<ul><li>'.implode("</li><li>", $aNames).'</li></ul>';
+					$sVerb = '';
+					$sAttCode = $sPlaceholderAttCode;
 				}
+				
+				$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+				$ret = $oAttDef->GetForTemplate($this->Get($sAttCode), $sVerb, $this);
 			}
-			$this->m_aAsArgs[$sArgName] = $aScalarArgs;
-			$oKPI->ComputeStats('ToArgs', get_class($this));
+
 		}
-		return $this->m_aAsArgs[$sArgName];
+		return $ret;
 	}
 
 	// To be optionaly overloaded

+ 34 - 3
core/metamodel.class.php

@@ -5420,7 +5420,7 @@ abstract class MetaModel
 	/**
 	 * Replaces all the parameters by the values passed in the hash array
 	 */
-	static public function ApplyParams($aInput, $aParams)
+	static public function ApplyParams($sInput, $aParams)
 	{
 		// Declare magic parameters
 		$aParams['APP_URL'] = utils::GetAbsoluteUrlAppRoot();
@@ -5431,12 +5431,43 @@ abstract class MetaModel
 		foreach($aParams as $sSearch => $replace)
 		{
 			// Some environment parameters are objects, we just need scalars
-			if (is_object($replace)) continue;
+			if (is_object($replace))
+			{
+				$iPos = strpos($sSearch, '->object()');
+				if ($iPos !== false)
+				{
+					// Expand the parameters for the object
+					$sName = substr($sSearch, 0, $iPos);
+					if (preg_match_all('/\\$'.$sName.'->([^\\$]+)\\$/', $sInput, $aMatches))
+					{
+						foreach($aMatches[1] as $sPlaceholderAttCode)
+						{
+							try
+							{
+								$sReplacement = $replace->GetForTemplate($sPlaceholderAttCode);
+								if ($sReplacement !== null)
+								{
+									$aReplacements[] = $sReplacement;
+									$aSearches[] = '$'.$sName.'->'.$sPlaceholderAttCode.'$';
+								}
+							}
+							catch(Exception $e)
+							{
+								// No replacement will occur
+							}
+						}
+					}
+				}
+				else
+				{
+					continue; // Ignore this non-scalar value
+				}
+			}
 
 			$aSearches[] = '$'.$sSearch.'$';
 			$aReplacements[] = (string) $replace;
 		}
-		return str_replace($aSearches, $aReplacements, $aInput);
+		return str_replace($aSearches, $aReplacements, $sInput);
 	}
 
 	/**