// --
// Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
// --
// This software comes with ABSOLUTELY NO WARRANTY. For details, see
// the enclosed file COPYING for license information (GPL). If you
// did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
// --
"use strict";
var Core = Core || {};
Core.Agent = Core.Agent || {};
/**
* @namespace Core.Agent.Search
* @memberof Core.Agent
* @author OTRS AG
* @description
* This namespace contains the special module functions for the search.
*/
Core.Agent.Search = (function (TargetNS) {
var AJAXStopWordCheckRunning = false;
/**
* @name AdditionalAttributeSelectionRebuild
* @memberof Core.Agent.Search
* @function
* @returns {Boolean} Returns true.
* @description
* This function rebuilds attribute selection, only show available attributes.
*/
TargetNS.AdditionalAttributeSelectionRebuild = function () {
// get original selection with all possible fields and clone it
var $AttributeClone = $('#AttributeOrig option').clone(),
$AttributeSelection = $('#Attribute').empty();
// strip all already used attributes
$AttributeClone.each(function () {
if (!$('#SearchInsert label#Label' + $(this).attr('value')).length) {
$AttributeSelection.append($(this));
}
});
$AttributeSelection.trigger('redraw.InputField');
return true;
};
/**
* @name SearchAttributeAdd
* @memberof Core.Agent.Search
* @function
* @returns {Boolean} Returns false.
* @param {String} Attribute - Name of attribute to add.
* @description
* This function adds one attributes for search.
*/
TargetNS.SearchAttributeAdd = function (Attribute) {
var $Label = $('#SearchAttributesHidden label#Label' + Attribute);
if ($Label.length) {
$Label.prev().clone().appendTo('#SearchInsert');
$Label.clone().appendTo('#SearchInsert');
$Label.next().clone().appendTo('#SearchInsert')
// bind click function to remove button now
.find('.RemoveButton').on('click', function () {
var $Element = $(this).parent();
TargetNS.SearchAttributeRemove($Element);
// rebuild selection
TargetNS.AdditionalAttributeSelectionRebuild();
return false;
});
$('.CustomerAutoCompleteSimple').each(function() {
Core.Agent.CustomerSearch.InitSimple($(this));
});
// Register event for tree selection dialog
Core.UI.TreeSelection.InitTreeSelection();
// Modernize fields
Core.UI.InputFields.Activate($('#SearchInsert'));
// Initially display dynamic fields with TreeMode = 1 correctly
Core.UI.TreeSelection.InitDynamicFieldTreeViewRestore();
}
return false;
};
/**
* @name SearchAttributeRemove
* @memberof Core.Agent.Search
* @function
* @param {jQueryObject} $Element - The jQuery object of the form or any element within this form check.
* @description
* This function removes attributes from an element.
*/
TargetNS.SearchAttributeRemove = function ($Element) {
$Element.prev().prev().remove();
$Element.prev().remove();
$Element.remove();
};
/**
* @private
* @name SearchProfileDelete
* @memberof Core.Agent.Search
* @function
* @param {String} Profile - The profile name that will be delete.
* @param {String} SearchProfileAction - The action of search profile delete module.
* @description
* Delete a profile via an ajax requests.
*/
function SearchProfileDelete(Profile, SearchProfileAction) {
var Data = {
Action: SearchProfileAction,
Subaction: 'AJAXProfileDelete',
Profile: Profile
};
Core.AJAX.FunctionCall(
Core.Config.Get('CGIHandle'),
Data,
function () {}
);
}
/**
* @private
* @name CheckForSearchedValues
* @memberof Core.Agent.Search
* @function
* @returns {Boolean} False if no values were found, true if values where there.
* @description
* Checks if any values were entered in the search.
* If nothing at all exists, it alerts with translated:
* "Please enter at least one search value or * to find anything"
*/
function CheckForSearchedValues() {
// loop through the SerachForm labels
var SearchValueFlag = false;
$('#SearchForm label').each(function () {
var ElementName,
$Element,
$LabelElement = $(this),
$FieldElement = $LabelElement.next('.Field');
// those with ID's are used for searching
if ($(this).attr('id')) {
// substring "Label" (e.g. first five characters ) from the
// label id, use the remaining name as name string for accessing
// the form input's value
ElementName = $(this).attr('id').substring(5);
$Element = $('#SearchForm input[name=' + Core.App.EscapeSelector(ElementName) + ']');
// If there's no input element with the selected name
// find the next "select" element and use that one for checking
if (!$Element.length) {
$Element = $(this).next().find('select');
}
// Fix for bug#10845: make sure time slot fields with TimeInputFormat
// 'Input' set are being considered correctly, too. As this is only
// relevant for search type 'TimeSlot', we check for the first
// input type=text elment in the corresponding field element.
// All time field elements have to be filled in, but if only one
// is missing, we treat the whole field as invalid.
if ($FieldElement.find('input[name$="SearchType"]').val() === 'TimeSlot' && !$FieldElement.find('select').length) {
$Element = $FieldElement.find('input[type=text]').first();
}
if ($Element.length) {
if ($Element.val() && $Element.val() !== '') {
SearchValueFlag = true;
}
}
}
});
if (!SearchValueFlag) {
alert(Core.Language.Translate('Please enter at least one search value or * to find anything.'));
}
return SearchValueFlag;
}
/**
* @private
* @name ShowWaitingDialog
* @memberof Core.Agent.Search
* @function
* @description
* Shows waiting dialog until search screen is ready.
*/
function ShowWaitingDialog(){
Core.UI.Dialog.ShowContentDialog('<div class="Spacing Center"><span class="AJAXLoader" title="' + Core.Language.Translate('Loading...') + '"></span></div>', Core.Language.Translate('Loading...'), '10px', 'Center', true);
}
/**
* @function
* @private
* @param {Array} Search strings to check for stop words.
* @param {Function} Callback function to execute if stop words were found.
* @param {Function} Callback function to execute if no stop words were found.
* @return nothing
* @description Checks if the given search strings contain stop words.
*/
function AJAXStopWordCheck(SearchStrings, CallbackStopWordsFound, CallbackNoStopWordsFound) {
var StopWordCheckData = {
Action: 'AgentTicketSearch',
Subaction: 'AJAXStopWordCheck',
SearchStrings: SearchStrings
},
MIMEBaseElements = {
'MIMEBase_From': 1,
'MIMEBase_To': 1,
'MIMEBase_Cc': 1,
'MIMEBase_Subject': 1,
'MIMEBase_Body': 1
};
// Prevent multiple stop word checks
if (AJAXStopWordCheckRunning) {
return;
}
AJAXStopWordCheckRunning = true;
Core.Form.DisableForm($('#SearchForm'));
Core.AJAX.FunctionCall(
Core.Config.Get('CGIHandle'),
StopWordCheckData,
function (Result) {
var FoundStopWords = '';
$.each(Result.FoundStopWords, function (Key, StopWords) {
var TranslatedKey = Core.Config.Get('FieldTitle' + Key);
if (!StopWords.length) {
return;
}
if (!TranslatedKey) {
TranslatedKey = Key;
}
// Substring 'MIMEBase_' for clearer alert description.
if (MIMEBaseElements[TranslatedKey]) {
TranslatedKey = TranslatedKey.replace('MIMEBase_', '');
}
FoundStopWords += TranslatedKey + ': ' + StopWords.join(', ') + "\n";
});
AJAXStopWordCheckRunning = false;
Core.Form.EnableForm($('#SearchForm'));
if (FoundStopWords.length) {
CallbackStopWordsFound(FoundStopWords);
}
else {
CallbackNoStopWordsFound();
}
}
);
}
/**
* @private
* @name CheckSearchStringsForStopWords
* @memberof Core.Agent.Search
* @function
* @param {Function} Callback - function to execute, if no stop words were found.
* @description Checks if specific values of the search form contain stop words.
* If stop words are present, a warning will be displayed.
* If stop words are not present, the given callback will be executed.
*/
function CheckSearchStringsForStopWords(Callback) {
var SearchStrings = {},
SearchStringsFound = 0,
RelevantElementNames = {
'MIMEBase_From': 1,
'MIMEBase_To': 1,
'MIMEBase_Cc': 1,
'MIMEBase_Subject': 1,
'MIMEBase_Body': 1,
'Fulltext': 1
};
if (!Core.Config.Get('CheckSearchStringsForStopWords')) {
Callback();
return;
}
$('#SearchForm label').each(function () {
var ElementName,
$Element;
// those with ID's are used for searching
if ($(this).attr('id')) {
// substring "Label" (e.g. first five characters ) from the
// label id, use the remaining name as name string for accessing
// the form input's value
ElementName = $(this).attr('id').substring(5);
if (!RelevantElementNames[ElementName]) {
return;
}
$Element = $('#SearchForm input[name=' + ElementName + ']');
if ($Element.length) {
if ($Element.val() && $Element.val() !== '') {
SearchStrings[ElementName] = $Element.val();
SearchStringsFound = 1;
}
}
}
});
// Check if stop words are present.
if (!SearchStringsFound) {
Callback();
return;
}
AJAXStopWordCheck(
SearchStrings,
function (FoundStopWords) {
alert(Core.Language.Translate('Please remove the following words from your search as they cannot be searched for:') + "\n" + FoundStopWords);
},
Callback
);
}
/**
* @name OpenSearchDialog
* @memberof Core.Agent.Search
* @function
* @param {String} Action - Action which is used in framework right now.
* @param {String} Profile - Used profile name.
* @param {Boolean} EmptySearch
* @description
* This function open the search dialog after clicking on "search" button in nav bar.
*/
TargetNS.OpenSearchDialog = function (Action, Profile, EmptySearch) {
var Referrer = Core.Config.Get('Action'),
Data;
if (!Action) {
Action = 'AgentSearch';
}
Data = {
Action: Action,
Referrer: Referrer,
Profile: Profile,
EmptySearch: EmptySearch,
Subaction: 'AJAX'
};
ShowWaitingDialog();
Core.AJAX.FunctionCall(
Core.Config.Get('CGIHandle'),
Data,
function (HTML) {
// if the waiting dialog was cancelled, do not show the search
// dialog as well
if (!$('.Dialog:visible').length) {
return;
}
Core.UI.Dialog.ShowDialog({
HTML: HTML,
Title: Core.Language.Translate('Search'),
Modal: true,
CloseOnClickOutside: false,
CloseOnEscape: true,
PositionTop: '10px',
PositionLeft: 'Center',
AllowAutoGrow: true
});
// hide add template block
$('#SearchProfileAddBlock').hide();
// hide save changes in template block
$('#SaveProfile').parent().hide().prev().hide().prev().hide();
// search profile is selected
if ($('#SearchProfile').val() && $('#SearchProfile').val() !== 'last-search') {
// show delete button
$('#SearchProfileDelete').show();
// show profile link
$('#SearchProfileAsLink').show();
// show save changes in template block
$('#SaveProfile').parent().show().prev().show().prev().show();
// set SaveProfile to 0
$('#SaveProfile').prop('checked', false);
}
Core.UI.InputFields.Activate($('.Dialog:visible'));
// register add of attribute
$('#Attribute').on('change', function () {
var Attribute = $('#Attribute').val();
TargetNS.SearchAttributeAdd(Attribute);
TargetNS.AdditionalAttributeSelectionRebuild();
// Register event for tree selection dialog
$('.ShowTreeSelection').off('click').on('click', function () {
Core.UI.TreeSelection.ShowTreeSelection($(this));
return false;
});
return false;
});
// register return key
$('#SearchForm').off('keypress.FilterInput').on('keypress.FilterInput', function (Event) {
if ((Event.charCode || Event.keyCode) === 13) {
if (!CheckForSearchedValues()) {
return false;
}
else {
$('#SearchFormSubmit').trigger('click');
}
return false;
}
});
// register submit
$('#SearchFormSubmit').on('click', function () {
var ShownAttributes = [];
if ($('#SearchProfileAddAction, #SearchProfileAddName').is(':visible') && $('#SearchProfileAddName').val()) {
$('#SearchProfileAddAction').trigger('click');
}
// remember shown attributes
$('#SearchInsert label').each(function () {
if ($(this).attr('id')) {
ShownAttributes.push($(this).attr('id'));
}
});
$('#SearchForm #ShownAttributes').val(ShownAttributes.join(';'));
// Normal results mode will return HTML in the same window
if ($('#SearchForm #ResultForm').val() === 'Normal') {
if (!CheckForSearchedValues()) {
return false;
}
else {
CheckSearchStringsForStopWords(function () {
$('#SearchForm').submit();
return false;
});
}
}
else { // Print and CSV should open in a new window, no waiting dialog
$('#SearchForm').attr('target', 'SearchResultPage');
if (!CheckForSearchedValues()) {
return false;
}
else {
CheckSearchStringsForStopWords(function () {
$('#SearchForm').submit();
$('#SearchForm').attr('target', '');
});
}
}
return false;
});
Core.Form.Validate.Init();
Core.Form.Validate.SetSubmitFunction($('#SearchForm'), function (Form) {
Form.submit();
// Show only a waiting dialog for Normal results mode, because this result
// will return the HTML in the same window.
if ($('#SearchForm #ResultForm').val() === 'Normal') {
ShowWaitingDialog();
}
});
// load profile
$('#SearchProfile').on('change', function () {
var SearchProfile = $('#SearchProfile').val(),
SearchProfileEmptySearch = $('#EmptySearch').val(),
SearchProfileAction = $('#SearchAction').val();
TargetNS.OpenSearchDialog(SearchProfileAction, SearchProfile, SearchProfileEmptySearch);
return false;
});
// show add profile block or not
$('#SearchProfileNew').on('click', function (Event) {
$('#SearchProfileAddBlock').toggle();
$('#SearchProfileAddName').focus();
Event.preventDefault();
return false;
});
// add new profile
$('#SearchProfileAddAction').on('click', function () {
var ProfileName, $Element1;
// get name
ProfileName = $('#SearchProfileAddName').val();
// check name
if (!ProfileName.length || ProfileName.length < 2) {
return;
}
// add name to profile selection
$Element1 = $('#SearchProfile').children().first().clone();
$Element1.text(ProfileName);
$Element1.attr('value', ProfileName);
$Element1.prop('selected', true);
$('#SearchProfile').append($Element1).trigger('redraw.InputField');
// set input box to empty
$('#SearchProfileAddName').val('');
// hide add template block
$('#SearchProfileAddBlock').hide();
// hide save changes in template block
$('#SaveProfile').parent().hide().prev().hide().prev().hide();
// set SaveProfile to 1
$('#SaveProfile').prop('checked', true);
// show delete button
$('#SearchProfileDelete').show();
// show profile link
$('#SearchProfileAsLink').show();
});
// direct link to profile
$('#SearchProfileAsLink').on('click', function () {
var SearchProfile = $('#SearchProfile').val(),
SearchProfileAction = $('#SearchAction').val();
window.location.href = Core.Config.Get('Baselink') + 'Action=' + SearchProfileAction +
';Subaction=Search;TakeLastSearch=1;SaveProfile=1;Profile=' + encodeURIComponent(SearchProfile);
return false;
});
// delete profile
$('#SearchProfileDelete').on('click', function (Event) {
var SearchProfileAction = $('#SearchAction').val();
// strip all already used attributes
$('#SearchProfile').find('option:selected').each(function () {
if ($(this).attr('value') !== 'last-search') {
// rebuild attributes
$('#SearchInsert').text('');
// remove remote
SearchProfileDelete($(this).val(), SearchProfileAction);
// remove local
$(this).remove();
// show fulltext
TargetNS.SearchAttributeAdd('Fulltext');
// rebuild selection
TargetNS.AdditionalAttributeSelectionRebuild();
}
});
$('#SearchProfile').trigger('change');
if ($('#SearchProfile').val() && $('#SearchProfile').val() === 'last-search') {
// hide delete link
$('#SearchProfileDelete').hide();
// show profile link
$('#SearchProfileAsLink').hide();
}
Event.preventDefault();
return false;
});
window.setTimeout(function (){
TargetNS.AddSearchAttributes();
TargetNS.AdditionalAttributeSelectionRebuild();
}, 0);
}, 'html'
);
};
/**
* @function
* @return nothing
* @description Inits toolbar fulltext search.
*/
TargetNS.InitToolbarFulltextSearch = function () {
// register return key
$('#ToolBar li.Extended.SearchFulltext form[name="SearchFulltext"]').off('keypress.FilterInput').on('keypress.FilterInput', function (Event) {
var SearchString;
if ((Event.charCode || Event.keyCode) === 13) {
SearchString = $('#Fulltext').val();
if (!SearchString.length || !Core.Config.Get('CheckSearchStringsForStopWords')) {
return true;
}
AJAXStopWordCheck(
{ Fulltext: SearchString },
function (FoundStopWords) {
alert(Core.Language.Translate('Please remove the following words from your search as they cannot be searched for:') + "\n" + FoundStopWords);
},
function () {
$('#ToolBar li.Extended.SearchFulltext form[name="SearchFulltext"]').submit();
}
);
return false;
}
});
};
/**
* @name AddSearchAttributes
* @memberof Core.Agent.Search
* @function
* @description
* This function determines and adds attributes for using in filter.
*/
TargetNS.AddSearchAttributes = function () {
var i,
SearchAttributes = Core.Config.Get('SearchAttributes');
if (typeof SearchAttributes !== 'undefined' && SearchAttributes.length > 0) {
for (i = 0; i < SearchAttributes.length; i++) {
Core.Agent.Search.SearchAttributeAdd(SearchAttributes[i]);
}
}
};
/**
* @name Init
* @memberof Core.Agent.Search
* @function
* @description
* This function opens search dialog only on empty page
* (when it opens via '...Action=AgentTicketSearch' from location bar).
*/
TargetNS.Init = function () {
var NonAJAXSearch = Core.Config.Get('NonAJAXSearch');
if (typeof NonAJAXSearch !== 'undefined' && parseInt(NonAJAXSearch, 10) === 1) {
Core.Agent.Search.OpenSearchDialog(Core.Config.Get('Action'), Core.Config.Get('Profile'));
}
};
Core.Init.RegisterNamespace(TargetNS, 'APP_MODULE');
return TargetNS;
}(Core.Agent.Search || {}));