mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-23 02:28:44 +02:00
Internal: Implemented DBObject::ExecActions, enables scripting object preset/modifications
SVN:trunk[3991]
This commit is contained in:
@@ -502,6 +502,11 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
public function GetStrict($sAttCode)
|
||||
{
|
||||
if ($sAttCode == 'id')
|
||||
{
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
|
||||
if (!$oAttDef->LoadInObject())
|
||||
@@ -3063,5 +3068,355 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
return $sFingerprint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a set of scripted actions onto the current object
|
||||
* See ExecAction for the syntax and features of the scripted actions
|
||||
*
|
||||
* @param $aActions array of statements (e.g. "set(name, Made after $source->name$)")
|
||||
* @param $aSourceObjects Array of Alias => Context objects (Convention: some statements require the 'source' element
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ExecActions($aActions, $aSourceObjects)
|
||||
{
|
||||
foreach($aActions as $sAction)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (preg_match('/^(\S*)\s*\((.*)\)$/ms', $sAction, $aMatches)) // multiline and newline matched by a dot
|
||||
{
|
||||
$sVerb = trim($aMatches[1]);
|
||||
$sParams = $aMatches[2];
|
||||
|
||||
// the coma is the separator for the parameters
|
||||
// comas can be escaped: \,
|
||||
$sParams = str_replace(array("\\\\", "\\,"), array("__backslash__", "__coma__"), $sParams);
|
||||
$sParams = trim($sParams);
|
||||
|
||||
if (strlen($sParams) == 0)
|
||||
{
|
||||
$aParams = array();
|
||||
}
|
||||
else
|
||||
{
|
||||
$aParams = explode(',', $sParams);
|
||||
foreach ($aParams as &$sParam)
|
||||
{
|
||||
$sParam = str_replace(array("__backslash__", "__coma__"), array("\\", ","), $sParam);
|
||||
$sParam = trim($sParam);
|
||||
}
|
||||
}
|
||||
$this->ExecAction($sVerb, $aParams, $aSourceObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Invalid syntax");
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
throw new Exception('Action: '.$sAction.' - '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to copy an attribute between two objects (in memory)
|
||||
* Originally designed for ExecAction()
|
||||
*/
|
||||
public function CopyAttribute($oSourceObject, $sSourceAttCode, $sDestAttCode)
|
||||
{
|
||||
if ($sSourceAttCode == 'id')
|
||||
{
|
||||
$oSourceAttDef = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sDestAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sDestAttCode);
|
||||
}
|
||||
if (!MetaModel::IsValidAttCode(get_class($oSourceObject), $sSourceAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oSourceObject)."::".$sSourceAttCode);
|
||||
}
|
||||
|
||||
$oSourceAttDef = MetaModel::GetAttributeDef(get_class($oSourceObject), $sSourceAttCode);
|
||||
}
|
||||
if (is_object($oSourceAttDef) && $oSourceAttDef->IsLinkSet())
|
||||
{
|
||||
// The copy requires that we create a new object set (the semantic of DBObject::Set is unclear about link sets)
|
||||
$oDestSet = DBObjectSet::FromScratch($oSourceAttDef->GetLinkedClass());
|
||||
$oSourceSet = $oSourceObject->Get($sSourceAttCode);
|
||||
$oSourceSet->Rewind();
|
||||
while ($oSourceLink = $oSourceSet->Fetch())
|
||||
{
|
||||
// Clone the link
|
||||
$sLinkClass = get_class($oSourceLink);
|
||||
$oLinkClone = MetaModel::NewObject($sLinkClass);
|
||||
foreach(MetaModel::ListAttributeDefs($sLinkClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
// As of now, ignore other attribute (do not attempt to recurse!)
|
||||
if ($oAttDef->IsScalar())
|
||||
{
|
||||
$oLinkClone->Set($sAttCode, $oSourceLink->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
|
||||
// Not necessary - this will be handled by DBObject
|
||||
// $oLinkClone->Set($oSourceAttDef->GetExtKeyToMe(), 0);
|
||||
$oDestSet->AddObject($oLinkClone);
|
||||
}
|
||||
$this->Set($sDestAttCode, $oDestSet);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->Set($sDestAttCode, $oSourceObject->Get($sSourceAttCode));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a scripted action onto the current object
|
||||
* - clone (att1, att2, att3, ...)
|
||||
* - clone_scalars ()
|
||||
* - copy (source_att, dest_att)
|
||||
* - reset (att)
|
||||
* - nullify (att)
|
||||
* - set (att, value (placeholders $source->att$ or $current_date$, or $current_contact_id$, ...))
|
||||
* - append (att, value (placeholders $source->att$ or $current_date$, or $current_contact_id$, ...))
|
||||
* - add_to_list (source_key_att, dest_att)
|
||||
* - add_to_list (source_key_att, dest_att, lnk_att, lnk_att_value)
|
||||
* - apply_stimulus (stimulus)
|
||||
* - call_method (method_name)
|
||||
*
|
||||
* @param $sVerb string Any of the verb listed above (e.g. "set")
|
||||
* @param $aParams array of strings (e.g. array('name', 'copied from $source->name$')
|
||||
* @param $aSourceObjects Array of Alias => Context objects (Convention: some statements require the 'source' element
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ExecAction($sVerb, $aParams, $aSourceObjects)
|
||||
{
|
||||
switch($sVerb)
|
||||
{
|
||||
case 'clone':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
foreach($aParams as $sAttCode)
|
||||
{
|
||||
$this->CopyAttribute($oObjectToRead, $sAttCode, $sAttCode);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'clone_scalars':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if ($oAttDef->IsScalar())
|
||||
{
|
||||
$this->CopyAttribute($oObjectToRead, $sAttCode, $sAttCode);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'copy':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: source attribute');
|
||||
}
|
||||
$sSourceAttCode = $aParams[0];
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: target attribute');
|
||||
}
|
||||
$sDestAttCode = $aParams[1];
|
||||
$this->CopyAttribute($oObjectToRead, $sSourceAttCode, $sDestAttCode);
|
||||
break;
|
||||
|
||||
case 'reset':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$this->Set($sAttCode, $oAttDef->GetDefaultValue());
|
||||
break;
|
||||
|
||||
case 'nullify':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$this->Set($sAttCode, $oAttDef->GetNullValue());
|
||||
break;
|
||||
|
||||
case 'set':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: value to set');
|
||||
}
|
||||
$sRawValue = $aParams[1];
|
||||
$aContext = array();
|
||||
foreach ($aSourceObjects as $sAlias => $oObject)
|
||||
{
|
||||
$aContext = array_merge($aContext, $oObject->ToArgs($sAlias));
|
||||
}
|
||||
$aContext['current_contact_id'] = UserRights::GetContactId();
|
||||
$aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName();
|
||||
$aContext['current_date'] = date('Y-m-d');
|
||||
$aContext['current_time'] = date('H:i:s');
|
||||
$sValue = MetaModel::ApplyParams($sRawValue, $aContext);
|
||||
$this->Set($sAttCode, $sValue);
|
||||
break;
|
||||
|
||||
case 'append':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: target attribute');
|
||||
}
|
||||
$sAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sAttCode);
|
||||
}
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: value to append');
|
||||
}
|
||||
$sRawAddendum = $aParams[1];
|
||||
$aContext = array();
|
||||
foreach ($aSourceObjects as $sAlias => $oObject)
|
||||
{
|
||||
$aContext = array_merge($aContext, $oObject->ToArgs($sAlias));
|
||||
}
|
||||
$aContext['current_contact_id'] = UserRights::GetContactId();
|
||||
$aContext['current_contact_friendlyname'] = UserRights::GetUserFriendlyName();
|
||||
$aContext['current_date'] = date('Y-m-d');
|
||||
$aContext['current_time'] = date('H:i:s');
|
||||
$sAddendum = MetaModel::ApplyParams($sRawAddendum, $aContext);
|
||||
$this->Set($sAttCode, $this->Get($sAttCode).$sAddendum);
|
||||
break;
|
||||
|
||||
case 'add_to_list':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: source attribute');
|
||||
}
|
||||
$sSourceKeyAttCode = $aParams[0];
|
||||
if (!MetaModel::IsValidAttCode(get_class($oObjectToRead), $sSourceKeyAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oObjectToRead)."::".$sSourceKeyAttCode);
|
||||
}
|
||||
if (!array_key_exists(1, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #2: target attribute (link set)');
|
||||
}
|
||||
$sTargetListAttCode = $aParams[1]; // indirect !!!
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sTargetListAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($this)."::".$sTargetListAttCode);
|
||||
}
|
||||
if (isset($aParams[2]) && isset($aParams[3]))
|
||||
{
|
||||
$sRoleAttCode = $aParams[2];
|
||||
$sRoleValue = $aParams[3];
|
||||
}
|
||||
|
||||
$iObjKey = $oObjectToRead->Get($sSourceKeyAttCode);
|
||||
if ($iObjKey > 0)
|
||||
{
|
||||
$oLinkSet = $this->Get($sTargetListAttCode);
|
||||
|
||||
$oListAttDef = MetaModel::GetAttributeDef(get_class($this), $sTargetListAttCode);
|
||||
$oLnk = MetaModel::NewObject($oListAttDef->GetLinkedClass());
|
||||
$oLnk->Set($oListAttDef->GetExtKeyToRemote(), $iObjKey);
|
||||
if (isset($sRoleAttCode))
|
||||
{
|
||||
if (!MetaModel::IsValidAttCode(get_class($oLnk), $sRoleAttCode))
|
||||
{
|
||||
throw new Exception("Unknown attribute ".get_class($oLnk)."::".$sRoleAttCode);
|
||||
}
|
||||
$oLnk->Set($sRoleAttCode, $sRoleValue);
|
||||
}
|
||||
$oLinkSet->AddObject($oLnk);
|
||||
$this->Set($sTargetListAttCode, $oLinkSet);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'apply_stimulus':
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: stimulus');
|
||||
}
|
||||
$sStimulus = $aParams[0];
|
||||
if (!in_array($sStimulus, MetaModel::EnumStimuli(get_class($this))))
|
||||
{
|
||||
throw new Exception("Unknown stimulus ".get_class($this)."::".$sStimulus);
|
||||
}
|
||||
$this->ApplyStimulus($sStimulus);
|
||||
break;
|
||||
|
||||
case 'call_method':
|
||||
if (!array_key_exists('source', $aSourceObjects))
|
||||
{
|
||||
throw new Exception('Missing conventional "source" object');
|
||||
}
|
||||
$oObjectToRead = $aSourceObjects['source'];
|
||||
if (!array_key_exists(0, $aParams))
|
||||
{
|
||||
throw new Exception('Missing argument #1: method name');
|
||||
}
|
||||
$sMethod = $aParams[0];
|
||||
$aCallSpec = array($this, $sMethod);
|
||||
if (!is_callable($aCallSpec))
|
||||
{
|
||||
throw new Exception("Unknown method ".get_class($this)."::".$sMethod.'()');
|
||||
}
|
||||
// Note: $oObjectToRead has been preserved when adding $aSourceObjects, so as to remain backward compatible with methods having only 1 parameter ($oObjectToReadà
|
||||
call_user_func($aCallSpec, $oObjectToRead, $aSourceObjects);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Invalid verb");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4894,4 +4894,158 @@ class TestLinkSetRecording_1NAdd_Remove extends TestBizModel
|
||||
sort($aRet);
|
||||
return $aRet;
|
||||
}
|
||||
}
|
||||
|
||||
class TestExecActions extends TestBizModel
|
||||
{
|
||||
static public function GetName()
|
||||
{
|
||||
return 'Scripted actions API DBObject::ExecAction - syntax errors';
|
||||
}
|
||||
|
||||
static public function GetDescription()
|
||||
{
|
||||
return 'Check that wrong arguments are correclty reported';
|
||||
}
|
||||
|
||||
protected function DoExecute()
|
||||
{
|
||||
$oSource = new UserRequest();
|
||||
$oSource->Set('title', 'Houston!');
|
||||
$oSource->Set('description', 'Looks like we have a problem');
|
||||
|
||||
$oTarget = new Server();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Scenarii
|
||||
//
|
||||
$aScenarii = array(
|
||||
array(
|
||||
'action' => 'set',
|
||||
'error' => 'Action: set - Invalid syntax'
|
||||
),
|
||||
array(
|
||||
'action' => 'smurf()',
|
||||
'error' => 'Action: smurf() - Invalid verb'
|
||||
),
|
||||
array(
|
||||
'action' => ' smurf () ',
|
||||
'error' => 'Action: smurf () - Invalid syntax'
|
||||
),
|
||||
array(
|
||||
'action' => 'clone(some_att_code, another_one)',
|
||||
'error' => 'Action: clone(some_att_code, another_one) - Unknown attribute Server::some_att_code'
|
||||
),
|
||||
array(
|
||||
'action' => 'copy(toto, titi)',
|
||||
'error' => 'Action: copy(toto, titi) - Unknown attribute Server::titi'
|
||||
),
|
||||
array(
|
||||
'action' => 'copy(toto, name)',
|
||||
'error' => 'Action: copy(toto, name) - Unknown attribute UserRequest::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'copy()',
|
||||
'error' => 'Action: copy() - Missing argument #1: source attribute'
|
||||
),
|
||||
array(
|
||||
'action' => 'copy(title)',
|
||||
'error' => 'Action: copy(title) - Missing argument #2: target attribute'
|
||||
),
|
||||
array(
|
||||
'action' => 'set(toto)',
|
||||
'error' => 'Action: set(toto) - Unknown attribute Server::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'set(toto, something)',
|
||||
'error' => 'Action: set(toto, something) - Unknown attribute Server::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'set()',
|
||||
'error' => 'Action: set() - Missing argument #1: target attribute'
|
||||
),
|
||||
array(
|
||||
'action' => 'reset(toto)',
|
||||
'error' => 'Action: reset(toto) - Unknown attribute Server::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'reset()',
|
||||
'error' => 'Action: reset() - Missing argument #1: target attribute'
|
||||
),
|
||||
array(
|
||||
'action' => 'nullify(toto)',
|
||||
'error' => 'Action: nullify(toto) - Unknown attribute Server::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'nullify()',
|
||||
'error' => 'Action: nullify() - Missing argument #1: target attribute'
|
||||
),
|
||||
array(
|
||||
'action' => 'append(toto, something)',
|
||||
'error' => 'Action: append(toto, something) - Unknown attribute Server::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'append(name)',
|
||||
'error' => 'Action: append(name) - Missing argument #2: value to append'
|
||||
),
|
||||
array(
|
||||
'action' => 'append()',
|
||||
'error' => 'Action: append() - Missing argument #1: target attribute'
|
||||
),
|
||||
array(
|
||||
'action' => 'add_to_list(toto, titi)',
|
||||
'error' => 'Action: add_to_list(toto, titi) - Unknown attribute UserRequest::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'add_to_list(caller_id, titi)',
|
||||
'error' => 'Action: add_to_list(caller_id, titi) - Unknown attribute Server::titi'
|
||||
),
|
||||
array(
|
||||
'action' => 'add_to_list(caller_id)',
|
||||
'error' => 'Action: add_to_list(caller_id) - Missing argument #2: target attribute (link set)'
|
||||
),
|
||||
array(
|
||||
'action' => 'add_to_list()',
|
||||
'error' => 'Action: add_to_list() - Missing argument #1: source attribute'
|
||||
),
|
||||
array(
|
||||
'action' => 'apply_stimulus(toto)',
|
||||
'error' => 'Action: apply_stimulus(toto) - Unknown stimulus Server::toto'
|
||||
),
|
||||
array(
|
||||
'action' => 'apply_stimulus()',
|
||||
'error' => 'Action: apply_stimulus() - Missing argument #1: stimulus'
|
||||
),
|
||||
array(
|
||||
'action' => 'call_method(toto)',
|
||||
'error' => 'Action: call_method(toto) - Unknown method Server::toto()'
|
||||
),
|
||||
array(
|
||||
'action' => 'call_method()',
|
||||
'error' => 'Action: call_method() - Missing argument #1: method name'
|
||||
),
|
||||
);
|
||||
|
||||
foreach ($aScenarii as $aScenario)
|
||||
{
|
||||
echo "<h4>".htmlentities($aScenario['action'], ENT_QUOTES, 'UTF-8')."</h4>\n";
|
||||
$sMessage = '';
|
||||
try
|
||||
{
|
||||
$oTarget->ExecActions(array($aScenario['action']), array('source' => $oSource));
|
||||
$sMessage = 'Expecting an exception... none has been thrown!';
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
if ($e->getMessage() != $aScenario['error'])
|
||||
{
|
||||
$sMessage = 'Wrong message: expecting "'.$aScenario['error'].'" and got "'.$e->getMessage().'"';
|
||||
}
|
||||
}
|
||||
if ($sMessage !='')
|
||||
{
|
||||
throw new Exception($sMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user