ticketsearch-includeinvalid.../Custom/Kernel/Modules/AgentTicketSearch.pm
2023-09-24 18:02:39 +02:00

2683 lines
104 KiB
Perl

# --
# OTOBO is a web-based ticketing system for service organisations.
# --
# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
# Copyright (C) 2019-2023 Rother OSS GmbH, https://otobo.de/
# --
# $origin: otobo - a077e914380d1a13d5aa31472ea687353b614622 - Kernel/Modules/AgentTicketSearch.pm
# --
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# --
package Kernel::Modules::AgentTicketSearch;
use strict;
use warnings;
use Kernel::System::VariableCheck qw(:all);
use Kernel::Language qw(Translatable);
our $ObjectManagerDisabled = 1;
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {%Param};
bless( $Self, $Type );
return $Self;
}
sub Run {
my ( $Self, %Param ) = @_;
my $Output;
# get needed objects
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
# get config data
$Self->{StartHit} = int( $ParamObject->GetParam( Param => 'StartHit' ) || 1 );
$Self->{SearchLimit} = $Config->{SearchLimit} || 500;
$Self->{SortBy} = $ParamObject->GetParam( Param => 'SortBy' )
|| $Config->{'SortBy::Default'}
|| 'Age';
$Self->{OrderBy} = $ParamObject->GetParam( Param => 'OrderBy' )
|| $Config->{'Order::Default'}
|| 'Down';
$Self->{Profile} = $ParamObject->GetParam( Param => 'Profile' ) || '';
$Self->{SaveProfile} = $ParamObject->GetParam( Param => 'SaveProfile' ) || '';
$Self->{TakeLastSearch} = $ParamObject->GetParam( Param => 'TakeLastSearch' ) || '';
$Self->{SelectTemplate} = $ParamObject->GetParam( Param => 'SelectTemplate' ) || '';
$Self->{EraseTemplate} = $ParamObject->GetParam( Param => 'EraseTemplate' ) || '';
# get list type
my $TreeView = 0;
if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) {
$TreeView = 1;
}
# get layout object
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
# check request
if ( $Self->{Subaction} eq 'OpenSearchDescriptionTicketNumber' ) {
my $Output = $LayoutObject->Output(
TemplateFile => 'AgentTicketSearchOpenSearchDescriptionTicketNumber',
Data => \%Param,
);
# TODO: maybe declare as UTF-8
return $LayoutObject->Attachment(
Filename => 'OpenSearchDescriptionTicketNumber.xml',
ContentType => 'application/opensearchdescription+xml',
Content => $Output,
Type => 'inline',
);
}
if ( $Self->{Subaction} eq 'OpenSearchDescriptionFulltext' ) {
my $Output = $LayoutObject->Output(
TemplateFile => 'AgentTicketSearchOpenSearchDescriptionFulltext',
Data => \%Param,
);
# TODO: maybe declare as UTF-8
return $LayoutObject->Attachment(
Filename => 'OpenSearchDescriptionFulltext.xml',
ContentType => 'application/opensearchdescription+xml',
Content => $Output,
Type => 'inline',
);
}
# Autocomplete is executed via AJAX request.
if ( $Self->{Subaction} eq 'AJAXAutocomplete' ) {
$LayoutObject->ChallengeTokenCheck();
my $Skip = $ParamObject->GetParam( Param => 'Skip' ) || '';
my $Search = $ParamObject->GetParam( Param => 'Term' ) || '';
my $Filter = $ParamObject->GetParam( Param => 'Filter' ) || '{}';
my $MaxResults = int( $ParamObject->GetParam( Param => 'MaxResults' ) || 20 );
# Remove leading and trailing spaces from search term.
$Search =~ s{ \A \s* ( [^\s]+ ) \s* \z }{$1}xms;
# Parse passed search filter.
my $SearchFilter = $Kernel::OM->Get('Kernel::System::JSON')->Decode(
Data => $Filter,
);
# Workaround, all auto completion requests get posted by UTF8 anyway.
# Convert any to 8bit string if application is not running in UTF8.
$Search = $Kernel::OM->Get('Kernel::System::Encode')->Convert2CharsetInternal(
Text => $Search,
From => 'utf-8',
);
my @TicketIDs;
# Search for tickets by:
# - Ticket Number
# - Ticket Title
if ($Search) {
@TicketIDs = $TicketObject->TicketSearch(
%{$SearchFilter},
TicketNumber => '%' . $Search . '%',
Limit => $MaxResults,
Result => 'ARRAY',
ArchiveFlags => ['n'],
UserID => $Self->{UserID},
);
if ( !@TicketIDs ) {
@TicketIDs = $TicketObject->TicketSearch(
%{$SearchFilter},
Title => '%' . $Search . '%',
Limit => $MaxResults,
Result => 'ARRAY',
ArchiveFlags => ['n'],
UserID => $Self->{UserID},
);
}
}
my @Results;
# Include additional ticket information in results.
TICKET:
for my $TicketID (@TicketIDs) {
next TICKET if !$TicketID;
next TICKET if $TicketID eq $Skip;
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
DynamicFields => 0,
UserID => $Self->{UserID},
);
next TICKET if !%Ticket;
push @Results, {
Key => $Ticket{TicketNumber},
Value => $Ticket{TicketNumber} . ' ' . $Ticket{Title},
};
}
my $JSON = $LayoutObject->JSONEncode(
Data => \@Results || [],
);
# Send JSON response.
return $LayoutObject->Attachment(
ContentType => 'application/json; charset=' . $LayoutObject->{Charset},
Content => $JSON || '',
Type => 'inline',
NoCache => 1,
);
}
# check request
if ( $ParamObject->GetParam( Param => 'SearchTemplate' ) && $Self->{Profile} ) {
my $Profile = $LayoutObject->LinkEncode( $Self->{Profile} );
return $LayoutObject->Redirect(
OP =>
"Action=AgentTicketSearch;Subaction=Search;TakeLastSearch=1;SaveProfile=1;Profile=$Profile"
);
}
# get single params
my %GetParam;
# get needed objects
my $SearchProfileObject = $Kernel::OM->Get('Kernel::System::SearchProfile');
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
my $BackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
# get dynamic field config for frontend module
my $DynamicFieldFilter = $Config->{DynamicField};
# get the dynamic fields for ticket object
my $DynamicField = $DynamicFieldObject->DynamicFieldListGet(
Valid => 1,
ObjectType => [ 'Ticket', 'Article' ],
FieldFilter => $DynamicFieldFilter || {},
);
# collect all searchable article field definitions and add the fields to the attributes array
my %ArticleSearchableFields = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleSearchableFieldsList();
# load profiles string params (press load profile)
if ( ( $Self->{Subaction} eq 'LoadProfile' && $Self->{Profile} ) || $Self->{TakeLastSearch} ) {
%GetParam = $SearchProfileObject->SearchProfileGet(
Base => 'TicketSearch',
Name => $Self->{Profile},
UserLogin => $Self->{UserLogin},
);
# convert attributes
if ( $GetParam{ShownAttributes} && ref $GetParam{ShownAttributes} eq 'ARRAY' ) {
$GetParam{ShownAttributes} = join ';', @{ $GetParam{ShownAttributes} };
}
}
# get search string params (get submitted params)
else {
for my $Key (
sort keys %ArticleSearchableFields,
qw(
TicketNumber Title CustomerID CustomerIDRaw CustomerUserLogin CustomerUserLoginRaw
CustomerUserID StateType Agent ResultForm TimeSearchType ChangeTimeSearchType
CloseTimeSearchType LastChangeTimeSearchType EscalationTimeSearchType PendingTimeSearchType
UseSubQueues ArticleTimeSearchType SearchInArchive Fulltext ShownAttributes
ArticleCreateTimePointFormat ArticleCreateTimePoint ArticleCreateTimePointStart
ArticleCreateTimeStart ArticleCreateTimeStartDay ArticleCreateTimeStartMonth
ArticleCreateTimeStartYear ArticleCreateTimeStop ArticleCreateTimeStopDay
ArticleCreateTimeStopMonth ArticleCreateTimeStopYear
TicketCreateTimePointFormat TicketCreateTimePoint
TicketCreateTimePointStart
TicketCreateTimeStart TicketCreateTimeStartDay TicketCreateTimeStartMonth
TicketCreateTimeStartYear
TicketCreateTimeStop TicketCreateTimeStopDay TicketCreateTimeStopMonth
TicketCreateTimeStopYear
TicketChangeTimePointFormat TicketChangeTimePoint
TicketChangeTimePointStart
TicketChangeTimeStart TicketChangeTimeStartDay TicketChangeTimeStartMonth
TicketChangeTimeStartYear
TicketChangeTimeStop TicketChangeTimeStopDay TicketChangeTimeStopMonth
TicketChangeTimeStopYear
TicketLastChangeTimePointFormat TicketLastChangeTimePoint
TicketLastChangeTimePointStart
TicketLastChangeTimeStart TicketLastChangeTimeStartDay TicketLastChangeTimeStartMonth
TicketLastChangeTimeStartYear
TicketLastChangeTimeStop TicketLastChangeTimeStopDay TicketLastChangeTimeStopMonth
TicketLastChangeTimeStopYear
TicketCloseTimePointFormat TicketCloseTimePoint
TicketCloseTimePointStart
TicketCloseTimeStart TicketCloseTimeStartDay TicketCloseTimeStartMonth
TicketCloseTimeStartYear
TicketCloseTimeStop TicketCloseTimeStopDay TicketCloseTimeStopMonth
TicketCloseTimeStopYear
TicketPendingTimePointFormat TicketPendingTimePoint
TicketPendingTimePointStart
TicketPendingTimeStart TicketPendingTimeStartDay TicketPendingTimeStartMonth
TicketPendingTimeStartYear
TicketPendingTimeStop TicketPendingTimeStopDay TicketPendingTimeStopMonth
TicketPendingTimeStopYear
TicketEscalationTimePointFormat TicketEscalationTimePoint
TicketEscalationTimePointStart
TicketEscalationTimeStart TicketEscalationTimeStartDay TicketEscalationTimeStartMonth
TicketEscalationTimeStartYear
TicketEscalationTimeStop TicketEscalationTimeStopDay TicketEscalationTimeStopMonth
TicketEscalationTimeStopYear
TicketCloseTimeNewerDate TicketCloseTimeOlderDate
FulltextES
)
)
{
# get search string params (get submitted params)
$GetParam{$Key} = $ParamObject->GetParam( Param => $Key );
# remove white space on the start and end
if ( $GetParam{$Key} ) {
$GetParam{$Key} =~ s/\s+$//g;
$GetParam{$Key} =~ s/^\s+//g;
}
}
# get array params
for my $Key (
qw(StateIDs States StateTypeIDs QueueIDs Queues PriorityIDs Priorities OwnerIDs
CreatedQueueIDs CreatedUserIDs WatchUserIDs ResponsibleIDs
TypeIDs Types ServiceIDs Services SLAIDs SLAs LockIDs Locks)
)
{
# get search array params (get submitted params)
my @Array = $ParamObject->GetArray( Param => $Key );
if (@Array) {
$GetParam{$Key} = \@Array;
}
}
# get Dynamic fields from param object
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{$DynamicField} ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
# get search field preferences
my $SearchFieldPreferences = $BackendObject->SearchFieldPreferences(
DynamicFieldConfig => $DynamicFieldConfig,
);
next DYNAMICFIELD if !IsArrayRefWithData($SearchFieldPreferences);
PREFERENCE:
for my $Preference ( @{$SearchFieldPreferences} ) {
# extract the dynamic field value from the web request
my $DynamicFieldValue = $BackendObject->SearchFieldValueGet(
DynamicFieldConfig => $DynamicFieldConfig,
ParamObject => $ParamObject,
ReturnProfileStructure => 1,
LayoutObject => $LayoutObject,
Type => $Preference->{Type},
);
# set the complete value structure in GetParam to store it later in the search profile
if ( IsHashRefWithData($DynamicFieldValue) ) {
%GetParam = ( %GetParam, %{$DynamicFieldValue} );
}
}
}
}
# get article create time option
if ( !$GetParam{ArticleTimeSearchType} ) {
$GetParam{'ArticleTimeSearchType::None'} = 1;
}
elsif ( $GetParam{ArticleTimeSearchType} eq 'TimePoint' ) {
$GetParam{'ArticleTimeSearchType::TimePoint'} = 1;
}
elsif ( $GetParam{ArticleTimeSearchType} eq 'TimeSlot' ) {
$GetParam{'ArticleTimeSearchType::TimeSlot'} = 1;
}
# get create time option
if ( !$GetParam{TimeSearchType} ) {
$GetParam{'TimeSearchType::None'} = 1;
}
elsif ( $GetParam{TimeSearchType} eq 'TimePoint' ) {
$GetParam{'TimeSearchType::TimePoint'} = 1;
}
elsif ( $GetParam{TimeSearchType} eq 'TimeSlot' ) {
$GetParam{'TimeSearchType::TimeSlot'} = 1;
}
# get change time option
if ( !$GetParam{ChangeTimeSearchType} ) {
$GetParam{'ChangeTimeSearchType::None'} = 1;
}
elsif ( $GetParam{ChangeTimeSearchType} eq 'TimePoint' ) {
$GetParam{'ChangeTimeSearchType::TimePoint'} = 1;
}
elsif ( $GetParam{ChangeTimeSearchType} eq 'TimeSlot' ) {
$GetParam{'ChangeTimeSearchType::TimeSlot'} = 1;
}
# get last change time option
if ( !$GetParam{LastChangeTimeSearchType} ) {
$GetParam{'LastChangeTimeSearchType::None'} = 1;
}
elsif ( $GetParam{LastChangeTimeSearchType} eq 'TimePoint' ) {
$GetParam{'LastChangeTimeSearchType::TimePoint'} = 1;
}
elsif ( $GetParam{LastChangeTimeSearchType} eq 'TimeSlot' ) {
$GetParam{'LastChangeTimeSearchType::TimeSlot'} = 1;
}
# get close time option
if ( !$GetParam{PendingTimeSearchType} ) {
$GetParam{'PendingTimeSearchType::None'} = 1;
}
elsif ( $GetParam{PendingTimeSearchType} eq 'TimePoint' ) {
$GetParam{'PendingTimeSearchType::TimePoint'} = 1;
}
elsif ( $GetParam{PendingTimeSearchType} eq 'TimeSlot' ) {
$GetParam{'PendingTimeSearchType::TimeSlot'} = 1;
}
# get close time option
if ( !$GetParam{CloseTimeSearchType} ) {
$GetParam{'CloseTimeSearchType::None'} = 1;
}
elsif ( $GetParam{CloseTimeSearchType} eq 'TimePoint' ) {
$GetParam{'CloseTimeSearchType::TimePoint'} = 1;
}
elsif ( $GetParam{CloseTimeSearchType} eq 'TimeSlot' ) {
$GetParam{'CloseTimeSearchType::TimeSlot'} = 1;
}
# get escalation time option
if ( !$GetParam{EscalationTimeSearchType} ) {
$GetParam{'EscalationTimeSearchType::None'} = 1;
}
elsif ( $GetParam{EscalationTimeSearchType} eq 'TimePoint' ) {
$GetParam{'EscalationTimeSearchType::TimePoint'} = 1;
}
elsif ( $GetParam{EscalationTimeSearchType} eq 'TimeSlot' ) {
$GetParam{'EscalationTimeSearchType::TimeSlot'} = 1;
}
# set result form env
if ( !$GetParam{ResultForm} ) {
$GetParam{ResultForm} = '';
}
# get needed objects
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
# show result site
if ( $Self->{Subaction} eq 'Search' && !$Self->{EraseTemplate} ) {
$Self->{ProfileName} = '';
if ( $Self->{Profile} ) {
$Self->{ProfileName} = "($Self->{Profile})";
}
# fill up profile name (e.g. with last-search)
if ( !$Self->{Profile} || !$Self->{SaveProfile} ) {
$Self->{Profile} = Translatable('last-search');
}
if ( !$Self->{ProfileName} ) {
$Self->{ProfileName} = "($Self->{Profile})";
}
if ( $Self->{ProfileName} eq '(last-search)' ) {
$Self->{ProfileName} = '(' . $LayoutObject->{LanguageObject}->Translate('last-search') . ')';
}
# save search profile (under last-search or real profile name)
$Self->{SaveProfile} = 1;
# remember last search values
if ( $Self->{SaveProfile} && $Self->{Profile} ) {
# remove old profile stuff
$SearchProfileObject->SearchProfileDelete(
Base => 'TicketSearch',
Name => $Self->{Profile},
UserLogin => $Self->{UserLogin},
);
# convert attributes
if ( $GetParam{ShownAttributes} && ref $GetParam{ShownAttributes} eq '' ) {
$GetParam{ShownAttributes} = [ split /;/, $GetParam{ShownAttributes} ];
}
# replace StateType to StateIDs
if ( $GetParam{StateType} ) {
my @StateIDs;
if ( $GetParam{StateType} eq 'Open' ) {
@StateIDs = $StateObject->StateGetStatesByType(
Type => 'Viewable',
Result => 'ID',
);
}
elsif ( $GetParam{StateType} eq 'Closed' ) {
my %ViewableStateOpenLookup = $StateObject->StateGetStatesByType(
Type => 'Viewable',
Result => 'HASH',
);
my %StateList = $StateObject->StateList( UserID => $Self->{UserID} );
for my $Item ( sort keys %StateList ) {
if ( !$ViewableStateOpenLookup{$Item} ) {
push @StateIDs, $Item;
}
}
}
# current ticket state type
else {
@StateIDs = $StateObject->StateGetStatesByType(
StateType => $GetParam{StateType},
Result => 'ID',
);
}
# merge with StateIDs
if ( @StateIDs && IsArrayRefWithData( $GetParam{StateIDs} ) ) {
my %StateIDs = map { $_ => 1 } @StateIDs;
@StateIDs = grep { exists $StateIDs{$_} } @{ $GetParam{StateIDs} };
}
if (@StateIDs) {
$GetParam{StateIDs} = \@StateIDs;
}
}
# insert new profile params
KEY:
for my $Key ( sort keys %GetParam ) {
next KEY if !defined $GetParam{$Key};
next KEY if $Key eq 'StateType';
$SearchProfileObject->SearchProfileAdd(
Base => 'TicketSearch',
Name => $Self->{Profile},
Key => $Key,
Value => $GetParam{$Key},
UserLogin => $Self->{UserLogin},
);
}
}
my %TimeMap = (
ArticleCreate => 'ArticleTime',
TicketCreate => 'Time',
TicketChange => 'ChangeTime',
TicketLastChange => 'LastChangeTime',
TicketClose => 'CloseTime',
TicketPending => 'PendingTime',
TicketEscalation => 'EscalationTime',
);
for my $TimeType ( sort keys %TimeMap ) {
# get create time settings
if ( !$GetParam{ $TimeMap{$TimeType} . 'SearchType' } ) {
# do nothing with time stuff
}
elsif ( $GetParam{ $TimeMap{$TimeType} . 'SearchType' } eq 'TimeSlot' ) {
for my $Key (qw(Month Day)) {
$GetParam{ $TimeType . 'TimeStart' . $Key } = sprintf( "%02d", $GetParam{ $TimeType . 'TimeStart' . $Key } );
$GetParam{ $TimeType . 'TimeStop' . $Key } = sprintf( "%02d", $GetParam{ $TimeType . 'TimeStop' . $Key } );
}
if (
$GetParam{ $TimeType . 'TimeStartDay' }
&& $GetParam{ $TimeType . 'TimeStartMonth' }
&& $GetParam{ $TimeType . 'TimeStartYear' }
)
{
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
Year => $GetParam{ $TimeType . 'TimeStartYear' },
Month => $GetParam{ $TimeType . 'TimeStartMonth' },
Day => $GetParam{ $TimeType . 'TimeStartDay' },
Hour => 0, # midnight
Minute => 0,
Second => 0,
TimeZone => $Self->{UserTimeZone} || Kernel::System::DateTime->UserDefaultTimeZoneGet(),
},
);
# Convert start time to local system time zone.
$DateTimeObject->ToOTOBOTimeZone();
$GetParam{ $TimeType . 'TimeNewerDate' } = $DateTimeObject->ToString();
}
if (
$GetParam{ $TimeType . 'TimeStopDay' }
&& $GetParam{ $TimeType . 'TimeStopMonth' }
&& $GetParam{ $TimeType . 'TimeStopYear' }
)
{
my $DateTimeObject = $Kernel::OM->Create(
'Kernel::System::DateTime',
ObjectParams => {
Year => $GetParam{ $TimeType . 'TimeStopYear' },
Month => $GetParam{ $TimeType . 'TimeStopMonth' },
Day => $GetParam{ $TimeType . 'TimeStopDay' },
Hour => 23, # just before midnight
Minute => 59,
Second => 59,
TimeZone => $Self->{UserTimeZone} || Kernel::System::DateTime->UserDefaultTimeZoneGet(),
},
);
# Convert stop time to local system time zone.
$DateTimeObject->ToOTOBOTimeZone();
$GetParam{ $TimeType . 'TimeOlderDate' } = $DateTimeObject->ToString();
}
}
elsif ( $GetParam{ $TimeMap{$TimeType} . 'SearchType' } eq 'TimePoint' ) {
if (
$GetParam{ $TimeType . 'TimePoint' }
&& $GetParam{ $TimeType . 'TimePointStart' }
&& $GetParam{ $TimeType . 'TimePointFormat' }
)
{
my $Time = 0;
if ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'minute' ) {
$Time = $GetParam{ $TimeType . 'TimePoint' };
}
elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'hour' ) {
$Time = $GetParam{ $TimeType . 'TimePoint' } * 60;
}
elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'day' ) {
$Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24;
}
elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'week' ) {
$Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24 * 7;
}
elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'month' ) {
$Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24 * 30;
}
elsif ( $GetParam{ $TimeType . 'TimePointFormat' } eq 'year' ) {
$Time = $GetParam{ $TimeType . 'TimePoint' } * 60 * 24 * 365;
}
if ( $GetParam{ $TimeType . 'TimePointStart' } eq 'Before' ) {
# more than ... ago
$GetParam{ $TimeType . 'TimeOlderMinutes' } = $Time;
}
elsif ( $GetParam{ $TimeType . 'TimePointStart' } eq 'Next' ) {
# within next
$GetParam{ $TimeType . 'TimeNewerMinutes' } = 0;
$GetParam{ $TimeType . 'TimeOlderMinutes' } = -$Time;
}
elsif ( $GetParam{ $TimeType . 'TimePointStart' } eq 'After' ) {
# in more then ...
$GetParam{ $TimeType . 'TimeNewerMinutes' } = -$Time;
}
else {
# within last ...
$GetParam{ $TimeType . 'TimeOlderMinutes' } = 0;
$GetParam{ $TimeType . 'TimeNewerMinutes' } = $Time;
}
}
}
}
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
# Special behavior for the fulltext search toolbar module:
# - Check full text string to see if contents is a ticket number.
# - If exists and not in print or CSV mode, redirect to the ticket.
# See http://bugs.otrs.org/show_bug.cgi?id=4238 for details.
# The original problem was that tickets with customer reply will be
# found by a fulltext search (ticket number is in the subjects), but
# 'new' tickets will not be found.
if (
$GetParam{Fulltext}
&& $ParamObject->GetParam( Param => 'CheckTicketNumberAndRedirect' )
&& $GetParam{ResultForm} ne 'Normal'
&& $GetParam{ResultForm} ne 'Print'
)
{
my $TicketID = $TicketObject->TicketIDLookup(
TicketNumber => $GetParam{Fulltext},
UserID => $Self->{UserID},
);
if ($TicketID) {
return $LayoutObject->Redirect(
OP => "Action=AgentTicketZoom;TicketID=$TicketID",
);
}
}
# prepare archive flag
if ( $ConfigObject->Get('Ticket::ArchiveSystem') ) {
$GetParam{SearchInArchive} ||= '';
if ( $GetParam{SearchInArchive} eq 'AllTickets' ) {
$GetParam{ArchiveFlags} = [ 'y', 'n' ];
}
elsif ( $GetParam{SearchInArchive} eq 'ArchivedTickets' ) {
$GetParam{ArchiveFlags} = ['y'];
}
else {
$GetParam{ArchiveFlags} = ['n'];
}
}
my %AttributeLookup;
# create attribute lookup table
for my $Attribute ( @{ $GetParam{ShownAttributes} || [] } ) {
$AttributeLookup{$Attribute} = 1;
}
# dynamic fields search parameters for ticket search
my %DynamicFieldSearchParameters;
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{$DynamicField} ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
# get search field preferences
my $SearchFieldPreferences = $BackendObject->SearchFieldPreferences(
DynamicFieldConfig => $DynamicFieldConfig,
);
next DYNAMICFIELD if !IsArrayRefWithData($SearchFieldPreferences);
PREFERENCE:
for my $Preference ( @{$SearchFieldPreferences} ) {
if (
!$AttributeLookup{
'LabelSearch_DynamicField_'
. $DynamicFieldConfig->{Name}
. $Preference->{Type}
}
)
{
next PREFERENCE;
}
# extract the dynamic field value from the profile
my $SearchParameter = $BackendObject->SearchFieldParameterBuild(
DynamicFieldConfig => $DynamicFieldConfig,
Profile => \%GetParam,
LayoutObject => $LayoutObject,
Type => $Preference->{Type},
);
# set search parameter
if ( defined $SearchParameter ) {
$DynamicFieldSearchParameters{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $SearchParameter->{Parameter};
}
}
}
my @ViewableTicketIDs;
my $ESActive = $Kernel::OM->Get('Kernel::Config')->Get('Elasticsearch::Active') || 0;
# check whether we want to perform a search via Elasticsearch or not
# use normal search for sorting, or if ES is not activated
if ( $GetParam{FulltextES} && ( !$ESActive || $Self->{TakeLastSearch} ) ) {
$GetParam{Fulltext} = $GetParam{Fulltext} || $GetParam{FulltextES};
delete $GetParam{FulltextES};
}
# search via DB if FulltextES is empty
if ( !$GetParam{FulltextES} ) {
local $Kernel::System::DB::UseSlaveDB = 1;
# perform ticket search
@ViewableTicketIDs = $TicketObject->TicketSearch(
Result => 'ARRAY',
SortBy => $Self->{SortBy},
OrderBy => $Self->{OrderBy},
Limit => $Self->{SearchLimit},
UserID => $Self->{UserID},
ConditionInline => $Config->{ExtendedSearchCondition},
ContentSearchPrefix => '*',
ContentSearchSuffix => '*',
FullTextIndex => 1,
%GetParam,
%DynamicFieldSearchParameters,
);
}
# if ES is activated
else {
# get data from cache
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
my $CacheData = $CacheObject->Get(
Type => 'TicketSearch',
Key => ( $GetParam{FulltextES} || '' ) . '_SortBy_' . $Self->{SortBy} . '_OrderBy_' . $Self->{OrderBy},
);
# take ticketids from cache if available, else perform ES search
if ( defined $CacheData ) {
@ViewableTicketIDs = @{$CacheData};
}
else {
my $ESObject = $Kernel::OM->Get('Kernel::System::Elasticsearch');
my $Count = $ConfigObject->Get('Elasticsearch::MaxArticleSearch');
# TODO please consider sorting via ES. For fields with type text, mapping for sorting must be the keyword version, e.g. Title.keyword!
#my $Field = $Mapping->{ticket}->{mappings}->{properties}->{ $Param{Sort} }->{fields};
#$Param{Sort} .= ( defined $Field ) ? '.' . ( %{$Field} )[0] : '';
if ( ( defined $GetParam{FulltextES} ) && ( !defined $GetParam{Fulltext} ) ) {
@ViewableTicketIDs = $ESObject->TicketSearch(
Fulltext => $GetParam{FulltextES},
UserID => $Self->{UserID},
UserLogin => $Self->{UserLogin},
CustomerUserID => $Self->{CustomerUserIDLogin} || '',
SortBy => $Self->{SortBy},
OrderBy => $Self->{OrderBy},
Limit => $Count,
Result => 'ARRAY',
);
# set cache for ES results
if ($CacheObject) {
$CacheObject->Set(
Type => 'TicketSearch',
Key => $GetParam{FulltextES}
. '_SortBy_'
. $Self->{SortBy}
. '_OrderBy_'
. $Self->{OrderBy},
Value => \@ViewableTicketIDs,
TTL => $GetParam{CacheTTL} || 60 * 4,
);
}
}
}
}
# get needed objects
my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser');
# get the ticket dynamic fields for CSV display
my $CSVDynamicField = $DynamicFieldObject->DynamicFieldListGet(
Valid => 1,
ObjectType => ['Ticket'],
FieldFilter => $Config->{SearchCSVDynamicField} || {},
);
# CSV and Excel output
if (
$GetParam{ResultForm} eq 'CSV'
||
$GetParam{ResultForm} eq 'Excel'
)
{
# create head (actual head and head for data fill)
my @TmpCSVHead = @{ $Config->{SearchCSVData} };
my @CSVHead = @{ $Config->{SearchCSVData} };
# include the selected dynamic fields in CVS results
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{$CSVDynamicField} ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
next DYNAMICFIELD if $DynamicFieldConfig->{Name} eq '';
push @TmpCSVHead, 'DynamicField_' . $DynamicFieldConfig->{Name};
push @CSVHead, $DynamicFieldConfig->{Label};
}
my @CSVData;
for my $TicketID (@ViewableTicketIDs) {
# Get ticket data.
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
DynamicFields => 1,
Extended => 1,
UserID => $Self->{UserID},
);
# Get last customer article.
my @Articles = $ArticleObject->ArticleList(
TicketID => $TicketID,
SenderType => 'customer',
OnlyLast => 1,
);
# If the ticket has no customer article, get the last agent article.
if ( !@Articles ) {
@Articles = $ArticleObject->ArticleList(
TicketID => $TicketID,
SenderType => 'agent',
OnlyLast => 1,
);
}
# Finally, if everything failed, get latest article.
if ( !@Articles ) {
@Articles = $ArticleObject->ArticleList(
TicketID => $TicketID,
OnlyLast => 1,
);
}
my %Article = $ArticleObject->BackendForArticle( %{ $Articles[0] } )->ArticleGet(
%{ $Articles[0] },
DynamicFields => 1,
);
my %Data;
if ( !%Article ) {
%Data = %Ticket;
# Set missing information.
$Data{Subject} = $Ticket{Title} || $LayoutObject->{LanguageObject}->Translate('Untitled');
$Data{Body} = $LayoutObject->{LanguageObject}->Translate('This item has no articles yet.');
$Data{From} = '--';
}
else {
%Data = ( %Ticket, %Article );
}
for my $Key (qw(State Lock)) {
$Data{$Key} = $LayoutObject->{LanguageObject}->Translate( $Data{$Key} );
}
$Data{Age} = $LayoutObject->CustomerAge(
Age => $Data{Age},
Space => ' ',
);
# get whole article (if configured!)
if ( $Config->{SearchArticleCSVTree} ) {
my @Articles = $ArticleObject->ArticleList(
TicketID => $TicketID,
);
if (@Articles) {
for my $Article (@Articles) {
my %ArticleData = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet(
TicketID => $TicketID,
ArticleID => $Article->{ArticleID},
DynamicFields => 0,
);
if ( $ArticleData{Body} ) {
$Data{ArticleTree}
.= "\n-->"
. "||$ArticleData{SenderType}"
. "||$ArticleData{From}"
. "||$ArticleData{CreateTime}"
. "||<--------------\n"
. $ArticleData{Body};
}
}
}
else {
$Data{ArticleTree} .= $LayoutObject->{LanguageObject}->Translate(
'This item has no articles yet.'
);
}
}
# customer info (customer name)
if ( $Data{CustomerUserID} ) {
$Data{CustomerName} = $CustomerUserObject->CustomerName(
UserLogin => $Data{CustomerUserID},
);
}
# user info
my %UserInfo = $UserObject->GetUserData(
User => $Data{Owner},
);
# merge row data
my %Info = (
%Data,
%UserInfo,
AccountedTime =>
$TicketObject->TicketAccountedTimeGet( TicketID => $TicketID ),
);
# Transform EscalationTime and EscalationTimeWorkingTime to a human readable format.
# See bug#13088 (https://bugs.otrs.org/show_bug.cgi?id=13088).
$Info{EscalationTime} = $LayoutObject->CustomerAgeInHours(
Age => $Info{EscalationTime},
Space => ' ',
);
$Info{EscalationTimeWorkingTime} = $LayoutObject->CustomerAgeInHours(
Age => $Info{EscalationTimeWorkingTime},
Space => ' ',
);
my @Data;
for my $Header (@TmpCSVHead) {
# check if header is a dynamic field and get the value from dynamic field
# backend
if ( $Header =~ m{\A DynamicField_ ( [a-zA-Z\d]+ ) \z}xms ) {
# loop over the dynamic fields configured for CSV output
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{$CSVDynamicField} ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
# skip all fields that does not match with current field name ($1)
# with out the 'DynamicField_' prefix
next DYNAMICFIELD if $DynamicFieldConfig->{Name} ne $1;
# get the value as for print (to correctly display)
my $ValueStrg = $BackendObject->DisplayValueRender(
DynamicFieldConfig => $DynamicFieldConfig,
Value => $Info{$Header},
HTMLOutput => 0,
LayoutObject => $LayoutObject,
);
push @Data, $ValueStrg->{Value};
# terminate the DYNAMICFIELD loop
last DYNAMICFIELD;
}
}
# otherwise retrieve data from article
else {
push @Data, $Info{$Header};
}
}
push @CSVData, \@Data;
}
# get Separator from language file
my $UserCSVSeparator = $LayoutObject->{LanguageObject}->{Separator};
if ( $ConfigObject->Get('PreferencesGroups')->{CSVSeparator}->{Active} ) {
my %UserData = $UserObject->GetUserData( UserID => $Self->{UserID} );
$UserCSVSeparator = $UserData{UserCSVSeparator} if $UserData{UserCSVSeparator};
}
my %HeaderMap = (
TicketNumber => Translatable('Ticket Number'),
CustomerName => Translatable('Customer Name'),
);
my @CSVHeadTranslated = map { $LayoutObject->{LanguageObject}->Translate( $HeaderMap{$_} || $_ ); }
@CSVHead;
my $CurDateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $FileName = sprintf(
'ticket_search_%s',
$CurDateTimeObject->Format(
Format => '%Y-%m-%d_%H-%M'
)
);
# get CSV object
my $CSVObject = $Kernel::OM->Get('Kernel::System::CSV');
# generate CSV output
if ( $GetParam{ResultForm} eq 'CSV' ) {
my $CSV = $CSVObject->Array2CSV(
Head => \@CSVHeadTranslated,
Data => \@CSVData,
Separator => $UserCSVSeparator,
);
# return csv to download
return $LayoutObject->Attachment(
Filename => $FileName . '.csv',
ContentType => "text/csv; charset=" . $LayoutObject->{UserCharset},
Content => $CSV,
);
}
# generate Excel output
elsif ( $GetParam{ResultForm} eq 'Excel' ) {
my $Excel = $CSVObject->Array2CSV(
Head => \@CSVHeadTranslated,
Data => \@CSVData,
Format => 'Excel',
);
# return Excel to download
return $LayoutObject->Attachment(
Filename => $FileName . '.xlsx',
ContentType =>
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
Content => $Excel,
);
}
}
# PDF output
elsif ( $GetParam{ResultForm} eq 'Print' ) {
# get PDF object
my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF');
my @PDFData;
for my $TicketID (@ViewableTicketIDs) {
# Get ticket data.
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
DynamicFields => 1,
UserID => $Self->{UserID},
);
# Get last customer article.
my @Articles = $ArticleObject->ArticleList(
TicketID => $TicketID,
SenderType => 'customer',
OnlyLast => 1,
);
# If the ticket has no customer article, get the last agent article.
if ( !@Articles ) {
@Articles = $ArticleObject->ArticleList(
TicketID => $TicketID,
SenderType => 'agent',
OnlyLast => 1,
);
}
# Finally, if everything failed, get latest article.
if ( !@Articles ) {
@Articles = $ArticleObject->ArticleList(
TicketID => $TicketID,
OnlyLast => 1,
);
}
my %Article = $ArticleObject->BackendForArticle( %{ $Articles[0] } )->ArticleGet(
%{ $Articles[0] },
DynamicFields => 1,
);
my %Data;
if ( !%Article ) {
%Data = %Ticket;
# Set missing information.
$Data{Subject} = $Data{Title} || Translatable('Untitled');
$Data{From} = '--';
}
else {
%Data = ( %Ticket, %Article );
}
# customer info
my %CustomerData;
if ( $Data{CustomerUserID} ) {
%CustomerData = $CustomerUserObject->CustomerUserDataGet(
User => $Data{CustomerUserID},
);
}
elsif ( $Data{CustomerID} ) {
%CustomerData = $CustomerUserObject->CustomerUserDataGet(
CustomerID => $Data{CustomerID},
);
}
# customer info (customer name)
if ( $CustomerData{UserLogin} ) {
$Data{CustomerName} = $CustomerUserObject->CustomerName(
UserLogin => $CustomerData{UserLogin},
);
}
# user info
my %UserInfo = $UserObject->GetUserData(
User => $Data{Owner},
);
# customer info string
$UserInfo{CustomerName} = '(' . $UserInfo{CustomerName} . ')'
if ( $UserInfo{CustomerName} );
my %Info = ( %Data, %UserInfo );
my $Created = $LayoutObject->{LanguageObject}->FormatTimeString(
$Data{CreateTime} // $Data{Created},
'DateFormat',
);
my $Owner = "$Info{Owner} ($Info{UserFullname})";
my $Customer = "$Data{CustomerID} $Data{CustomerName}";
my @PDFRow;
push @PDFRow, $Data{TicketNumber};
push @PDFRow, $Created;
push @PDFRow, $Data{From};
push @PDFRow, $Data{Subject};
push @PDFRow, $Data{State};
push @PDFRow, $Data{Queue};
push @PDFRow, $Owner;
push @PDFRow, $Customer;
push @PDFData, \@PDFRow;
}
my $Title = $LayoutObject->{LanguageObject}->Translate('Ticket') . ' '
. $LayoutObject->{LanguageObject}->Translate('Search');
my $PrintedBy = $LayoutObject->{LanguageObject}->Translate('printed by');
my $Page = $LayoutObject->{LanguageObject}->Translate('Page');
my $DateTimeString = $Kernel::OM->Create('Kernel::System::DateTime')->ToString();
my $Time = $LayoutObject->{LanguageObject}->FormatTimeString(
$DateTimeString,
'DateFormat',
);
# get maximum number of pages
my $MaxPages = $ConfigObject->Get('PDF::MaxPages');
if ( !$MaxPages || $MaxPages < 1 || $MaxPages > 1000 ) {
$MaxPages = 100;
}
my $CellData;
# verify if there are tickets to show
if (@PDFData) {
# create the header
$CellData->[0]->[0]->{Content} = $ConfigObject->Get('Ticket::Hook');
$CellData->[0]->[0]->{Font} = 'ProportionalBold';
$CellData->[0]->[1]->{Content} = $LayoutObject->{LanguageObject}->Translate('Created');
$CellData->[0]->[1]->{Font} = 'ProportionalBold';
$CellData->[0]->[2]->{Content} = $LayoutObject->{LanguageObject}->Translate('From');
$CellData->[0]->[2]->{Font} = 'ProportionalBold';
$CellData->[0]->[3]->{Content} = $LayoutObject->{LanguageObject}->Translate('Subject');
$CellData->[0]->[3]->{Font} = 'ProportionalBold';
$CellData->[0]->[4]->{Content} = $LayoutObject->{LanguageObject}->Translate('State');
$CellData->[0]->[4]->{Font} = 'ProportionalBold';
$CellData->[0]->[5]->{Content} = $LayoutObject->{LanguageObject}->Translate('Queue');
$CellData->[0]->[5]->{Font} = 'ProportionalBold';
$CellData->[0]->[6]->{Content} = $LayoutObject->{LanguageObject}->Translate('Owner');
$CellData->[0]->[6]->{Font} = 'ProportionalBold';
$CellData->[0]->[7]->{Content} = $LayoutObject->{LanguageObject}->Translate('CustomerID');
$CellData->[0]->[7]->{Font} = 'ProportionalBold';
# create the content array
my $CounterRow = 1;
for my $Row (@PDFData) {
my $CounterColumn = 0;
for my $Content ( @{$Row} ) {
$CellData->[$CounterRow]->[$CounterColumn]->{Content} = $Content;
$CounterColumn++;
}
$CounterRow++;
}
}
# otherwise, show 'No ticket data found' message
else {
$CellData->[0]->[0]->{Content} = $LayoutObject->{LanguageObject}->Translate('No ticket data found.');
}
# page params
my %PageParam;
$PageParam{PageOrientation} = 'landscape';
$PageParam{MarginTop} = 30;
$PageParam{MarginRight} = 40;
$PageParam{MarginBottom} = 40;
$PageParam{MarginLeft} = 40;
$PageParam{HeaderRight} = $Title;
$PageParam{HeadlineLeft} = $Title;
# table params
my %TableParam;
$TableParam{CellData} = $CellData;
$TableParam{Type} = 'Cut';
$TableParam{FontSize} = 6;
$TableParam{Border} = 0;
$TableParam{BackgroundColorEven} = '#DDDDDD';
$TableParam{Padding} = 1;
$TableParam{PaddingTop} = 3;
$TableParam{PaddingBottom} = 3;
# create new pdf document
$PDFObject->DocumentNew(
Title => $ConfigObject->Get('Product') . ': ' . $Title,
Encode => $LayoutObject->{UserCharset},
);
# start table output
$PDFObject->PageNew(
%PageParam,
FooterRight => $Page . ' 1',
);
$PDFObject->PositionSet(
Move => 'relativ',
Y => -6,
);
# output title
$PDFObject->Text(
Text => $Title,
FontSize => 13,
);
$PDFObject->PositionSet(
Move => 'relativ',
Y => -6,
);
# output "printed by"
$PDFObject->Text(
Text => $PrintedBy . ' '
. $Self->{UserFullname} . ' ('
. $Self->{UserEmail} . ')'
. ', ' . $Time,
FontSize => 9,
);
$PDFObject->PositionSet(
Move => 'relativ',
Y => -14,
);
PAGE:
for my $PageNumber ( 2 .. $MaxPages ) {
# output table (or a fragment of it)
%TableParam = $PDFObject->Table(%TableParam);
# stop output or another page
if ( $TableParam{State} ) {
last PAGE;
}
else {
$PDFObject->PageNew(
%PageParam,
FooterRight => $Page . ' ' . $PageNumber,
);
}
}
# return the pdf document
my $CurDateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
my $Filename = sprintf(
'ticket_search_%s.pdf',
$CurDateTimeObject->Format(
Format => '%Y-%m-%d_%H-%M'
)
);
my $PDFString = $PDFObject->DocumentOutput();
return $LayoutObject->Attachment(
Filename => $Filename,
ContentType => "application/pdf",
Content => $PDFString,
Type => 'inline',
);
}
else {
# redirect to the ticketzoom if result of the search is only one
if ( scalar @ViewableTicketIDs eq 1 && !$Self->{TakeLastSearch} ) {
return $LayoutObject->Redirect(
OP => "Action=AgentTicketZoom;TicketID=$ViewableTicketIDs[0]",
);
}
# store last overview screen
my $URL = "Action=AgentTicketSearch;Subaction=Search"
. ";Profile=" . $LayoutObject->LinkEncode( $Self->{Profile} )
. ";SortBy=" . $LayoutObject->LinkEncode( $Self->{SortBy} )
. ";OrderBy=" . $LayoutObject->LinkEncode( $Self->{OrderBy} )
. ";TakeLastSearch=1;StartHit="
. $LayoutObject->LinkEncode( $Self->{StartHit} );
$Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID(
SessionID => $Self->{SessionID},
Key => 'LastScreenOverview',
Value => $URL,
);
# start HTML page
my $Output = $LayoutObject->Header();
$Output .= $LayoutObject->NavigationBar();
# Notify if there are tickets which are not updated.
$Output .= $LayoutObject->NotifyNonUpdatedTickets() // '';
$Self->{Filter} = $ParamObject->GetParam( Param => 'Filter' ) || '';
$Self->{View} = $ParamObject->GetParam( Param => 'View' ) || '';
# show tickets
my $LinkPage = 'Filter='
. $LayoutObject->LinkEncode( $Self->{Filter} )
. ';View=' . $LayoutObject->LinkEncode( $Self->{View} )
. ';SortBy=' . $LayoutObject->LinkEncode( $Self->{SortBy} )
. ';OrderBy=' . $LayoutObject->LinkEncode( $Self->{OrderBy} )
. ';Profile='
. $LayoutObject->LinkEncode( $Self->{Profile} )
. ';TakeLastSearch=1;Subaction=Search'
. ';';
my $LinkSort = 'Filter='
. $LayoutObject->LinkEncode( $Self->{Filter} )
. ';View=' . $LayoutObject->LinkEncode( $Self->{View} )
. ';Profile='
. $LayoutObject->LinkEncode( $Self->{Profile} )
. ';TakeLastSearch=1;Subaction=Search'
. ';';
my $LinkFilter = 'TakeLastSearch=1;Subaction=Search;Profile='
. $LayoutObject->LinkEncode( $Self->{Profile} )
. ';';
my $LinkBack = 'Subaction=LoadProfile;Profile='
. $LayoutObject->LinkEncode( $Self->{Profile} )
. ';TakeLastSearch=1&';
my $FilterLink = 'SortBy=' . $LayoutObject->LinkEncode( $Self->{SortBy} )
. ';OrderBy=' . $LayoutObject->LinkEncode( $Self->{OrderBy} )
. ';View=' . $LayoutObject->LinkEncode( $Self->{View} )
. ';Profile='
. $LayoutObject->LinkEncode( $Self->{Profile} )
. ';TakeLastSearch=1;Subaction=Search'
. ';';
$Output .= $LayoutObject->TicketListShow(
TicketIDs => \@ViewableTicketIDs,
Total => scalar @ViewableTicketIDs,
View => $Self->{View},
Env => $Self,
LinkPage => $LinkPage,
LinkSort => $LinkSort,
LinkFilter => $LinkFilter,
LinkBack => $LinkBack,
Profile => $Self->{Profile},
ProfileName => $Self->{ProfileName},
TitleName => Translatable('Search Results'),
Bulk => 1,
Limit => $Self->{SearchLimit},
Filter => $Self->{Filter},
OrderBy => $Self->{OrderBy},
SortBy => $Self->{SortBy},
RequestedURL => 'Action=' . $Self->{Action} . ';' . $LinkPage,
# do not print the result earlier, but return complete content
Output => 1,
);
# build footer
$Output .= $LayoutObject->Footer();
return $Output;
}
}
elsif ( $Self->{Subaction} eq 'AJAXProfileDelete' ) {
my $Profile = $ParamObject->GetParam( Param => 'Profile' );
# remove old profile stuff
$SearchProfileObject->SearchProfileDelete(
Base => 'TicketSearch',
Name => $Profile,
UserLogin => $Self->{UserLogin},
);
my $Output = $LayoutObject->JSONEncode(
Data => 1,
);
# TODO: why not application/json
return $LayoutObject->Attachment(
NoCache => 1,
ContentType => 'text/html',
Content => $Output,
Type => 'inline'
);
}
elsif ( $Self->{Subaction} eq 'AJAXStopWordCheck' ) {
my $StopWordCheckResult = {
FoundStopWords => [],
};
if ( $Kernel::OM->Get('Kernel::System::Ticket::Article')->SearchStringStopWordsUsageWarningActive() ) {
my @ParamNames = $ParamObject->GetParamNames();
my %SearchStrings;
SEARCHSTRINGPARAMNAME:
for my $SearchStringParamName ( sort @ParamNames ) {
next SEARCHSTRINGPARAMNAME if $SearchStringParamName !~ m{\ASearchStrings\[(.*)\]\z}sm;
$SearchStrings{$1} = $ParamObject->GetParam( Param => $SearchStringParamName );
}
$StopWordCheckResult->{FoundStopWords} = $Kernel::OM->Get('Kernel::System::Ticket::Article')->SearchStringStopWordsFind(
SearchStrings => \%SearchStrings,
);
}
my $Output = $LayoutObject->JSONEncode(
Data => $StopWordCheckResult,
);
# TODO: why not application/json
return $LayoutObject->Attachment(
NoCache => 1,
ContentType => 'text/html',
Charset => $LayoutObject->{UserCharset},
Content => $Output,
Type => 'inline'
);
}
elsif ( $Self->{Subaction} eq 'AJAX' ) {
my $Profile = $ParamObject->GetParam( Param => 'Profile' ) || '';
my $EmptySearch = $ParamObject->GetParam( Param => 'EmptySearch' );
if ( !$Profile ) {
$EmptySearch = 1;
}
my %GetParam = $SearchProfileObject->SearchProfileGet(
Base => 'TicketSearch',
Name => $Profile,
UserLogin => $Self->{UserLogin},
);
# convert attributes
if ( IsArrayRefWithData( $GetParam{ShownAttributes} ) ) {
my @ShowAttributes = grep {defined} @{ $GetParam{ShownAttributes} };
$GetParam{ShownAttributes} = join ';', @ShowAttributes;
}
# if no profile is used, set default params of default attributes
if ( !$Profile ) {
if ( $Config->{Defaults} ) {
KEY:
for my $Key ( sort keys %{ $Config->{Defaults} } ) {
next KEY if !$Config->{Defaults}->{$Key};
next KEY if $Key eq 'DynamicField';
if ( $Key =~ /^(Ticket|Article)(Create|Change|Close|Escalation)/ ) {
my @Items = split /;/, $Config->{Defaults}->{$Key};
for my $Item (@Items) {
my ( $Key, $Value ) = split /=/, $Item;
$GetParam{$Key} = $Value;
}
}
else {
$GetParam{$Key} = $Config->{Defaults}->{$Key};
}
}
}
}
my @Attributes = (
# Main fields
{
Key => 'TicketNumber',
Value => Translatable('Ticket Number'),
},
{
Key => 'Fulltext',
Value => Translatable('Fulltext'),
},
{
Key => 'Title',
Value => Translatable('Title'),
},
{
Key => '',
Value => '-',
Disabled => 1,
},
);
for my $ArticleFieldKey ( sort keys %ArticleSearchableFields ) {
push @Attributes, (
{
Key => $ArticleSearchableFields{$ArticleFieldKey}->{Key},
Value => Translatable( $ArticleSearchableFields{$ArticleFieldKey}->{Label} ),
},
);
}
# Ticket fields
push @Attributes, (
{
Key => '',
Value => '-',
Disabled => 1,
},
{
Key => 'CustomerID',
Value => Translatable('CustomerID (complex search)'),
},
{
Key => 'CustomerIDRaw',
Value => Translatable('CustomerID (exact match)'),
},
{
Key => 'CustomerUserLogin',
Value => Translatable('Assigned to Customer User Login (complex search)'),
},
{
Key => 'CustomerUserLoginRaw',
Value => Translatable('Assigned to Customer User Login (exact match)'),
},
{
Key => 'CustomerUserID',
Value => Translatable('Accessible to Customer User Login (exact match)'),
},
{
Key => 'StateIDs',
Value => Translatable('State'),
},
{
Key => 'PriorityIDs',
Value => Translatable('Priority'),
},
{
Key => 'LockIDs',
Value => Translatable('Lock'),
},
{
Key => 'QueueIDs',
Value => Translatable('Queue'),
},
{
Key => 'CreatedQueueIDs',
Value => Translatable('Created in Queue'),
},
);
if ( $ConfigObject->Get('Ticket::Type') ) {
push @Attributes, (
{
Key => 'TypeIDs',
Value => Translatable('Type'),
},
);
}
if ( $ConfigObject->Get('Ticket::Service') ) {
push @Attributes, (
{
Key => 'ServiceIDs',
Value => Translatable('Service'),
},
{
Key => 'SLAIDs',
Value => Translatable('SLA'),
},
);
}
push @Attributes, (
{
Key => 'OwnerIDs',
Value => Translatable('Owner'),
},
{
Key => 'CreatedUserIDs',
Value => Translatable('Created by'),
},
);
if ( $ConfigObject->Get('Ticket::Watcher') ) {
push @Attributes, (
{
Key => 'WatchUserIDs',
Value => Translatable('Watcher'),
},
);
}
if ( $ConfigObject->Get('Ticket::Responsible') ) {
push @Attributes, (
{
Key => 'ResponsibleIDs',
Value => Translatable('Responsible'),
},
);
}
# Time fields
push @Attributes, (
{
Key => '',
Value => '-',
Disabled => 1,
},
{
Key => 'TicketLastChangeTimePoint',
Value => Translatable('Ticket Last Change Time (before/after)'),
},
{
Key => 'TicketLastChangeTimeSlot',
Value => Translatable('Ticket Last Change Time (between)'),
},
{
Key => 'TicketChangeTimePoint',
Value => Translatable('Ticket Change Time (before/after)'),
},
{
Key => 'TicketChangeTimeSlot',
Value => Translatable('Ticket Change Time (between)'),
},
{
Key => 'TicketCloseTimePoint',
Value => Translatable('Ticket Close Time (before/after)'),
},
{
Key => 'TicketCloseTimeSlot',
Value => Translatable('Ticket Close Time (between)'),
},
{
Key => 'TicketCreateTimePoint',
Value => Translatable('Ticket Create Time (before/after)'),
},
{
Key => 'TicketCreateTimeSlot',
Value => Translatable('Ticket Create Time (between)'),
},
{
Key => 'TicketPendingTimePoint',
Value => Translatable('Ticket Pending Until Time (before/after)'),
},
{
Key => 'TicketPendingTimeSlot',
Value => Translatable('Ticket Pending Until Time (between)'),
},
{
Key => 'TicketEscalationTimePoint',
Value => Translatable('Ticket Escalation Time (before/after)'),
},
{
Key => 'TicketEscalationTimeSlot',
Value => Translatable('Ticket Escalation Time (between)'),
},
{
Key => 'ArticleCreateTimePoint',
Value => Translatable('Article Create Time (before/after)'),
},
{
Key => 'ArticleCreateTimeSlot',
Value => Translatable('Article Create Time (between)'),
},
);
if ( $ConfigObject->Get('Ticket::ArchiveSystem') ) {
push @Attributes, (
{
Key => 'SearchInArchive',
Value => Translatable('Archive Search'),
},
);
}
# Dynamic fields
my $DynamicFieldSeparator = 1;
# create dynamic fields search options for attribute select
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{$DynamicField} ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
next DYNAMICFIELD if $DynamicFieldConfig->{Name} eq '';
# create a separator for dynamic fields attributes
if ($DynamicFieldSeparator) {
push @Attributes, (
{
Key => '',
Value => '-',
Disabled => 1,
},
);
$DynamicFieldSeparator = 0;
}
# get search field preferences
my $SearchFieldPreferences = $BackendObject->SearchFieldPreferences(
DynamicFieldConfig => $DynamicFieldConfig,
);
next DYNAMICFIELD if !IsArrayRefWithData($SearchFieldPreferences);
# translate the dynamic field label
my $TranslatedDynamicFieldLabel = $LayoutObject->{LanguageObject}->Translate(
$DynamicFieldConfig->{Label},
);
PREFERENCE:
for my $Preference ( @{$SearchFieldPreferences} ) {
# translate the suffix
my $TranslatedSuffix = $LayoutObject->{LanguageObject}->Translate(
$Preference->{LabelSuffix},
) || '';
if ($TranslatedSuffix) {
$TranslatedSuffix = ' (' . $TranslatedSuffix . ')';
}
push @Attributes, (
{
Key => 'Search_DynamicField_'
. $DynamicFieldConfig->{Name}
. $Preference->{Type},
Value => $TranslatedDynamicFieldLabel . $TranslatedSuffix,
},
);
}
}
# create HTML strings for all dynamic fields
my %DynamicFieldHTML;
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{$DynamicField} ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
my $PossibleValuesFilter;
my $IsACLReducible = $BackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsACLReducible',
);
if ($IsACLReducible) {
# get PossibleValues
my $PossibleValues = $BackendObject->PossibleValuesGet(
DynamicFieldConfig => $DynamicFieldConfig,
);
# check if field has PossibleValues property in its configuration
if ( IsHashRefWithData($PossibleValues) ) {
# get historical values from database
my $HistoricalValues = $BackendObject->HistoricalValuesGet(
DynamicFieldConfig => $DynamicFieldConfig,
);
my $Data = $PossibleValues;
# add historic values to current values (if they don't exist anymore)
if ( IsHashRefWithData($HistoricalValues) ) {
for my $Key ( sort keys %{$HistoricalValues} ) {
if ( !$Data->{$Key} ) {
$Data->{$Key} = $HistoricalValues->{$Key};
}
}
}
# convert possible values key => value to key => key for ACLs using a Hash slice
my %AclData = %{$Data};
@AclData{ keys %AclData } = keys %AclData;
# set possible values filter from ACLs
my $ACL = $TicketObject->TicketAcl(
Action => $Self->{Action},
ReturnType => 'Ticket',
ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
Data => \%AclData,
UserID => $Self->{UserID},
);
if ($ACL) {
my %Filter = $TicketObject->TicketAclData();
# convert Filer key => key back to key => value using map
%{$PossibleValuesFilter} = map { $_ => $Data->{$_} } keys %Filter;
}
}
}
# get search field preferences
my $SearchFieldPreferences = $BackendObject->SearchFieldPreferences(
DynamicFieldConfig => $DynamicFieldConfig,
);
next DYNAMICFIELD if !IsArrayRefWithData($SearchFieldPreferences);
PREFERENCE:
for my $Preference ( @{$SearchFieldPreferences} ) {
# get field HTML
$DynamicFieldHTML{ $DynamicFieldConfig->{Name} . $Preference->{Type} } = $BackendObject->SearchFieldRender(
DynamicFieldConfig => $DynamicFieldConfig,
Profile => \%GetParam,
PossibleValuesFilter => $PossibleValuesFilter,
DefaultValue =>
$Config->{Defaults}->{DynamicField}
->{ $DynamicFieldConfig->{Name} },
LayoutObject => $LayoutObject,
Type => $Preference->{Type},
);
}
}
$Param{AttributesStrg} = $LayoutObject->BuildSelection(
PossibleNone => 1,
Data => \@Attributes,
Name => 'Attribute',
Multiple => 0,
Class => 'Modernize',
);
$Param{AttributesOrigStrg} = $LayoutObject->BuildSelection(
PossibleNone => 1,
Data => \@Attributes,
Name => 'AttributeOrig',
Multiple => 0,
Class => 'Modernize',
);
# get all users of own groups
my %AllUsers = $UserObject->UserList(
Type => 'Long',
Valid => 0,
);
# Rother OSS / TicketSearch-IncludeInvalidAgents
# if ( !$ConfigObject->Get('Ticket::ChangeOwnerToEveryone') ) {
if ( !$ConfigObject->Get('Ticket::ChangeOwnerToEveryone') && !$ConfigObject->Get('Ticket::Search')->{IncludeInvalidAgents} ) {
# EO TicketSearch-IncludeInvalidAgents
my %Involved = $Kernel::OM->Get('Kernel::System::Group')->PermissionUserInvolvedGet(
UserID => $Self->{UserID},
Type => 'ro',
);
for my $UserID ( sort keys %AllUsers ) {
if ( !$Involved{$UserID} ) {
delete $AllUsers{$UserID};
}
}
}
my @ShownUsers;
my %UsersInvalid;
# get valid users of own groups
my %ValidUsers = $UserObject->UserList(
Type => 'Long',
Valid => 1,
);
USERID:
for my $UserID ( sort { $AllUsers{$a} cmp $AllUsers{$b} } keys %AllUsers ) {
if ( !$ValidUsers{$UserID} ) {
$UsersInvalid{$UserID} = $AllUsers{$UserID};
next USERID;
}
push @ShownUsers, {
Key => $UserID,
Value => $AllUsers{$UserID},
};
}
# also show invalid agents (if any)
if ( scalar %UsersInvalid ) {
push @ShownUsers, {
Key => '-',
Value => '_____________________',
Disabled => 1,
};
push @ShownUsers, {
Key => '-',
Value => $LayoutObject->{LanguageObject}->Translate('Invalid Users'),
Disabled => 1,
};
push @ShownUsers, {
Key => '-',
Value => '',
Disabled => 1,
};
for my $UserID ( sort { $UsersInvalid{$a} cmp $UsersInvalid{$b} } keys %UsersInvalid ) {
push @ShownUsers, {
Key => $UserID,
Value => $UsersInvalid{$UserID},
};
}
}
$Param{UserStrg} = $LayoutObject->BuildSelection(
Data => \@ShownUsers,
Name => 'OwnerIDs',
Multiple => 1,
Size => 5,
SelectedID => $GetParam{OwnerIDs},
Class => 'Modernize',
);
$Param{CreatedUserStrg} = $LayoutObject->BuildSelection(
Data => \@ShownUsers,
Name => 'CreatedUserIDs',
Multiple => 1,
Size => 5,
SelectedID => $GetParam{CreatedUserIDs},
Class => 'Modernize',
);
if ( $ConfigObject->Get('Ticket::Watcher') ) {
$Param{WatchUserStrg} = $LayoutObject->BuildSelection(
Data => \@ShownUsers,
Name => 'WatchUserIDs',
Multiple => 1,
Size => 5,
SelectedID => $GetParam{WatchUserIDs},
Class => 'Modernize',
);
}
if ( $ConfigObject->Get('Ticket::Responsible') ) {
$Param{ResponsibleStrg} = $LayoutObject->BuildSelection(
Data => \@ShownUsers,
Name => 'ResponsibleIDs',
Multiple => 1,
Size => 5,
SelectedID => $GetParam{ResponsibleIDs},
Class => 'Modernize',
);
}
# build service string
if ( $ConfigObject->Get('Ticket::Service') ) {
my %Service = $Kernel::OM->Get('Kernel::System::Service')->ServiceList(
UserID => $Self->{UserID},
KeepChildren => $ConfigObject->Get('Ticket::Service::KeepChildren'),
);
$Param{ServicesStrg} = $LayoutObject->BuildSelection(
Data => \%Service,
Name => 'ServiceIDs',
SelectedID => $GetParam{ServiceIDs},
TreeView => $TreeView,
Sort => 'TreeView',
Size => 5,
Multiple => 1,
Translation => 0,
Max => 200,
Class => 'Modernize',
);
my %SLA = $Kernel::OM->Get('Kernel::System::SLA')->SLAList(
UserID => $Self->{UserID},
);
$Param{SLAsStrg} = $LayoutObject->BuildSelection(
Data => \%SLA,
Name => 'SLAIDs',
SelectedID => $GetParam{SLAIDs},
Sort => 'AlphanumericValue',
Size => 5,
Multiple => 1,
Translation => 0,
Max => 200,
Class => 'Modernize',
);
}
$Param{ResultFormStrg} = $LayoutObject->BuildSelection(
Data => {
Normal => Translatable('Normal'),
Print => Translatable('Print'),
CSV => Translatable('CSV'),
Excel => Translatable('Excel'),
},
Name => 'ResultForm',
SelectedID => $GetParam{ResultForm} || 'Normal',
Class => 'Modernize',
);
if ( $ConfigObject->Get('Ticket::ArchiveSystem') ) {
$Param{SearchInArchiveStrg} = $LayoutObject->BuildSelection(
Data => {
ArchivedTickets => Translatable('Archived tickets'),
NotArchivedTickets => Translatable('Unarchived tickets'),
AllTickets => Translatable('All tickets'),
},
Name => 'SearchInArchive',
SelectedID => $GetParam{SearchInArchive} || 'NotArchivedTickets',
Class => 'Modernize',
);
}
my %Profiles = $SearchProfileObject->SearchProfileList(
Base => 'TicketSearch',
UserLogin => $Self->{UserLogin},
);
if ( $Profiles{'last-search'} ) {
$Profiles{'last-search'} = $LayoutObject->{LanguageObject}->Translate('last-search');
}
$Param{ProfilesStrg} = $LayoutObject->BuildSelection(
Data => \%Profiles,
Name => 'Profile',
ID => 'SearchProfile',
SelectedID => $Profile,
Class => 'Modernize',
Translation => 0,
PossibleNone => 1,
);
$Param{StatesStrg} = $LayoutObject->BuildSelection(
Data => {
$StateObject->StateList(
UserID => $Self->{UserID},
Action => $Self->{Action},
),
},
Name => 'StateIDs',
Multiple => 1,
Size => 5,
SelectedID => $GetParam{StateIDs},
Class => 'Modernize',
);
my %AllQueues = $Kernel::OM->Get('Kernel::System::Queue')->GetAllQueues(
UserID => $Self->{UserID},
Type => 'ro',
);
$Param{QueuesStrg} = $LayoutObject->AgentQueueListOption(
Data => \%AllQueues,
Size => 5,
Multiple => 1,
Name => 'QueueIDs',
TreeView => $TreeView,
SelectedIDRefArray => $GetParam{QueueIDs},
OnChangeSubmit => 0,
Class => 'Modernize',
);
$Param{CreatedQueuesStrg} = $LayoutObject->AgentQueueListOption(
Data => \%AllQueues,
Size => 5,
Multiple => 1,
Name => 'CreatedQueueIDs',
TreeView => $TreeView,
SelectedIDRefArray => $GetParam{CreatedQueueIDs},
OnChangeSubmit => 0,
Class => 'Modernize',
);
$Param{PrioritiesStrg} = $LayoutObject->BuildSelection(
Data => {
$TicketObject->TicketPriorityList(
UserID => $Self->{UserID},
Action => $Self->{Action},
),
},
Name => 'PriorityIDs',
Multiple => 1,
Size => 5,
SelectedID => $GetParam{PriorityIDs},
Class => 'Modernize',
);
$Param{LocksStrg} = $LayoutObject->BuildSelection(
Data => {
$Kernel::OM->Get('Kernel::System::Lock')->LockList(
UserID => $Self->{UserID},
Action => $Self->{Action},
),
},
Name => 'LockIDs',
Multiple => 1,
Size => 5,
SelectedID => $GetParam{LockIDs},
Class => 'Modernize',
);
$Param{ArticleCreateTimePoint} = $LayoutObject->BuildSelection(
Data => [ 1 .. 59 ],
Name => 'ArticleCreateTimePoint',
SelectedID => $GetParam{ArticleCreateTimePoint},
);
$Param{ArticleCreateTimePointStart} = $LayoutObject->BuildSelection(
Data => {
'Last' => Translatable('within the last ...'),
'Before' => Translatable('more than ... ago'),
},
Name => 'ArticleCreateTimePointStart',
SelectedID => $GetParam{ArticleCreateTimePointStart} || 'Last',
);
$Param{ArticleCreateTimePointFormat} = $LayoutObject->BuildSelection(
Data => {
minute => Translatable('minute(s)'),
hour => Translatable('hour(s)'),
day => Translatable('day(s)'),
week => Translatable('week(s)'),
month => Translatable('month(s)'),
year => Translatable('year(s)'),
},
Name => 'ArticleCreateTimePointFormat',
SelectedID => $GetParam{ArticleCreateTimePointFormat},
);
$Param{ArticleCreateTimeStart} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'ArticleCreateTimeStart',
Format => 'DateInputFormat',
DiffTime => -( ( 60 * 60 * 24 ) * 30 ),
Validate => 1,
ValidateDateBeforePrefix => 'ArticleCreateTimeStop',
);
$Param{ArticleCreateTimeStop} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'ArticleCreateTimeStop',
Format => 'DateInputFormat',
Validate => 1,
ValidateDateAfterPrefix => 'ArticleCreateTimeStart',
);
$Param{TicketCreateTimePoint} = $LayoutObject->BuildSelection(
Data => [ 1 .. 59 ],
Name => 'TicketCreateTimePoint',
SelectedID => $GetParam{TicketCreateTimePoint},
);
$Param{TicketCreateTimePointStart} = $LayoutObject->BuildSelection(
Data => {
'Last' => Translatable('within the last ...'),
'Before' => Translatable('more than ... ago'),
},
Name => 'TicketCreateTimePointStart',
SelectedID => $GetParam{TicketCreateTimePointStart} || 'Last',
);
$Param{TicketCreateTimePointFormat} = $LayoutObject->BuildSelection(
Data => {
minute => Translatable('minute(s)'),
hour => Translatable('hour(s)'),
day => Translatable('day(s)'),
week => Translatable('week(s)'),
month => Translatable('month(s)'),
year => Translatable('year(s)'),
},
Name => 'TicketCreateTimePointFormat',
SelectedID => $GetParam{TicketCreateTimePointFormat},
);
$Param{TicketCreateTimeStart} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketCreateTimeStart',
Format => 'DateInputFormat',
DiffTime => -( ( 60 * 60 * 24 ) * 30 ),
Validate => 1,
ValidateDateBeforePrefix => 'TicketCreateTimeStop',
);
$Param{TicketCreateTimeStop} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketCreateTimeStop',
Format => 'DateInputFormat',
Validate => 1,
ValidateDateAfterPrefix => 'TicketCreateTimeStart',
);
$Param{TicketPendingTimePoint} = $LayoutObject->BuildSelection(
Data => [ 1 .. 59 ],
Name => 'TicketPendingTimePoint',
SelectedID => $GetParam{TicketPendingTimePoint},
);
$Param{TicketPendingTimePointStart} = $LayoutObject->BuildSelection(
Data => {
'Last' => Translatable('within the last ...'),
'Next' => Translatable('within the next ...'),
'Before' => Translatable('more than ... ago'),
'After' => Translatable('in more than ...'),
},
Name => 'TicketPendingTimePointStart',
SelectedID => $GetParam{TicketPendingTimePointStart} || 'Next',
);
$Param{TicketPendingTimePointFormat} = $LayoutObject->BuildSelection(
Data => {
minute => Translatable('minute(s)'),
hour => Translatable('hour(s)'),
day => Translatable('day(s)'),
week => Translatable('week(s)'),
month => Translatable('month(s)'),
year => Translatable('year(s)'),
},
Name => 'TicketPendingTimePointFormat',
SelectedID => $GetParam{TicketPendingTimePointFormat},
);
$Param{TicketPendingTimeStart} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketPendingTimeStart',
Format => 'DateInputFormat',
DiffTime => -( ( 60 * 60 * 24 ) * 30 ),
Validate => 1,
ValidateDateBeforePrefix => 'TicketPendingTimeStop',
);
$Param{TicketPendingTimeStop} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketPendingTimeStop',
Format => 'DateInputFormat',
Validate => 1,
ValidateDateAfterPrefix => 'TicketPendingTimeStart',
);
$Param{TicketChangeTimePoint} = $LayoutObject->BuildSelection(
Data => [ 1 .. 59 ],
Name => 'TicketChangeTimePoint',
SelectedID => $GetParam{TicketChangeTimePoint},
);
$Param{TicketChangeTimePointStart} = $LayoutObject->BuildSelection(
Data => {
'Last' => 'within the last ...',
'Before' => 'more than ... ago',
},
Name => 'TicketChangeTimePointStart',
SelectedID => $GetParam{TicketChangeTimePointStart} || 'Last',
);
$Param{TicketChangeTimePointFormat} = $LayoutObject->BuildSelection(
Data => {
minute => Translatable('minute(s)'),
hour => Translatable('hour(s)'),
day => Translatable('day(s)'),
week => Translatable('week(s)'),
month => Translatable('month(s)'),
year => Translatable('year(s)'),
},
Name => 'TicketChangeTimePointFormat',
SelectedID => $GetParam{TicketChangeTimePointFormat},
);
$Param{TicketChangeTimeStart} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketChangeTimeStart',
Format => 'DateInputFormat',
DiffTime => -( ( 60 * 60 * 24 ) * 30 ),
Validate => 1,
ValidateDateBeforePrefix => 'TicketChangeTimeStop',
);
$Param{TicketChangeTimeStop} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketChangeTimeStop',
Format => 'DateInputFormat',
Validate => 1,
ValidateDateAfterPrefix => 'TicketChangeTimeStart',
);
$Param{TicketCloseTimePoint} = $LayoutObject->BuildSelection(
Data => [ 1 .. 59 ],
Name => 'TicketCloseTimePoint',
SelectedID => $GetParam{TicketCloseTimePoint},
);
$Param{TicketCloseTimePointStart} = $LayoutObject->BuildSelection(
Data => {
'Last' => Translatable('within the last ...'),
'Before' => Translatable('more than ... ago'),
},
Name => 'TicketCloseTimePointStart',
SelectedID => $GetParam{TicketCloseTimePointStart} || 'Last',
);
$Param{TicketCloseTimePointFormat} = $LayoutObject->BuildSelection(
Data => {
minute => Translatable('minute(s)'),
hour => Translatable('hour(s)'),
day => Translatable('day(s)'),
week => Translatable('week(s)'),
month => Translatable('month(s)'),
year => Translatable('year(s)'),
},
Name => 'TicketCloseTimePointFormat',
SelectedID => $GetParam{TicketCloseTimePointFormat},
);
$Param{TicketCloseTimeStart} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketCloseTimeStart',
Format => 'DateInputFormat',
DiffTime => -( ( 60 * 60 * 24 ) * 30 ),
Validate => 1,
ValidateDateBeforePrefix => 'TicketCloseTimeStop',
);
$Param{TicketCloseTimeStop} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketCloseTimeStop',
Format => 'DateInputFormat',
Validate => 1,
ValidateDateAfterPrefix => 'TicketCloseTimeStart',
);
$Param{TicketLastChangeTimePoint} = $LayoutObject->BuildSelection(
Data => [ 1 .. 59 ],
Name => 'TicketLastChangeTimePoint',
SelectedID => $GetParam{TicketLastChangeTimePoint},
);
$Param{TicketLastChangeTimePointStart} = $LayoutObject->BuildSelection(
Data => {
'Last' => Translatable('within the last ...'),
'Before' => Translatable('more than ... ago'),
},
Name => 'TicketLastChangeTimePointStart',
SelectedID => $GetParam{TicketLastChangeTimePointStart} || 'Last',
);
$Param{TicketLastChangeTimePointFormat} = $LayoutObject->BuildSelection(
Data => {
minute => Translatable('minute(s)'),
hour => Translatable('hour(s)'),
day => Translatable('day(s)'),
week => Translatable('week(s)'),
month => Translatable('month(s)'),
year => Translatable('year(s)'),
},
Name => 'TicketLastChangeTimePointFormat',
SelectedID => $GetParam{TicketLastChangeTimePointFormat},
);
$Param{TicketLastChangeTimeStart} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketLastChangeTimeStart',
Format => 'DateInputFormat',
DiffTime => -( ( 60 * 60 * 24 ) * 30 ),
Validate => 1,
ValidateDateBeforePrefix => 'TicketLastChangeTimeStop',
);
$Param{TicketLastChangeTimeStop} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketLastChangeTimeStop',
Format => 'DateInputFormat',
Validate => 1,
ValidateDateAfterPrefix => 'TicketLastChangeTimeStart',
);
$Param{TicketEscalationTimePoint} = $LayoutObject->BuildSelection(
Data => [ 1 .. 59 ],
Name => 'TicketEscalationTimePoint',
SelectedID => $GetParam{TicketEscalationTimePoint},
);
$Param{TicketEscalationTimePointStart} = $LayoutObject->BuildSelection(
Data => {
'Last' => Translatable('within the last ...'),
'Next' => Translatable('within the next ...'),
'Before' => Translatable('more than ... ago'),
},
Name => 'TicketEscalationTimePointStart',
SelectedID => $GetParam{TicketEscalationTimePointStart} || 'Last',
);
$Param{TicketEscalationTimePointFormat} = $LayoutObject->BuildSelection(
Data => {
minute => Translatable('minute(s)'),
hour => Translatable('hour(s)'),
day => Translatable('day(s)'),
week => Translatable('week(s)'),
month => Translatable('month(s)'),
year => Translatable('year(s)'),
},
Name => 'TicketEscalationTimePointFormat',
SelectedID => $GetParam{TicketEscalationTimePointFormat},
);
$Param{TicketEscalationTimeStart} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketEscalationTimeStart',
Format => 'DateInputFormat',
DiffTime => -( ( 60 * 60 * 24 ) * 30 ),
Validate => 1,
ValidateDateBeforePrefix => 'TicketEscalationTimeStop',
);
$Param{TicketEscalationTimeStop} = $LayoutObject->BuildDateSelection(
%GetParam,
Prefix => 'TicketEscalationTimeStop',
Format => 'DateInputFormat',
Validate => 1,
ValidateDateAfterPrefix => 'TicketEscalationTimeStart',
);
my %GetParamBackup = %GetParam;
for my $Key (
qw(TicketEscalation TicketClose TicketChange TicketLastChange TicketPending TicketCreate ArticleCreate)
)
{
for my $SubKey (qw(TimeStart TimeStop TimePoint TimePointStart TimePointFormat)) {
delete $GetParam{ $Key . $SubKey };
delete $GetParamBackup{ $Key . $SubKey };
}
}
# build type string
if ( $ConfigObject->Get('Ticket::Type') ) {
# get ticket object
my %Type = $TicketObject->TicketTypeList(
UserID => $Self->{UserID},
Action => $Self->{Action},
);
$Param{TypesStrg} = $LayoutObject->BuildSelection(
Data => \%Type,
Name => 'TypeIDs',
SelectedID => $GetParam{TypeIDs},
Sort => 'AlphanumericValue',
Size => 3,
Multiple => 1,
Translation => 0,
Class => 'Modernize',
);
}
# html search mask output
$LayoutObject->Block(
Name => 'SearchAJAX',
Data => {
%Param,
%GetParam,
EmptySearch => $EmptySearch,
},
);
# create the field entries to be displayed in the modal dialog
for my $ArticleFieldKey ( sort keys %ArticleSearchableFields ) {
$LayoutObject->Block(
Name => 'SearchableArticleField',
Data => {
ArticleFieldLabel => $ArticleSearchableFields{$ArticleFieldKey}->{Label},
ArticleFieldKey => $ArticleSearchableFields{$ArticleFieldKey}->{Key},
ArticleFieldValue => $GetParam{$ArticleFieldKey} // '',
},
);
}
# output Dynamic fields blocks
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{$DynamicField} ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
# get search field preferences
my $SearchFieldPreferences = $BackendObject->SearchFieldPreferences(
DynamicFieldConfig => $DynamicFieldConfig,
);
next DYNAMICFIELD if !IsArrayRefWithData($SearchFieldPreferences);
PREFERENCE:
for my $Preference ( @{$SearchFieldPreferences} ) {
# skip fields that HTML could not be retrieved
next PREFERENCE if !IsHashRefWithData(
$DynamicFieldHTML{ $DynamicFieldConfig->{Name} . $Preference->{Type} }
);
$LayoutObject->Block(
Name => 'DynamicField',
Data => {
Label =>
$DynamicFieldHTML{ $DynamicFieldConfig->{Name} . $Preference->{Type} }
->{Label},
Field =>
$DynamicFieldHTML{ $DynamicFieldConfig->{Name} . $Preference->{Type} }
->{Field},
},
);
}
}
# compat. map for attributes
my %Map = (
TimeSearchType => 'TicketCreate',
ChangeTimeSearchType => 'TicketChange',
CloseTimeSearchType => 'TicketClose',
LastChangeTimeSearchType => 'TicketLastChange',
PendingTimeSearchType => 'TicketPending',
EscalationTimeSearchType => 'TicketEscalation',
ArticleTimeSearchType => 'ArticleCreate',
);
KEY:
for my $Key ( sort keys %Map ) {
next KEY if !defined $GetParamBackup{$Key};
if ( $GetParamBackup{$Key} eq 'TimePoint' ) {
$GetParamBackup{ $Map{$Key} . 'TimePoint' } = 1;
}
elsif ( $GetParamBackup{$Key} eq 'TimeSlot' ) {
$GetParamBackup{ $Map{$Key} . 'TimeSlot' } = 1;
}
}
# attributes for search
my @SearchAttributes;
# show attributes
my @ShownAttributes;
if ( $GetParamBackup{ShownAttributes} ) {
@ShownAttributes = split /;/, $GetParamBackup{ShownAttributes};
}
my %AlreadyShown;
if ($Profile) {
ITEM:
for my $Item (@Attributes) {
my $Key = $Item->{Key};
next ITEM if !$Key;
# check if shown
if (@ShownAttributes) {
my $Show = 0;
SHOWN_ATTRIBUTE:
for my $ShownAttribute (@ShownAttributes) {
if ( 'Label' . $Key eq $ShownAttribute ) {
$Show = 1;
last SHOWN_ATTRIBUTE;
}
}
next ITEM if !$Show;
}
else {
# Skip undefined
next ITEM if !defined $GetParamBackup{$Key};
# Skip empty strings
next ITEM if $GetParamBackup{$Key} eq '';
# Skip empty arrays
if ( ref $GetParamBackup{$Key} eq 'ARRAY' && !@{ $GetParamBackup{$Key} } ) {
next ITEM;
}
}
# show attribute
next ITEM if $AlreadyShown{$Key};
$AlreadyShown{$Key} = 1;
push @SearchAttributes, $Key;
}
}
# No profile, show default screen
else {
# Merge regular show/hide settings and the settings for the dynamic fields
my %Defaults = %{ $Config->{Defaults} || {} };
for my $DynamicFields ( sort keys %{ $Config->{DynamicField} || {} } ) {
if ( $Config->{DynamicField}->{$DynamicFields} == 2 ) {
$Defaults{"Search_DynamicField_$DynamicFields"} = 1;
}
}
my @OrderedDefaults;
if (%Defaults) {
# ordering attributes on the same order like in Attributes
for my $Item (@Attributes) {
my $KeyAtr = $Item->{Key};
for my $Key ( sort keys %Defaults ) {
if ( $Key eq $KeyAtr ) {
push @OrderedDefaults, $Key;
}
}
}
KEY:
for my $Key (@OrderedDefaults) {
next KEY if $Key eq 'DynamicField'; # Ignore entry for DF config
next KEY if $AlreadyShown{$Key};
$AlreadyShown{$Key} = 1;
push @SearchAttributes, $Key;
}
}
# If no attribute is shown, show fulltext search.
if ( !keys %AlreadyShown ) {
push @SearchAttributes, 'Fulltext';
}
}
$LayoutObject->AddJSData(
Key => 'SearchAttributes',
Value => \@SearchAttributes,
);
my $Output = $LayoutObject->Output(
TemplateFile => 'AgentTicketSearch',
Data => \%Param,
AJAX => 1,
);
return $LayoutObject->Attachment(
NoCache => 1,
ContentType => 'text/html',
Charset => $LayoutObject->{UserCharset},
Content => $Output,
Type => 'inline',
);
}
# show default search screen
$Output = $LayoutObject->Header();
$Output .= $LayoutObject->NavigationBar();
# Notify if there are tickets which are not updated.
$Output .= $LayoutObject->NotifyNonUpdatedTickets() // '';
$LayoutObject->AddJSData(
Key => 'NonAJAXSearch',
Value => 1,
);
if ( $Self->{Profile} ) {
$LayoutObject->AddJSData(
Key => 'Profile',
Value => $Self->{Profile},
);
}
$Output .= $LayoutObject->Output(
TemplateFile => 'AgentTicketSearch',
Data => \%Param,
);
$Output .= $LayoutObject->Footer();
return $Output;
}
1;