mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°7552 - Fix mentions not taking triggers filter into account
This commit is contained in:
@@ -22,12 +22,12 @@ const CombodoCKEditorFeeds = {
|
|||||||
})
|
})
|
||||||
.then(json => {
|
.then(json => {
|
||||||
// ckeditor mandatory data
|
// ckeditor mandatory data
|
||||||
json.data['search_data'].forEach(e => {
|
json.search_data.forEach(e => {
|
||||||
e['name'] = e['friendlyname'];
|
e['name'] = e['friendlyname'];
|
||||||
e['id'] = options['marker']+e['friendlyname'];
|
e['id'] = options['marker']+e['friendlyname'];
|
||||||
});
|
});
|
||||||
// return searched data
|
// return searched data
|
||||||
resolve( json.data['search_data']);
|
resolve( json.search_data);
|
||||||
});
|
});
|
||||||
|
|
||||||
}, options.throttle);
|
}, options.throttle);
|
||||||
|
|||||||
@@ -2334,121 +2334,13 @@ EOF
|
|||||||
$oPage->add("</fieldset></div>");
|
$oPage->add("</fieldset></div>");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// TODO 3.0.0: Move this to new ajax render controller?
|
/**
|
||||||
|
* @internal
|
||||||
|
* @deprecated 3.2.0 N°7552 Use object.search_for_mentions route instead
|
||||||
|
*/
|
||||||
case 'cke_mentions':
|
case 'cke_mentions':
|
||||||
$oPage->SetContentType('application/json');
|
$oController = new ObjectController();
|
||||||
$sMarker = utils::ReadParam('marker', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
|
$oPage = $oController->OperationSearchForMentions();
|
||||||
$sNeedle = utils::ReadParam('needle', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
|
|
||||||
$sHostClass = utils::ReadParam('host_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
|
||||||
$iHostId = (int)utils::ReadParam('host_id', 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER);
|
|
||||||
|
|
||||||
// Check parameters
|
|
||||||
if ($sMarker === '') {
|
|
||||||
throw new Exception('Invalid parameters, marker must be specified.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$aMentionsAllowedClasses = MetaModel::GetConfig()->Get('mentions.allowed_classes');
|
|
||||||
if (isset($aMentionsAllowedClasses[$sMarker]) === false) {
|
|
||||||
throw new Exception('Invalid marker "'.$sMarker.'"');
|
|
||||||
}
|
|
||||||
|
|
||||||
$aMatches = array();
|
|
||||||
if ($sNeedle !== '') {
|
|
||||||
// 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");
|
|
||||||
$aSearchParams = ['needle' => "%$sNeedle%"];
|
|
||||||
|
|
||||||
// Retrieve restricting scopes from triggers if any
|
|
||||||
if ((strlen($sHostClass) > 0) && ($iHostId > 0)) {
|
|
||||||
$oHostObj = MetaModel::GetObject($sHostClass, $iHostId);
|
|
||||||
$aSearchParams['this'] = $oHostObj;
|
|
||||||
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$sSearchMainClassName = $oSearch->GetClass();
|
|
||||||
$sSearchMainClassAlias = $oSearch->GetClassAlias();
|
|
||||||
|
|
||||||
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sSearchMainClassName);
|
|
||||||
|
|
||||||
// Add condition to filter on the friendlyname
|
|
||||||
$oSearch->AddConditionExpression(
|
|
||||||
new BinaryExpression(new FieldExpression('friendlyname', $sSearchMainClassAlias), 'LIKE', new VariableExpression('needle'))
|
|
||||||
);
|
|
||||||
|
|
||||||
$oSet = new DBObjectSet($oSearch, [], $aSearchParams);
|
|
||||||
// Optimize fields to load
|
|
||||||
$aObjectAttCodesToLoad = [];
|
|
||||||
if (MetaModel::IsValidAttCode($sSearchMainClassName, $sObjectImageAttCode)) {
|
|
||||||
$aObjectAttCodesToLoad[] = $sObjectImageAttCode;
|
|
||||||
}
|
|
||||||
$oSet->OptimizeColumnLoad([$oSearch->GetClassAlias() => $aObjectAttCodesToLoad]);
|
|
||||||
$oSet->SetLimit(MetaModel::GetConfig()->Get('max_autocomplete_results'));
|
|
||||||
// Note: We have to this manually because of a bug in DBSearch not checking the user prefs. by default.
|
|
||||||
$oSet->SetShowObsoleteData(utils::ShowObsoleteData());
|
|
||||||
|
|
||||||
while ($oObject = $oSet->Fetch()) {
|
|
||||||
// Note $oObject finalclass might be different than $sMentionedClass
|
|
||||||
$sObjectClass = get_class($oObject);
|
|
||||||
$iObjectId = $oObject->GetKey();
|
|
||||||
$aMatch = [
|
|
||||||
'class' => $sObjectClass,
|
|
||||||
'id' => $iObjectId,
|
|
||||||
'friendlyname' => $oObject->Get('friendlyname'),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Try to retrieve image for contact
|
|
||||||
if (!empty($sObjectImageAttCode)) {
|
|
||||||
/** @var \ormDocument $oImage */
|
|
||||||
$oImage = $oObject->Get($sObjectImageAttCode);
|
|
||||||
if (!$oImage->IsEmpty()) {
|
|
||||||
$aMatch['picture_style'] = "background-image: url('".$oImage->GetDisplayURL($sObjectClass, $iObjectId, $sObjectImageAttCode)."')";
|
|
||||||
$aMatch['initials'] = '';
|
|
||||||
} else {
|
|
||||||
// If no image found, fallback on initials
|
|
||||||
$aMatch['picture_style'] = '';
|
|
||||||
$aMatch['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oObject->Get('friendlyname')));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$aMatches[] = $aMatch;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$oPage->add(json_encode($aMatches));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'custom_fields_update':
|
case 'custom_fields_update':
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ class CKEditorHelper
|
|||||||
'minimumCharacters' => MetaModel::GetConfig()->Get('min_autocomplete_chars'),
|
'minimumCharacters' => MetaModel::GetConfig()->Get('min_autocomplete_chars'),
|
||||||
'feed_type' => 'ajax',
|
'feed_type' => 'ajax',
|
||||||
'feed_ajax_options' => [
|
'feed_ajax_options' => [
|
||||||
'url' => utils::GetAbsoluteUrlAppRoot(). "pages/ajax.render.php?route=object.search&object_class=$sMentionClass&oql=SELECT $sMentionClass&search=",
|
'url' => utils::GetAbsoluteUrlAppRoot(). "pages/ajax.render.php?route=object.search_for_mentions&marker=".urlencode($sMentionMarker)."&needle=",
|
||||||
'throttle' => 500,
|
'throttle' => 500,
|
||||||
'marker' => $sMentionMarker,
|
'marker' => $sMentionMarker,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -217,12 +217,23 @@ class CaseLogEntryForm extends UIContentBlock
|
|||||||
$this->oTextInput = new RichText();
|
$this->oTextInput = new RichText();
|
||||||
|
|
||||||
// Add the "host_class" to the mention endpoints so it can filter objects regarding the triggers
|
// Add the "host_class" to the mention endpoints so it can filter objects regarding the triggers
|
||||||
|
// Mind that `&needle=` must be ending the endpoint URL in order for the JS plugin to append the needle string
|
||||||
$aConfig = $this->oTextInput->GetConfig();
|
$aConfig = $this->oTextInput->GetConfig();
|
||||||
if (isset($aConfig['mentions'])) {
|
if (isset($aConfig['mention']['feeds'])) {
|
||||||
foreach ($aConfig['mentions'] as $iIdx => $aData) {
|
foreach ($aConfig['mention']['feeds'] as $iIdx => $aData) {
|
||||||
$sFeed = $aConfig['mentions'][$iIdx]['feed'];
|
$sFeed = $aConfig['mention']['feeds'][$iIdx]['feed_ajax_options']['url'];
|
||||||
|
|
||||||
|
// Remove existing "needle" parameter
|
||||||
|
$sFeed = str_replace('&needle=', '', $sFeed);
|
||||||
|
|
||||||
|
// Add new parameters
|
||||||
$sFeed = utils::AddParameterToUrl($sFeed, 'host_class', $this->GetObjectClass());
|
$sFeed = utils::AddParameterToUrl($sFeed, 'host_class', $this->GetObjectClass());
|
||||||
$aConfig['mentions'][$iIdx]['feed'] = utils::AddParameterToUrl($sFeed, 'host_id', $this->GetObjectId());
|
$sFeed = utils::AddParameterToUrl($sFeed, 'host_id', $this->GetObjectId());
|
||||||
|
|
||||||
|
// Re-append "needle" parameter
|
||||||
|
$sFeed = utils::AddParameterToUrl($sFeed, 'needle', '');
|
||||||
|
|
||||||
|
$aConfig['mention']['feeds'][$iIdx]['feed_ajax_options']['url'] = $sFeed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->oTextInput->SetConfig($aConfig);
|
$this->oTextInput->SetConfig($aConfig);
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ use Combodo\iTop\Controller\AbstractController;
|
|||||||
use Combodo\iTop\Service\Base\ObjectRepository;
|
use Combodo\iTop\Service\Base\ObjectRepository;
|
||||||
use Combodo\iTop\Service\Router\Router;
|
use Combodo\iTop\Service\Router\Router;
|
||||||
use CoreCannotSaveObjectException;
|
use CoreCannotSaveObjectException;
|
||||||
|
use DBObjectSearch;
|
||||||
|
use DBObjectSet;
|
||||||
|
use DBSearch;
|
||||||
|
use DBUnionSearch;
|
||||||
use DeleteException;
|
use DeleteException;
|
||||||
use Dict;
|
use Dict;
|
||||||
use Exception;
|
use Exception;
|
||||||
@@ -833,6 +837,95 @@ JS;
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function OperationSearchForMentions(): JsonPage
|
||||||
|
{
|
||||||
|
$oPage = new JsonPage();
|
||||||
|
$oPage->SetOutputDataOnly(true);
|
||||||
|
|
||||||
|
$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);
|
||||||
|
$iHostId = (int) utils::ReadParam('host_id', 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER);
|
||||||
|
|
||||||
|
// Check parameters
|
||||||
|
if (utils::IsNullOrEmptyString($sMarker)) {
|
||||||
|
throw new ApplicationException('Invalid parameters, marker must be specified.');
|
||||||
|
}
|
||||||
|
if (utils::IsNullOrEmptyString($sNeedle)) {
|
||||||
|
throw new ApplicationException('Invalid parameters, needle must be specified.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$aMentionsAllowedClasses = MetaModel::GetConfig()->Get('mentions.allowed_classes');
|
||||||
|
if (isset($aMentionsAllowedClasses[$sMarker]) === false) {
|
||||||
|
throw new ApplicationException('Invalid marker "'.$sMarker.'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
$aMatches = array();
|
||||||
|
// Retrieve mentioned class from marker
|
||||||
|
$sMentionedClass = $aMentionsAllowedClasses[$sMarker];
|
||||||
|
if (MetaModel::IsValidClass($sMentionedClass) === false) {
|
||||||
|
throw new ApplicationException('Invalid class "'.$sMentionedClass.'" for marker "'.$sMarker.'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base search used when no trigger configured
|
||||||
|
$oSearch = DBSearch::FromOQL("SELECT $sMentionedClass");
|
||||||
|
$aSearchParams = ['needle' => "%$sNeedle%"];
|
||||||
|
|
||||||
|
// Retrieve restricting scopes from triggers if any
|
||||||
|
if (utils::IsNotNullOrEmptyString($sHostClass) && ($iHostId > 0)) {
|
||||||
|
$oHostObj = MetaModel::GetObject($sHostClass, $iHostId);
|
||||||
|
$aSearchParams['this'] = $oHostObj;
|
||||||
|
|
||||||
|
$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 (utils::IsNullOrEmptyString($sTriggerMentionedOQL)) {
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sSearchMainClassName = $oSearch->GetClass();
|
||||||
|
$sSearchMainClassAlias = $oSearch->GetClassAlias();
|
||||||
|
|
||||||
|
$sObjectImageAttCode = MetaModel::GetImageAttributeCode($sSearchMainClassName);
|
||||||
|
|
||||||
|
|
||||||
|
// Optimize fields to load
|
||||||
|
$aObjectAttCodesToLoad = [];
|
||||||
|
if (MetaModel::IsValidAttCode($sSearchMainClassName, $sObjectImageAttCode)) {
|
||||||
|
$aObjectAttCodesToLoad[] = $sObjectImageAttCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aResult = ObjectRepository::SearchFromOql($sSearchMainClassName, $aObjectAttCodesToLoad, $oSearch->ToOQL(), $sNeedle, $oHostObj, MetaModel::GetConfig()->Get('max_autocomplete_results'));
|
||||||
|
|
||||||
|
return $oPage->SetData([
|
||||||
|
'search_data' => $aResult,
|
||||||
|
'success' => $aResult !== null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OperationGet.
|
* OperationGet.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -79,10 +79,12 @@ class ObjectRepository
|
|||||||
* @param string $sOql Oql expression
|
* @param string $sOql Oql expression
|
||||||
* @param string $sSearch Friendly name search string
|
* @param string $sSearch Friendly name search string
|
||||||
* @param DBObject|null $oThisObject This object reference for oql
|
* @param DBObject|null $oThisObject This object reference for oql
|
||||||
|
* @param int $iLimit Limit results to the $iLimit first elements
|
||||||
*
|
*
|
||||||
* @return array|null
|
* @return array|null
|
||||||
|
* @since 3.2.0 Add $iLimit parameter
|
||||||
*/
|
*/
|
||||||
public static function SearchFromOql(string $sObjectClass, array $aFieldsToLoad, string $sOql, string $sSearch, DBObject $oThisObject = null): ?array
|
public static function SearchFromOql(string $sObjectClass, array $aFieldsToLoad, string $sOql, string $sSearch, DBObject $oThisObject = null, int $iLimit = 0): ?array
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -94,6 +96,11 @@ class ObjectRepository
|
|||||||
// Create db set from db search
|
// Create db set from db search
|
||||||
$oDbObjectSet = new DBObjectSet($oDbObjectSearch, [], ['this' => $oThisObject]);
|
$oDbObjectSet = new DBObjectSet($oDbObjectSearch, [], ['this' => $oThisObject]);
|
||||||
|
|
||||||
|
// Limit results
|
||||||
|
if ($iLimit > 0) {
|
||||||
|
$oDbObjectSet->SetLimit($iLimit);
|
||||||
|
}
|
||||||
|
|
||||||
// return object array
|
// return object array
|
||||||
return ObjectRepository::DBSetToObjectArray($oDbObjectSet, $sObjectClass, $aFieldsToLoad);
|
return ObjectRepository::DBSetToObjectArray($oDbObjectSet, $sObjectClass, $aFieldsToLoad);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user