N°2875 - Invert TriggerOnObjectMention approach

- Trigger is now positioned on a specific "host" class (eg. Ticket)
- Trigger now has a "mentioned objects" filter which determines the scope of mentioned objects which will activate the trigger
This commit is contained in:
Molkobain
2021-10-19 11:59:52 +02:00
parent df49e9c3b5
commit 8f2b5ad8e2
4 changed files with 98 additions and 16 deletions

View File

@@ -3220,17 +3220,19 @@ abstract class DBObject implements iDisplay
foreach ($aMentionedIds as $sMentionedId) {
/** @var \DBObject $oMentionedObject */
$oMentionedObject = MetaModel::GetObject($sMentionedClass, $sMentionedId);
// Important: Here the "$this->object()$" placeholder is actually the mentioned object and not the current object. The current object can be used through the $source->object()$ placeholder.
// This is due to the current implementation of triggers, the events will only be visible on the object the trigger's OQL is based on... 😕
$aTriggerArgs = $this->ToArgs('source') + array('this->object()' => $oMentionedObject);
$aTriggerArgs = $this->ToArgs('this') + array('mentioned->object()' => $oMentionedObject);
$aParams = array('class_list' => MetaModel::EnumParentClasses($sMentionedClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"),
array(), $aParams);
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aParams);
while ($oTrigger = $oSet->Fetch())
{
/** @var \TriggerOnObjectMention $oTrigger */
try {
// Ensure to handle only mentioned object in the trigger's scope
if ($oTrigger->IsMentionedObjectInScope($oMentionedObject) === false) {
continue;
}
$oTrigger->DoActivate($aTriggerArgs);
}
catch (Exception $e) {

View File

@@ -239,7 +239,8 @@ abstract class TriggerOnObject extends Trigger
* @param $iObjectId
* @param array $aChanges
*
* @return bool
* @return bool True if the object of ID $iObjectId is within the scope of the OQL defined by the "filter" attribute
*
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
@@ -583,13 +584,45 @@ class TriggerOnObjectMention extends TriggerOnObject
);
MetaModel::Init_Params($aParams);
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeOQL("mentioned_filter", array("allowed_values" => null, "sql" => "mentioned_filter", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('details', array('description', 'context', 'target_class', 'filter', 'mentioned_filter', 'action_list')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('finalclass', 'target_class')); // Attributes to be displayed for a list
// Search criteria
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
}
/**
* @param \DBObject $oObject
*
* @return bool True if $oObject is within the scope of the OQL defined by the "mentioned_filter" attribute OR if no mentioned_filter defined. Otherwise, returns false.
*
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \MissingQueryArgument
* @throws \MySQLException
* @throws \MySQLHasGoneAwayException
* @throws \OQLException
*/
public function IsMentionedObjectInScope(DBObject $oObject)
{
$sFilter = trim($this->Get('mentioned_filter'));
if (strlen($sFilter) > 0)
{
$oSearch = DBObjectSearch::FromOQL($sFilter);
$oSearch->AddCondition('id', $oObject->GetKey(), '=');
$oSearch->AddCondition('finalclass', get_class($oObject), '=');
$oSet = new DBObjectSet($oSearch);
$bRet = $oSet->CountExceeds(0);
}
else
{
$bRet = true;
}
return $bRet;
}
}
/**

View File

@@ -2412,8 +2412,9 @@ EOF
// TODO 3.0.0: Move this to new ajax render controller?
case 'cke_mentions':
$oPage->SetContentType('application/json');
$sMarker = utils::ReadParam('marker', '', false, 'raw_data');
$sNeedle = utils::ReadParam('needle', '', false, 'raw_data');
$sMarker = utils::ReadParam('marker', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$sNeedle = utils::ReadParam('needle', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
$sHostClass = utils::ReadParam('host_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS);
// Check parameters
if($sMarker === '') {
@@ -2427,12 +2428,46 @@ EOF
$aMatches = array();
if ($sNeedle !== '') {
// Retrieve scope from marker
$sScope = $aMentionsAllowedClasses[$sMarker];
if (MetaModel::IsValidClass($sScope)) {
$sScope = "SELECT $sScope";
// Retrieve mentioned class from marker
$sMentionedClass = $aMentionsAllowedClasses[$sMarker];
if (MetaModel::IsValidClass($sMentionedClass) === false) {
throw new Exception('Invalid class "'.$sMentionedClass.'" for marker "'.$sMarker.'"');
}
// Base search used when no trigger configured
$oSearch = DBSearch::FromOQL("SELECT $sMentionedClass");
// Retrieve restricting scopes from triggers if any
if (strlen($sHostClass) > 0) {
$aTriggerMentionedSearches = [];
$aTriggerSetParams = array('class_list' => MetaModel::EnumParentClasses($sHostClass, ENUM_PARENT_CLASSES_ALL));
$oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aTriggerSetParams);
/** @var \TriggerOnObjectMention $oTrigger */
while ($oTrigger = $oTriggerSet->Fetch()) {
$sTriggerMentionedOQL = $oTrigger->Get('mentioned_filter');
// No filter on mentioned objects, don't restrict the scope at all, it can be any object of $sMentionedClass
if (strlen($sTriggerMentionedOQL) === 0) {
$aTriggerMentionedSearches = [$oSearch];
break;
}
$oTriggerMentionedSearch = DBSearch::FromOQL($sTriggerMentionedOQL);
$sTriggerMentionedClass = $oTriggerMentionedSearch->GetClass();
// Filter is not about the mentioned class, don't mind it
if (is_a($sMentionedClass, $sTriggerMentionedClass, true) === false) {
continue;
}
$aTriggerMentionedSearches[] = $oTriggerMentionedSearch;
}
if (count($aTriggerMentionedSearches) > 0) {
$oSearch = new DBUnionSearch($aTriggerMentionedSearches);
}
}
$oSearch = DBSearch::FromOQL($sScope);
$sSearchMainClassName = $oSearch->GetClass();
$sSearchMainClassAlias = $oSearch->GetClassAlias();
@@ -2451,7 +2486,7 @@ EOF
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
while ($oObject = $oSet->Fetch()) {
// Note $oObject finalclass might be different than $sTargetClass
// Note $oObject finalclass might be different than $sMentionedClass
$sObjectClass = get_class($oObject);
$iObjectId = $oObject->GetKey();
$aMatch = [

View File

@@ -11,6 +11,7 @@ use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\UIBlock;
use DBObject;
use MetaModel;
use utils;
/**
* Class CaseLogEntryForm
@@ -203,6 +204,17 @@ class CaseLogEntryForm extends UIContentBlock
protected function InitTextInput()
{
$this->oTextInput = new RichText();
// Add the "host_class" to the mention endpoints so it can filter objects regarding the triggers
$aConfig = $this->oTextInput->GetConfig();
if (isset($aConfig['mentions'])) {
foreach ($aConfig['mentions'] as $iIdx => $aData) {
$sFeed = $aConfig['mentions'][$iIdx]['feed'];
$aConfig['mentions'][$iIdx]['feed'] = utils::AddParameterToUrl($sFeed, 'host_class', $this->GetObjectClass());
}
}
$this->oTextInput->SetConfig($aConfig);
return $this;
}