diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index 96810b5b2..cfa71d118 100644
--- a/core/attributedef.class.inc.php
+++ b/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 '
';
+
+ 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 ''.str_replace( array( "\r\n", "\n", "\r"), "
", htmlentities($value->GetLatestEntry(), ENT_QUOTES, 'UTF-8')).'
';
+
+ 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
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 541034d28..034edacc3 100644
--- a/core/dbobject.class.php
+++ b/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))
- {
- $this->m_aAsArgs = array();
- }
- if (!array_key_exists($sArgName, $this->m_aAsArgs))
- {
- $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();
+ return $this->ToArgsForQuery($sArgName);
+ }
- $sClass = get_class($this);
- foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+ public function GetForTemplate($sPlaceholderAttCode)
+ {
+ $ret = null;
+ if (($iPos = strpos($sPlaceholderAttCode, '->')) !== false)
+ {
+ $sExtKeyAttCode = substr($sPlaceholderAttCode, 0, $iPos);
+ $sRemoteAttCode = substr($sPlaceholderAttCode, $iPos + 2);
+ if (!MetaModel::IsValidAttCode(get_class($this), $sExtKeyAttCode))
{
- if ($oAttDef instanceof AttributeCaseLog)
- {
- $oCaseLog = $this->Get($sAttCode);
- $aScalarArgs[$sArgName.'->'.$sAttCode] = $oCaseLog->GetText();
- $sHead = $oCaseLog->GetLatestEntry();
- $aScalarArgs[$sArgName.'->head('.$sAttCode.')'] = $sHead;
- $aScalarArgs[$sArgName.'->head_html('.$sAttCode.')'] = ''.str_replace(array("\r\n", "\n", "\r"), "
", htmlentities($sHead, ENT_QUOTES, 'UTF-8')).'
';
- $aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $oCaseLog->GetAsEmailHtml();
- }
- elseif ($oAttDef->IsScalar())
- {
- $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.')'] = '';
- }
+ 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);
}
- $this->m_aAsArgs[$sArgName] = $aScalarArgs;
- $oKPI->ComputeStats('ToArgs', get_class($this));
}
- return $this->m_aAsArgs[$sArgName];
+ else
+ {
+ switch($sPlaceholderAttCode)
+ {
+ 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))
+ {
+ $sVerb = $aMatches[1];
+ $sAttCode = $aMatches[2];
+ }
+ else
+ {
+ $sVerb = '';
+ $sAttCode = $sPlaceholderAttCode;
+ }
+
+ $oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+ $ret = $oAttDef->GetForTemplate($this->Get($sAttCode), $sVerb, $this);
+ }
+
+ }
+ return $ret;
}
// To be optionaly overloaded
diff --git a/core/metamodel.class.php b/core/metamodel.class.php
index 1142170ca..919b08ddc 100644
--- a/core/metamodel.class.php
+++ b/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);
}
/**