diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 4ff6f9783..f09321fb5 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -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) { diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php index e605d55a0..b9c883fae 100644 --- a/core/trigger.class.inc.php +++ b/core/trigger.class.inc.php @@ -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; + } } /** diff --git a/pages/ajax.render.php b/pages/ajax.render.php index dd1801cbc..ab8236e49 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -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 = [ diff --git a/sources/application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php b/sources/application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php index 869d4edd9..9a7ca98d8 100644 --- a/sources/application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php +++ b/sources/application/UI/Base/Layout/ActivityPanel/CaseLogEntryForm/CaseLogEntryForm.php @@ -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; }