# -- # Kernel/System/Ticket.pm - all ticket functions # Copyright (C) 2001-2008 OTRS AG, http://otrs.org/ # -- # $Id: Ticket.pm,v 1.346.2.6 2008/12/10 12:49:33 martin Exp $ # -- # 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 http://www.gnu.org/licenses/gpl-2.0.txt. # -- package Kernel::System::Ticket; use strict; use warnings; use File::Path; use Kernel::System::Ticket::Article; use Kernel::System::Type; use Kernel::System::State; use Kernel::System::Priority; use Kernel::System::Service; use Kernel::System::SLA; use Kernel::System::Lock; use Kernel::System::Queue; use Kernel::System::User; use Kernel::System::Group; use Kernel::System::Cache; use Kernel::System::CustomerUser; use Kernel::System::CustomerGroup; use Kernel::System::Encode; use Kernel::System::Email; use Kernel::System::AutoResponse; use Kernel::System::StdAttachment; use Kernel::System::PostMaster::LoopProtection; use Kernel::System::Notification; use Kernel::System::LinkObject; use Kernel::System::Valid; use vars qw(@ISA $VERSION); $VERSION = qw($Revision: 1.346.2.6 $) [1]; =head1 NAME Kernel::System::Ticket - ticket lib =head1 SYNOPSIS All ticket functions. =head1 PUBLIC INTERFACE =over 4 =cut =item new() create an object use Kernel::Config; use Kernel::System::Time; use Kernel::System::Log; use Kernel::System::Main; use Kernel::System::DB; use Kernel::System::Ticket; my $ConfigObject = Kernel::Config->new(); my $LogObject = Kernel::System::Log->new( ConfigObject => $ConfigObject, ); my $TimeObject = Kernel::System::Time->new( LogObject => $LogObject, ConfigObject => $ConfigObject, ); my $MainObject = Kernel::System::Main->new( LogObject => $LogObject, ConfigObject => $ConfigObject, ); my $DBObject = Kernel::System::DB->new( ConfigObject => $ConfigObject, MainObject => $MainObject, LogObject => $LogObject, ); my $TicketObject = Kernel::System::Ticket->new( ConfigObject => $ConfigObject, LogObject => $LogObject, DBObject => $DBObject, MainObject => $MainObject, TimeObject => $TimeObject, ); =cut sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {}; bless( $Self, $Type ); # 0=off; 1=on; $Self->{Debug} = $Param{Debug} || 0; # set @ISA @ISA = ('Kernel::System::Ticket::Article'); # get needed objects for (qw(ConfigObject LogObject TimeObject DBObject MainObject)) { if ( $Param{$_} ) { $Self->{$_} = $Param{$_}; } else { die "Got no $_!"; } } # create common needed module objects if ( !$Param{EncodeObject} ) { $Self->{EncodeObject} = Kernel::System::Encode->new(%Param); } else { $Self->{EncodeObject} = $Param{EncodeObject}; } $Self->{UserObject} = Kernel::System::User->new(%Param); if ( !$Param{GroupObject} ) { $Self->{GroupObject} = Kernel::System::Group->new(%Param); } else { $Self->{GroupObject} = $Param{GroupObject}; } $Self->{CustomerUserObject} = Kernel::System::CustomerUser->new(%Param); if ( !$Param{CustomerGroupObject} ) { $Self->{CustomerGroupObject} = Kernel::System::CustomerGroup->new(%Param); } else { $Self->{CustomerGroupObject} = $Param{CustomerGroupObject}; } if ( !$Param{QueueObject} ) { $Self->{QueueObject} = Kernel::System::Queue->new(%Param); } else { $Self->{QueueObject} = $Param{QueueObject}; } $Self->{SendmailObject} = Kernel::System::Email->new(%Param); $Self->{AutoResponse} = Kernel::System::AutoResponse->new(%Param); $Self->{LoopProtectionObject} = Kernel::System::PostMaster::LoopProtection->new(%Param); $Self->{StdAttachmentObject} = Kernel::System::StdAttachment->new(%Param); $Self->{TypeObject} = Kernel::System::Type->new(%Param); $Self->{PriorityObject} = Kernel::System::Priority->new(%Param); $Self->{ServiceObject} = Kernel::System::Service->new(%Param); $Self->{SLAObject} = Kernel::System::SLA->new(%Param); $Self->{StateObject} = Kernel::System::State->new(%Param); $Self->{LockObject} = Kernel::System::Lock->new(%Param); $Self->{NotificationObject} = Kernel::System::Notification->new(%Param); $Self->{ValidObject} = Kernel::System::Valid->new(%Param); $Self->{LinkObject} = Kernel::System::LinkObject->new(%Param); # get viewable states list my @ViewableStates = $Self->{StateObject}->StateGetStatesByType( Type => 'Viewable', Result => 'Name', ); $Self->{ViewableStates} = \@ViewableStates; my @ViewableStateIDs = $Self->{StateObject}->StateGetStatesByType( Type => 'Viewable', Result => 'ID', ); $Self->{ViewableStateIDs} = \@ViewableStateIDs; my @ViewableLocks = $Self->{LockObject}->LockViewableLock( Type => 'Name' ); $Self->{ViewableLocks} = \@ViewableLocks; my @ViewableLockIDs = $Self->{LockObject}->LockViewableLock( Type => 'ID' ); $Self->{ViewableLockIDs} = \@ViewableLockIDs; # get basic config settings $Self->{Sendmail} = $Self->{ConfigObject}->Get('Sendmail'); $Self->{SendmailBcc} = $Self->{ConfigObject}->Get('SendmailBcc'); $Self->{FQDN} = $Self->{ConfigObject}->Get('FQDN'); $Self->{Organization} = $Self->{ConfigObject}->Get('Organization'); # load ticket number generator my $GeneratorModule = $Self->{ConfigObject}->Get('Ticket::NumberGenerator') || 'Kernel::System::Ticket::Number::AutoIncrement'; if ( !$Self->{MainObject}->Require($GeneratorModule) ) { die "Can't load ticket number generator backend module $GeneratorModule! $@"; } push @ISA, $GeneratorModule; # load ticket index generator my $GeneratorIndexModule = $Self->{ConfigObject}->Get('Ticket::IndexModule') || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB'; if ( !$Self->{MainObject}->Require($GeneratorIndexModule) ) { die "Can't load ticket index backend module $GeneratorIndexModule! $@"; } push @ISA, $GeneratorIndexModule; # load article storage module my $StorageModule = $Self->{ConfigObject}->Get('Ticket::StorageModule') || 'Kernel::System::Ticket::ArticleStorageDB'; if ( !$Self->{MainObject}->Require($StorageModule) ) { die "Can't load ticket storage backend module $StorageModule! $@"; } push @ISA, $StorageModule; # load article search index module my $SearchIndexModule = $Self->{ConfigObject}->Get('Ticket::SearchIndexModule') || 'Kernel::System::Ticket::ArticleSearchIndex::RuntimeDB'; if ( !$Self->{MainObject}->Require($SearchIndexModule) ) { die "Can't load ticket search index backend module $SearchIndexModule! $@"; } push @ISA, $SearchIndexModule; # load ticket extension modules my $CustomModule = $Self->{ConfigObject}->Get('Ticket::CustomModule'); if ($CustomModule) { if ( !$Self->{MainObject}->Require($CustomModule) ) { die "Can't load ticket custom module $CustomModule! $@"; } push @ISA, $CustomModule; } $Self->ArticleStorageInit(); return $Self; } =item TicketCreateNumber() creates a new ticket number my $TicketNumber = $TicketObject->TicketCreateNumber(); =cut # use it from Kernel/System/Ticket/Number/*.pm =item TicketCheckNumber() checks if ticket number exists, returns ticket id if number exists my $TicketID = $TicketObject->TicketCheckNumber( Tn => '200404051004575', ); =cut sub TicketCheckNumber { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{Tn} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TN!' ); return; } # db query return if !$Self->{DBObject}->Prepare( SQL => 'SELECT id FROM ticket WHERE tn = ?', Bind => [ \$Param{Tn} ], ); my $Id; while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Id = $Row[0]; } # get main ticket id if ticket has been merged return if !$Id; my %Ticket = $Self->TicketGet( TicketID => $Id ); return $Id if $Ticket{StateType} ne 'merged'; my @Lines = $Self->HistoryGet( TicketID => $Ticket{TicketID}, UserID => 1 ); for my $Data ( reverse @Lines ) { if ( $Data->{HistoryType} eq 'Merged' ) { if ( $Data->{Name} =~ /^.*\(\d+?\/(\d+?)\)$/ ) { return $1; } } } return $Id; } =item TicketCreate() creates a new ticket my $TicketID = $TicketObject->TicketCreate( Title => 'Some Ticket Title', Queue => 'Raw', # or QueueID => 123, Lock => 'unlock', Priority => '3 normal', # or PriorityID => 2, State => 'new', # or StateID => 5, CustomerID => '123465', CustomerUser => 'customer@example.com', OwnerID => 123, UserID => 123, ); or my $TicketID = $TicketObject->TicketCreate( TN => $TicketObject->TicketCreateNumber(), # optional Title => 'Some Ticket Title', Queue => 'Raw', # or QueueID => 123, Lock => 'unlock', Priority => '3 normal', # or PriorityID => 2, State => 'new', # or StateID => 5, Type => 'normal', # or TypeID => 1, not required Service => 'Service A', # or ServiceID => 1, not required SLA => 'SLA A', # or SALID => 1, not required CustomerID => '123465', CustomerUser => 'customer@example.com', OwnerID => 123, ResponsibleID => 123, # not required UserID => 123, ); =cut sub TicketCreate { my ( $Self, %Param ) = @_; my $GroupID = $Param{GroupID} || 1; my $ValidID = $Param{ValidID} || 1; my $Age = $Self->{TimeObject}->SystemTime(); # check needed stuff for (qw(OwnerID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # set default values if no values are specified if ( !$Param{ResponsibleID} ) { $Param{ResponsibleID} = 1; } if ( !$Param{TypeID} && !$Param{Type} ) { $Param{TypeID} = 1; } # TypeID/Type lookup! if ( !$Param{TypeID} && $Param{Type} ) { $Param{TypeID} = $Self->{TypeObject}->TypeLookup( Type => $Param{Type} ); } elsif ( $Param{TypeID} && !$Param{Type} ) { $Param{Type} = $Self->{TypeObject}->TypeLookup( TypeID => $Param{TypeID} ); } if ( !$Param{TypeID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No TypeID for '$Param{Type}'!", ); return; } # QueueID/Queue lookup! if ( !$Param{QueueID} && $Param{Queue} ) { $Param{QueueID} = $Self->{QueueObject}->QueueLookup( Queue => $Param{Queue} ); } elsif ( !$Param{Queue} ) { $Param{Queue} = $Self->{QueueObject}->QueueLookup( QueueID => $Param{QueueID} ); } if ( !$Param{QueueID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No QueueID for '$Param{Queue}'!", ); return; } # StateID/State lookup! if ( !$Param{StateID} ) { my %State = $Self->{StateObject}->StateGet( Name => $Param{State} ); $Param{StateID} = $State{ID}; } elsif ( !$Param{State} ) { my %State = $Self->{StateObject}->StateGet( ID => $Param{StateID} ); $Param{State} = $State{Name}; } if ( !$Param{StateID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No StateID for '$Param{State}'!", ); return; } # LockID lookup! if ( !$Param{LockID} && $Param{Lock} ) { $Param{LockID} = $Self->{LockObject}->LockLookup( Lock => $Param{Lock} ); } if ( !$Param{LockID} && !$Param{Lock} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'No LockID and no LockType!', ); return; } # PriorityID/Priority lookup! if ( !$Param{PriorityID} && $Param{Priority} ) { $Param{PriorityID} = $Self->{PriorityObject}->PriorityLookup( Priority => $Param{Priority}, ); } elsif ( $Param{PriorityID} && !$Param{Priority} ) { $Param{Priority} = $Self->{PriorityObject}->PriorityLookup( PriorityID => $Param{PriorityID}, ); } if ( !$Param{PriorityID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'No PriorityID (invalid Priority Name?)!', ); return; } # ServiceID/Service lookup! if ( !$Param{ServiceID} && $Param{Service} ) { $Param{ServiceID} = $Self->{ServiceObject}->ServiceLookup( Name => $Param{Service}, ); } elsif ( $Param{ServiceID} && !$Param{Service} ) { $Param{Service} = $Self->{ServiceObject}->ServiceLookup( ServiceID => $Param{ServiceID}, ); } # SLAID/SLA lookup! if ( !$Param{SLAID} && $Param{SLA} ) { $Param{SLAID} = $Self->{SLAObject}->SLALookup( Name => $Param{SLA} ); } elsif ( $Param{SLAID} && !$Param{SLA} ) { $Param{SLA} = $Self->{SLAObject}->SLALookup( SLAID => $Param{SLAID} ); } # create ticket number if none is given if ( !$Param{TN} ) { $Param{TN} = $Self->TicketCreateNumber(); } # check ticket title if ( !defined $Param{Title} ) { $Param{Title} = ''; } # check database undef/NULL (set value to undef/NULL to prevent database errors) for (qw(ServiceID SLAID)) { if ( !$Param{$_} ) { $Param{$_} = undef; } } # create db record return if !$Self->{DBObject}->Do( SQL => 'INSERT INTO ticket (tn, title, create_time_unix, type_id, queue_id, ticket_lock_id,' . ' user_id, responsible_user_id, group_id, ticket_priority_id, ticket_state_id,' . ' ticket_answered, escalation_time, escalation_update_time, escalation_response_time,' . ' escalation_solution_time, timeout, service_id, sla_id,' . ' valid_id, create_time, create_by, change_time, change_by)' . ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, 0, 0, ?, ?, ?,' . ' current_timestamp, ?, current_timestamp, ?)', Bind => [ \$Param{TN}, \$Param{Title}, \$Age, \$Param{TypeID}, \$Param{QueueID}, \$Param{LockID}, \$Param{OwnerID}, \$Param{ResponsibleID}, \$GroupID, \$Param{PriorityID}, \$Param{StateID}, \$Param{ServiceID}, \$Param{SLAID}, \$ValidID, \$Param{UserID}, \$Param{UserID}, ], ); # get ticket id my $TicketID = $Self->TicketIDLookup( TicketNumber => $Param{TN}, UserID => $Param{UserID}, ); # add history entry $Self->HistoryAdd( TicketID => $TicketID, QueueID => $Param{QueueID}, HistoryType => 'NewTicket', Name => "\%\%$Param{TN}\%\%$Param{Queue}\%\%$Param{Priority}\%\%$Param{State}\%\%$TicketID", CreateUserID => $Param{UserID}, ); # set customer data if given if ( $Param{CustomerNo} || $Param{CustomerID} || $Param{CustomerUser} ) { $Self->SetCustomerData( TicketID => $TicketID, No => $Param{CustomerNo} || $Param{CustomerID} || '', User => $Param{CustomerUser} || '', UserID => $Param{UserID}, ); } # update ticket view index $Self->TicketAcceleratorAdd( TicketID => $TicketID ); # log ticket creation $Self->{LogObject}->Log( Priority => 'notice', Message => "New Ticket [$Param{TN}/" . substr( $Param{Title}, 0, 15 ) . "] created " . "(TicketID=$TicketID,Queue=$Param{Queue},Priority=$Param{Priority},State=$Param{State})", ); # trigger ticket event $Self->TicketEventHandlerPost( Event => 'TicketCreate', UserID => $Param{UserID}, TicketID => $TicketID, ); return $TicketID; } =item TicketDelete() deletes a ticket with articles from storage $TicketObject->TicketDelete( TicketID => 123, UserID => 123, ); =cut sub TicketDelete { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # delete ticket links $Self->{LinkObject}->LinkDeleteAll( Object => 'Ticket', Key => $Param{TicketID}, UserID => $Param{UserID}, ); # update ticket index return if !$Self->TicketAcceleratorDelete(%Param); # remove ticket watcher return if !$Self->{DBObject}->Do( SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ?', Bind => [ \$Param{TicketID} ], ); # delete ticket_history return if !$Self->HistoryDelete( TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); # delete article, attachments and plain emails my @Articles = $Self->ArticleIndex( TicketID => $Param{TicketID} ); for my $ArticleID (@Articles) { return if !$Self->ArticleDelete( ArticleID => $ArticleID, %Param, ); } # delete ticket return if !$Self->{DBObject}->Do( SQL => 'DELETE FROM ticket WHERE id = ?', Bind => [ \$Param{TicketID} ], ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketDelete', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketIDLookup() ticket id lookup by ticket number my $TicketID = $TicketObject->TicketIDLookup( TicketNumber => '2004040510440485', UserID => 123, ); =cut sub TicketIDLookup { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{TicketNumber} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TicketNumber!' ); return; } # db query $Self->{DBObject}->Prepare( SQL => 'SELECT id FROM ticket WHERE tn = ?', Bind => [ \$Param{TicketNumber} ], ); my $ID; while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $ID = $Row[0]; } return $ID; } =item TicketNumberLookup() ticket number lookup by ticket id my $TicketNumber = $TicketObject->TicketNumberLookup( TicketID => 123, UserID => 123, ); =cut sub TicketNumberLookup { my ( $Self, %Param ) = @_; my $Tn = ''; # check needed stuff if ( !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TicketID!' ); return; } my %Ticket = $Self->TicketGet(%Param); if ( !%Ticket ) { return; } return $Ticket{TicketNumber}; } =item TicketSubjectBuild() rebuild a new ticket subject my $NewSubject = $TicketObject->TicketSubjectBuild( TicketNumber => '2004040510440485', Subject => $OldSubject, ); a new subject like "RE: [Ticket# 2004040510440485] Some subject" will be generated my $NewSubject = $TicketObject->TicketSubjectBuild( TicketNumber => '2004040510440485', Subject => $OldSubject, Type => 'New', ); a new subject like "[Ticket# 2004040510440485] Some subject" (without RE: ) will be generated =cut sub TicketSubjectBuild { my ( $Self, %Param ) = @_; my $Subject = $Param{Subject} || ''; # check needed stuff for (qw(TicketNumber)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } $Subject = $Self->TicketSubjectClean(%Param); my $TicketHook = $Self->{ConfigObject}->Get('Ticket::Hook'); my $TicketHookDivider = $Self->{ConfigObject}->Get('Ticket::HookDivider'); my $TicketSubjectRe = $Self->{ConfigObject}->Get('Ticket::SubjectRe'); if ( $Param{Type} && $Param{Type} eq 'New' ) { $Subject = "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject; } else { $Subject = "$TicketSubjectRe: [$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject; } return $Subject; } =item TicketSubjectClean() strip/clean up a ticket subject my $NewSubject = $TicketObject->TicketSubjectClean( TicketNumber => '2004040510440485', Subject => $OldSubject, ); =cut sub TicketSubjectClean { my ( $Self, %Param ) = @_; my $Subject = $Param{Subject} || ''; # check needed stuff for (qw(TicketNumber)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get ticket data my $TicketHook = $Self->{ConfigObject}->Get('Ticket::Hook'); my $TicketHookDivider = $Self->{ConfigObject}->Get('Ticket::HookDivider'); my $TicketSubjectSize = $Self->{ConfigObject}->Get('Ticket::SubjectSize') || 120; my $TicketSubjectRe = $Self->{ConfigObject}->Get('Ticket::SubjectRe'); $Subject =~ s/\[$TicketHook: $Param{TicketNumber}\] //g; $Subject =~ s/\[$TicketHook:$Param{TicketNumber}\] //g; $Subject =~ s/\[$TicketHook$TicketHookDivider$Param{TicketNumber}\] //g; if ( $Self->{ConfigObject}->Get('Ticket::SubjectCleanAllNumbers') ) { $Subject =~ s/\[$TicketHook$TicketHookDivider\d+?\] //g; } $Subject =~ s/$TicketHook: $Param{TicketNumber} //g; $Subject =~ s/$TicketHook:$Param{TicketNumber} //g; $Subject =~ s/$TicketHook$TicketHookDivider$Param{TicketNumber} //g; if ( $Self->{ConfigObject}->Get('Ticket::SubjectCleanAllNumbers') ) { $Subject =~ s/$TicketHook$TicketHookDivider\d+? //g; } $Subject =~ s/^(..(\[\d+\])?: )+//; $Subject =~ s/^($TicketSubjectRe(\[\d+\])?: )+//; $Subject =~ s/^(.{$TicketSubjectSize}).*$/$1 [...]/; return $Subject; } =item TicketGet() get ticket info (TicketNumber, TicketID, State, StateID, StateType, Priority, PriorityID, Lock, LockID, Queue, QueueID, CustomerID, CustomerUserID, Owner, OwnerID, Type, TypeID, SLA, SLAID, Service, ServiceID, Responsible, ResponsibleID, Created, Changed, TicketFreeKey1-16, TicketFreeText1-16, TicketFreeTime1-6, ...) my %Ticket = $TicketObject->TicketGet( TicketID => 123, UserID => 123, ); =cut sub TicketGet { my ( $Self, %Param ) = @_; my %Ticket = (); # check needed stuff if ( !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TicketID!' ); return; } # check if result is cached if ( $Self->{ 'Cache::GetTicket' . $Param{TicketID} } ) { return %{ $Self->{ 'Cache::GetTicket' . $Param{TicketID} } }; } # db query my $SQL = 'SELECT st.id, st.queue_id, sq.name, st.ticket_state_id, st.ticket_lock_id,' . ' sp.id, sp.name, st.create_time_unix, st.create_time, sq.group_id, st.tn,' . ' st.customer_id, st.customer_user_id, st.user_id, st.responsible_user_id, st.until_time,' . ' st.freekey1, st.freetext1, st.freekey2, st.freetext2,' . ' st.freekey3, st.freetext3, st.freekey4, st.freetext4,' . ' st.freekey5, st.freetext5, st.freekey6, st.freetext6,' . ' st.freekey7, st.freetext7, st.freekey8, st.freetext8,' . ' st.freekey9, st.freetext9, st.freekey10, st.freetext10,' . ' st.freekey11, st.freetext11, st.freekey12, st.freetext12,' . ' st.freekey13, st.freetext13, st.freekey14, st.freetext14,' . ' st.freekey15, st.freetext15, st.freekey16, st.freetext16,' . ' st.freetime1, st.freetime2, st.freetime3, st.freetime4,' . ' st.freetime5, st.freetime6,' . ' st.change_time, st.title, st.escalation_update_time, st.timeout,' . ' st.type_id, st.service_id, st.sla_id, st.escalation_response_time,' . ' st.escalation_solution_time, st.escalation_time' . ' FROM ticket st, ticket_priority sp, queue sq' . ' WHERE sp.id = st.ticket_priority_id AND sq.id = st.queue_id AND st.id = ?'; $Self->{DBObject}->Prepare( SQL => $SQL, Bind => [ \$Param{TicketID} ] ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Ticket{TicketID} = $Row[0]; $Ticket{Title} = $Row[55]; $Ticket{QueueID} = $Row[1]; $Ticket{Queue} = $Row[2]; $Ticket{StateID} = $Row[3]; $Ticket{LockID} = $Row[4]; $Ticket{PriorityID} = $Row[5]; $Ticket{Priority} = $Row[6]; $Ticket{Age} = $Self->{TimeObject}->SystemTime() - $Row[7]; $Ticket{CreateTimeUnix} = $Row[7]; $Ticket{Created} = $Self->{TimeObject}->SystemTime2TimeStamp( SystemTime => $Row[7] ); $Ticket{EscalationTime} = $Row[63]; $Ticket{EscalationUpdateTime} = $Row[56]; $Ticket{EscalationResponseTime} = $Row[61]; $Ticket{EscalationSolutionTime} = $Row[62]; $Ticket{UnlockTimeout} = $Row[57]; $Ticket{GroupID} = $Row[9]; $Ticket{TicketNumber} = $Row[10]; $Ticket{CustomerID} = $Row[11]; $Ticket{CustomerUserID} = $Row[12]; $Ticket{OwnerID} = $Row[13]; $Ticket{ResponsibleID} = $Row[14] || 1; $Ticket{RealTillTimeNotUsed} = $Row[15]; $Ticket{TypeID} = $Row[58] || 1; $Ticket{ServiceID} = $Row[59] || ''; $Ticket{SLAID} = $Row[60] || ''; $Ticket{TicketFreeKey1} = defined( $Row[16] ) ? $Row[16] : ''; $Ticket{TicketFreeText1} = defined( $Row[17] ) ? $Row[17] : ''; $Ticket{TicketFreeKey2} = defined( $Row[18] ) ? $Row[18] : ''; $Ticket{TicketFreeText2} = defined( $Row[19] ) ? $Row[19] : ''; $Ticket{TicketFreeKey3} = defined( $Row[20] ) ? $Row[20] : ''; $Ticket{TicketFreeText3} = defined( $Row[21] ) ? $Row[21] : ''; $Ticket{TicketFreeKey4} = defined( $Row[22] ) ? $Row[22] : ''; $Ticket{TicketFreeText4} = defined( $Row[23] ) ? $Row[23] : ''; $Ticket{TicketFreeKey5} = defined( $Row[24] ) ? $Row[24] : ''; $Ticket{TicketFreeText5} = defined( $Row[25] ) ? $Row[25] : ''; $Ticket{TicketFreeKey6} = defined( $Row[26] ) ? $Row[26] : ''; $Ticket{TicketFreeText6} = defined( $Row[27] ) ? $Row[27] : ''; $Ticket{TicketFreeKey7} = defined( $Row[28] ) ? $Row[28] : ''; $Ticket{TicketFreeText7} = defined( $Row[29] ) ? $Row[29] : ''; $Ticket{TicketFreeKey8} = defined( $Row[30] ) ? $Row[30] : ''; $Ticket{TicketFreeText8} = defined( $Row[31] ) ? $Row[31] : ''; $Ticket{TicketFreeKey9} = defined( $Row[32] ) ? $Row[32] : ''; $Ticket{TicketFreeText9} = defined( $Row[33] ) ? $Row[33] : ''; $Ticket{TicketFreeKey10} = defined( $Row[34] ) ? $Row[34] : ''; $Ticket{TicketFreeText10} = defined( $Row[35] ) ? $Row[35] : ''; $Ticket{TicketFreeKey11} = defined( $Row[36] ) ? $Row[36] : ''; $Ticket{TicketFreeText11} = defined( $Row[37] ) ? $Row[37] : ''; $Ticket{TicketFreeKey12} = defined( $Row[38] ) ? $Row[38] : ''; $Ticket{TicketFreeText12} = defined( $Row[39] ) ? $Row[39] : ''; $Ticket{TicketFreeKey13} = defined( $Row[40] ) ? $Row[40] : ''; $Ticket{TicketFreeText13} = defined( $Row[41] ) ? $Row[41] : ''; $Ticket{TicketFreeKey14} = defined( $Row[42] ) ? $Row[42] : ''; $Ticket{TicketFreeText14} = defined( $Row[43] ) ? $Row[43] : ''; $Ticket{TicketFreeKey15} = defined( $Row[44] ) ? $Row[44] : ''; $Ticket{TicketFreeText15} = defined( $Row[45] ) ? $Row[45] : ''; $Ticket{TicketFreeKey16} = defined( $Row[46] ) ? $Row[46] : ''; $Ticket{TicketFreeText16} = defined( $Row[47] ) ? $Row[47] : ''; $Ticket{TicketFreeTime1} = defined( $Row[48] ) ? $Row[48] : ''; $Ticket{TicketFreeTime2} = defined( $Row[49] ) ? $Row[49] : ''; $Ticket{TicketFreeTime3} = defined( $Row[50] ) ? $Row[50] : ''; $Ticket{TicketFreeTime4} = defined( $Row[51] ) ? $Row[51] : ''; $Ticket{TicketFreeTime5} = defined( $Row[52] ) ? $Row[52] : ''; $Ticket{TicketFreeTime6} = defined( $Row[53] ) ? $Row[53] : ''; } # check ticket if ( !$Ticket{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No such TicketID ($Param{TicketID})!", ); return; } # cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000 # and 0000-00-00 00:00:00 time stamps) for my $Time ( 1 .. 6 ) { if ( $Ticket{ 'TicketFreeTime' . $Time } ) { if ( $Ticket{ 'TicketFreeTime' . $Time } eq '0000-00-00 00:00:00' ) { $Ticket{ 'TicketFreeTime' . $Time } = ''; next; } $Ticket{ 'TicketFreeTime' . $Time } =~ s/^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\..+?$/$1/; } } # get owner $Ticket{Owner} = $Self->{UserObject}->UserLookup( UserID => $Ticket{OwnerID}, ); # get responsible $Ticket{Responsible} = $Self->{UserObject}->UserLookup( UserID => $Ticket{ResponsibleID}, ); # get lock $Ticket{Lock} = $Self->{LockObject}->LockLookup( LockID => $Ticket{LockID} ); # get service $Ticket{Type} = $Self->{TypeObject}->TypeLookup( TypeID => $Ticket{TypeID} ); # get service if ( $Ticket{ServiceID} ) { $Ticket{Service} = $Self->{ServiceObject}->ServiceLookup( ServiceID => $Ticket{ServiceID}, ); } # get sla if ( $Ticket{SLAID} ) { $Ticket{SLA} = $Self->{SLAObject}->SLALookup( SLAID => $Ticket{SLAID} ); } # get state info my %StateData = $Self->{StateObject}->StateGet( ID => $Ticket{StateID} ); $Ticket{StateType} = $StateData{TypeName}; $Ticket{State} = $StateData{Name}; if ( !$Ticket{RealTillTimeNotUsed} || $StateData{TypeName} !~ /^pending/i ) { $Ticket{UntilTime} = 0; } else { $Ticket{UntilTime} = $Ticket{RealTillTimeNotUsed} - $Self->{TimeObject}->SystemTime(); } # get escalation attributes my %Escalation = $Self->TicketEscalationDateCalculation( Ticket => \%Ticket, UserID => $Param{UserID} || 1, ); for my $Key ( keys %Escalation ) { $Ticket{$Key} = $Escalation{$Key}; } # cache user result $Self->{ 'Cache::GetTicket' . $Param{TicketID} } = \%Ticket; # return ticket data return %Ticket; } =item TicketTitleUpdate() update ticket title $TicketObject->TicketTitleUpdate( Title => 'Some Title', TicketID => 123, UserID => 1, ); =cut sub TicketTitleUpdate { my ( $Self, %Param ) = @_; # check needed stuff for (qw(Title TicketID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # check if update is needed my %Ticket = $Self->TicketGet( TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); if ( defined $Ticket{Title} && $Ticket{Title} eq $Param{Title} ) { return 1; } # db access return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET title = ? WHERE id = ?', Bind => [ \$Param{Title}, \$Param{TicketID} ], ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # ticket event $Self->TicketEventHandlerPost( Event => 'TicketTitleUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketUnlockTimeoutUpdate() update ticket unlock time to now $TicketObject->TicketUnlockTimeoutUpdate( UnlockTimeout => $TimeObject->SystemTime(), TicketID => 123, UserID => 143, ); =cut sub TicketUnlockTimeoutUpdate { my ( $Self, %Param ) = @_; # check needed stuff for (qw(UnlockTimeout TicketID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # check if update is needed my %Ticket = $Self->TicketGet(%Param); if ( $Ticket{UnlockTimeout} eq $Param{UnlockTimeout} ) { return 1; } # reset unlock time return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET timeout = ? WHERE id = ?', Bind => [ \$Param{UnlockTimeout}, \$Param{TicketID} ], ); # reset ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # add history $Self->HistoryAdd( TicketID => $Param{TicketID}, CreateUserID => $Param{UserID}, HistoryType => 'Misc', Name => 'Reset of unlock time.', ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketUnlockTimeoutUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketQueueID() get ticket queue id my $QueueID = $TicketObject->TicketQueueID( TicketID => 123, ); =cut sub TicketQueueID { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TicketID!' ); return; } my %Ticket = $Self->TicketGet( TicketID => $Param{TicketID}, UserID => 1 ); if ( !%Ticket ) { return; } return $Ticket{QueueID}; } =item MoveList() to get the move queue list for a ticket (depends on workflow, if configured) my %Queues = $TicketObject->MoveList( Type => 'create', UserID => 123, ); my %Queues = $TicketObject->MoveList( QueueID => 123, UserID => 123, ); my %Queues = $TicketObject->MoveList( TicketID => 123, UserID => 123, ); =cut sub MoveList { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!', ); return; } # check needed stuff if ( !$Param{QueueID} && !$Param{TicketID} && !$Param{Type} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need QueueID, TicketID or Type!', ); return; } my %Queues = (); if ( $Param{UserID} && $Param{UserID} eq 1 ) { %Queues = $Self->{QueueObject}->GetAllQueues(); } else { %Queues = $Self->{QueueObject}->GetAllQueues(%Param); } # workflow if ( $Self->TicketAcl( %Param, ReturnType => 'Ticket', ReturnSubType => 'Queue', Data => \%Queues, ) ) { return $Self->TicketAclData(); } return %Queues; } =item MoveTicket() to move a ticket (send notification to agents of selected my queues, it ticket isn't closed) $TicketObject->MoveTicket( QueueID => 123, TicketID => 123, UserID => 123, ); $TicketObject->MoveTicket( Queue => 'Some Queue Name', TicketID => 123, UserID => 123, ); $TicketObject->MoveTicket( Queue => 'Some Queue Name', TicketID => 123, Comment => 'some comment', # optional ForceNotificationToUserID => [1,43,56], # if you want to force somebody UserID => 123, ); Optional attribute: SendNoNotification, disable or enable agent and customer notification for this action. Otherwise a notification will be send to agent and cusomer. For example: SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) =cut sub MoveTicket { my ( $Self, %Param ) = @_; # queue lookup if ( $Param{Queue} && !$Param{QueueID} ) { $Param{QueueID} = $Self->{QueueObject}->QueueLookup( Queue => $Param{Queue} ); } # check needed stuff for (qw(TicketID QueueID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get current ticket my %Ticket = $Self->TicketGet(%Param); # move needed? if ( $Param{QueueID} == $Ticket{QueueID} && !$Param{Comment} ) { # update not needed return 1; } # permission check my %MoveList = $Self->MoveList( %Param, Type => 'move_into' ); if ( !$MoveList{ $Param{QueueID} } ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied on TicketID: $Param{TicketID}!", ); return; } return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET queue_id = ? WHERE id = ?', Bind => [ \$Param{QueueID}, \$Param{TicketID} ], ); # queue lookup my $Queue = $Self->{QueueObject}->QueueLookup( QueueID => $Param{QueueID} ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # update ticket view index $Self->TicketAcceleratorUpdate( TicketID => $Param{TicketID} ); # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, QueueID => $Ticket{QueueID}, HistoryType => 'Move', Name => "\%\%$Queue\%\%$Param{QueueID}\%\%$Ticket{Queue}\%\%$Ticket{QueueID}", CreateUserID => $Param{UserID}, ); # send move notify to queue subscriber if ( !$Param{SendNoNotification} && $Ticket{StateType} ne 'closed' ) { my %Used = (); my @UserIDs = $Self->GetSubscribedUserIDsByQueueID( QueueID => $Param{QueueID} ); if ( $Param{ForceNotificationToUserID} ) { push @UserIDs, @{ $Param{ForceNotificationToUserID} }; } for my $UserID (@UserIDs) { if ( !$Used{$UserID} && $UserID ne $Param{UserID} ) { $Used{$UserID} = 1; my %UserData = $Self->{UserObject}->GetUserData( UserID => $UserID, Cached => 1, Valid => 1, ); if ( $UserData{UserSendMoveNotification} ) { # send agent notification $Self->SendAgentNotification( Type => 'Move', UserData => \%UserData, CustomerMessageParams => { Queue => $Queue, Body => $Param{Comment} || '', }, TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } } } # send customer notification email my %Preferences = $Self->{UserObject}->GetUserData( UserID => $Param{UserID} ); $Self->SendCustomerNotification( Type => 'QueueUpdate', CustomerMessageParams => { %Preferences, Queue => $Queue }, TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } # ticket event $Self->TicketEventHandlerPost( Event => 'TicketQueueUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item MoveQueueList() returns a list of used queue ids / names my @QueueIDList = $TicketObject->MoveQueueList( TicketID => 123, Type => 'ID', ); my @QueueList = $TicketObject->MoveQueueList( TicketID => 123, Type => 'Name', ); =cut sub MoveQueueList { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # db query $Self->{DBObject}->Prepare( SQL => 'SELECT sh.name, ht.name, sh.create_by, sh.queue_id FROM ' . ' ticket_history sh, ticket_history_type ht WHERE ' . ' sh.ticket_id = ? AND ' . ' ht.name IN (\'Move\', \'NewTicket\') AND ' . ' ht.id = sh.history_type_id' . ' ORDER BY sh.id', Bind => [ \$Param{TicketID} ], ); my @QueueID = (); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { # store result if ( $Row[1] eq 'NewTicket' ) { if ( $Row[3] ) { push @QueueID, $Row[3]; } } elsif ( $Row[1] eq 'Move' ) { if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) { push @QueueID, $2; } elsif ( $Row[0] =~ /^Ticket moved to Queue '.+?' \(ID=(.+?)\)/ ) { push @QueueID, $1; } } } # queue lookup my @QueueName = (); for my $QueueID (@QueueID) { my $Queue = $Self->{QueueObject}->QueueLookup( QueueID => $QueueID ); push @QueueName, $Queue; } if ( $Param{Type} && $Param{Type} eq 'Name' ) { return @QueueName; } else { return @QueueID; } } =item TicketTypeList() to get all possible types for a ticket (depends on workflow, if configured) my %Types = $TicketObject->TicketTypeList( UserID => 123, ); my %Types = $TicketObject->TicketTypeList( QueueID => 123, UserID => 123, ); my %Types = $TicketObject->TicketTypeList( TicketID => 123, UserID => 123, ); =cut sub TicketTypeList { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!' ); return; } # check needed stuff # if (!$Param{QueueID} && !$Param{TicketID}) { # $Self->{LogObject}->Log(Priority => 'error', Message => 'Need QueueID or TicketID!'); # return; # } my %Types = $Self->{TypeObject}->TypeList( Valid => 1 ); # workflow if ( $Self->TicketAcl( %Param, ReturnType => 'Ticket', ReturnSubType => 'Type', Data => \%Types, ) ) { return $Self->TicketAclData(); } return %Types; } =item TicketTypeSet() to set a ticket type $TicketObject->TicketTypeSet( TypeID => 123, TicketID => 123, UserID => 123, ); $TicketObject->TicketTypeSet( Type => 'normal', TicketID => 123, UserID => 123, ); =cut sub TicketTypeSet { my ( $Self, %Param ) = @_; # type lookup if ( $Param{Type} && !$Param{TypeID} ) { $Param{TypeID} = $Self->{TypeObject}->TypeLookup( Type => $Param{Type} ); } # check needed stuff for (qw(TicketID TypeID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get current ticket my %Ticket = $Self->TicketGet(%Param); # update needed? return 1 if ( $Param{TypeID} == $Ticket{TypeID} ); # permission check my %TypeList = $Self->TicketTypeList(%Param); if ( !$TypeList{ $Param{TypeID} } ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied on TicketID: $Param{TicketID}!", ); return; } return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET type_id = ? WHERE id = ?', Bind => [ \$Param{TypeID}, \$Param{TicketID} ], ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # get new ticket data my %TicketNew = $Self->TicketGet(%Param); $TicketNew{Type} = $TicketNew{Type} || 'NULL'; $Param{TypeID} = $Param{TypeID} || ''; $Ticket{Type} = $Ticket{Type} || 'NULL'; $Ticket{TypeID} = $Ticket{TypeID} || ''; # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, HistoryType => 'TypeUpdate', Name => "\%\%$TicketNew{Type}\%\%$Param{TypeID}\%\%$Ticket{Type}\%\%$Ticket{TypeID}", CreateUserID => $Param{UserID}, ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketTypeUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketServiceList() to get all possible services for a ticket (depends on workflow, if configured) my %Services = $TicketObject->TicketServiceList( QueueID => 123, UserID => 123, ); my %Services = $TicketObject->TicketServiceList( CustomerUserID => 123, QueueID => 123, ); my %Services = $TicketObject->TicketServiceList( CustomerUserID => 123, TicketID => 123, UserID => 123, ); =cut sub TicketServiceList { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID, CustomerUserID or UserID and CustomerUserID is needed!', ); return; } # check needed stuff if ( !$Param{QueueID} && !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need QueueID or TicketID!', ); return; } my %Services = (); if ( !$Param{CustomerUserID} ) { %Services = $Self->{ServiceObject}->ServiceList( UserID => 1, ); } else { %Services = $Self->{ServiceObject}->CustomerUserServiceMemberList( Result => 'HASH', CustomerUserLogin => $Param{CustomerUserID}, UserID => 1, ); } # workflow if ( $Self->TicketAcl( %Param, ReturnType => 'Ticket', ReturnSubType => 'Service', Data => \%Services, ) ) { return $Self->TicketAclData(); } return %Services; } =item TicketServiceSet() to set a ticket service $TicketObject->TicketServiceSet( ServiceID => 123, TicketID => 123, UserID => 123, ); $TicketObject->TicketServiceSet( Service => 'Service A', TicketID => 123, UserID => 123, ); =cut sub TicketServiceSet { my ( $Self, %Param ) = @_; # service lookup if ( $Param{Service} && !$Param{ServiceID} ) { $Param{ServiceID} = $Self->{ServiceObject}->ServiceLookup( Name => $Param{Service} ); } # check needed stuff for (qw(TicketID ServiceID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get current ticket my %Ticket = $Self->TicketGet(%Param); # update needed? return 1 if ( $Param{ServiceID} eq $Ticket{ServiceID} ); # permission check my %ServiceList = $Self->TicketServiceList(%Param); if ( $Param{ServiceID} ne '' && !$ServiceList{ $Param{ServiceID} } ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied on TicketID: $Param{TicketID}!", ); return; } # check database undef/NULL (set value to undef/NULL to prevent database errors) for (qw(ServiceID SLAID)) { if ( !$Param{$_} ) { $Param{$_} = undef; } } return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET service_id = ? WHERE id = ?', Bind => [ \$Param{ServiceID}, \$Param{TicketID} ], ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # get new ticket data my %TicketNew = $Self->TicketGet(%Param); $TicketNew{Service} = $TicketNew{Service} || 'NULL'; $Param{ServiceID} = $Param{ServiceID} || ''; $Ticket{Service} = $Ticket{Service} || 'NULL'; $Ticket{ServiceID} = $Ticket{ServiceID} || ''; # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, HistoryType => 'ServiceUpdate', Name => "\%\%$TicketNew{Service}\%\%$Param{ServiceID}\%\%$Ticket{Service}\%\%$Ticket{ServiceID}", CreateUserID => $Param{UserID}, ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketServiceUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } sub TicketEscalationDateCalculation { my ( $Self, %Param ) = @_; # check needed stuff for (qw(Ticket UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } my %Ticket = %{ $Param{Ticket} }; my %Escalation = (); if ( $Self->{ConfigObject}->Get('Ticket::Service') && $Ticket{SLAID} ) { %Escalation = $Self->{SLAObject}->SLAGet( SLAID => $Ticket{SLAID}, UserID => $Param{UserID}, Cache => 1, ); } else { %Escalation = $Self->{QueueObject}->QueueGet( ID => $Ticket{QueueID}, UserID => $Param{UserID}, Cache => 1, ); } # return if we do not have any escalation attributes my %Map = ( EscalationResponseTime => 'FirstResponse', EscalationUpdateTime => 'Update', EscalationSolutionTime => 'Solution', ); my $EscalationAttribute; for my $Key ( keys %Map ) { if ( $Escalation{ $Map{$Key} . 'Time' } ) { $EscalationAttribute = 1; last; } } return if !$EscalationAttribute; my $Time = $Self->{TimeObject}->SystemTime(); my %Data; for my $Key ( keys %Map ) { if ( $Ticket{$Key} ) { # do not escalations in "pending auto" for escalation update time if ( $Key eq 'EscalationUpdateTime' && $Ticket{StateType} =~ /^(pending)/i ) { next; } my $WorkingTime = 0; my $TimeDiff = $Ticket{$Key} - $Time; # ticket is not escalated till now if ( $TimeDiff > 0 ) { $WorkingTime = $Self->{TimeObject}->WorkingTime( StartTime => $Time, StopTime => $Ticket{$Key}, Calendar => $Escalation{Calendar}, ); # set notification if notfy % is reached if ( $Escalation{ $Map{$Key} . 'Notify' } ) { my $Reached = 100 - ( $WorkingTime / ( $Escalation{ $Map{$Key} . 'Time' } * 60 / 100 ) ); if ( $Reached >= $Escalation{ $Map{$Key} . 'Notify' } ) { $Data{ $Map{$Key} . 'TimeNotification' } = 1; } } } # ticket is overtime else { $WorkingTime = $Self->{TimeObject}->WorkingTime( StartTime => $Ticket{$Key}, StopTime => $Time, Calendar => $Escalation{Calendar}, ); $WorkingTime = "-$WorkingTime"; # set escalation $Data{ $Map{$Key} . 'TimeEscalation' } = 1; } my $DestinationDate = $Self->{TimeObject}->SystemTime2TimeStamp( SystemTime => $Ticket{$Key}, ); $Data{ $Map{$Key} . 'TimeDestinationTime' } = $Ticket{$Key}; $Data{ $Map{$Key} . 'TimeDestinationDate' } = $DestinationDate; $Data{ $Map{$Key} . 'TimeWorkingTime' } = $WorkingTime; $Data{ $Map{$Key} . 'Time' } = $TimeDiff; # set escalation attributes if ( !$Data{EscalationDestinationTime} || $Data{EscalationDestinationTime} > $Ticket{$Key} ) { $Data{EscalationDestinationTime} = $Ticket{$Key}; $Data{EscalationDestinationDate} = $DestinationDate; $Data{EscalationTimeWorkingTime} = $WorkingTime; $Data{EscalationTime} = $TimeDiff; # escalation time in readable way $Data{EscalationDestinationIn} = ''; if ( $WorkingTime >= 3600 ) { $Data{EscalationDestinationIn} .= int( $WorkingTime / 3600 ) . 'h '; } if ( $WorkingTime <= 3600 || int( ( $WorkingTime / 60 ) % 60 ) ) { $Data{EscalationDestinationIn} .= int( ( $WorkingTime / 60 ) % 60 ) . 'm'; } } } } return %Data; } sub TicketEscalationIndexBuild { my ( $Self, %Param ) = @_; my %Ticket = $Self->TicketGet( TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); # do no escalations on (merge|close|remove) tickets if ( $Ticket{StateType} =~ /^(merge|close|remove)/i ) { # update escalation times with 0 my %EscalationTimes = ( EscalationTime => 'escalation_time', EscalationResponseTime => 'escalation_response_time', EscalationUpdateTime => 'escalation_update_time', EscalationSolutionTime => 'escalation_solution_time', ); my $Time = 0; for my $Key ( keys %EscalationTimes ) { # check if table update is needed if ( !$Ticket{$Key} ) { next; } # update ticket table $Self->{DBObject}->Do( SQL => "UPDATE ticket SET $EscalationTimes{$Key} = ? WHERE id = ?", Bind => [ \$Time, \$Ticket{TicketID}, ] ); } return 1; } # find escalation times my %Escalation = (); my $EscalationTime = 0; if ( $Self->{ConfigObject}->Get('Ticket::Service') && $Ticket{SLAID} ) { %Escalation = $Self->{SLAObject}->SLAGet( SLAID => $Ticket{SLAID}, UserID => $Param{UserID}, Cache => 1, ); } else { %Escalation = $Self->{QueueObject}->QueueGet( ID => $Ticket{QueueID}, UserID => $Param{UserID}, Cache => 1, ); } # update first response (if not responded till now) my $FirstResponseTime = 0; if ( !$Escalation{FirstResponseTime} ) { $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_response_time = ? WHERE id = ?', Bind => [ \$FirstResponseTime, \$Ticket{TicketID}, ] ); } else { # check if first response is already done $Self->{DBObject}->Prepare( SQL => 'SELECT a.create_time,a.id FROM' . ' article a, article_sender_type ast, article_type art' . ' WHERE' . ' a.article_sender_type_id = ast.id AND' . ' a.article_type_id = art.id AND' . ' a.ticket_id = ? AND' . ' ast.name = \'agent\' AND' . ' (art.name LIKE \'email-ext%\' OR art.name LIKE \'note-ext%\' OR art.name = \'phone\' OR art.name = \'fax\' OR art.name = \'sms\')' . ' ORDER BY a.create_time', Bind => [ \$Ticket{TicketID} ], Limit => 1, ); my $FirstResponseDone = 0; while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $FirstResponseDone = 1; } # update first response time to 0 if ($FirstResponseDone) { $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_response_time = ? WHERE id = ?', Bind => [ \$FirstResponseTime, \$Ticket{TicketID}, ] ); } # update first response time to expected escaltion destination time else { my $DestinationTime = $Self->{TimeObject}->DestinationTime( StartTime => $Self->{TimeObject}->TimeStamp2SystemTime( String => $Ticket{Created} ), Time => $Escalation{FirstResponseTime} * 60, Calendar => $Escalation{Calendar}, ); # update first response time to $DestinationTime $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_response_time = ? WHERE id = ?', Bind => [ \$DestinationTime, \$Ticket{TicketID}, ] ); # remember escalation time $EscalationTime = $DestinationTime; } } # update update && do not escalate in "pending auto" for escalation update time my $UpdateTime = 0; if ( !$Escalation{UpdateTime} || $Ticket{StateType} =~ /^(pending)/i ) { $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_update_time = ? WHERE id = ?', Bind => [ \$UpdateTime, \$Ticket{TicketID}, ] ); } else { # check if update escalation should be set my @SenderHistory; $Self->{DBObject}->Prepare( SQL => 'SELECT article_sender_type_id, article_type_id, create_time FROM ' . 'article WHERE ticket_id = ? ORDER BY create_time ASC', Bind => [ \$Param{TicketID} ], ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { push @SenderHistory, { SenderTypeID => $Row[0], ArticleTypeID => $Row[1], Created => $Row[2], }; } # fill up lookups for my $Row (@SenderHistory) { # get sender type $Row->{SenderType} = $Self->ArticleSenderTypeLookup( SenderTypeID => $Row->{SenderTypeID}, ); # get article type $Row->{ArticleType} = $Self->ArticleTypeLookup( ArticleTypeID => $Row->{ArticleTypeID}, ); } # get latest customer contact time my $LastSenderTime; my $LastSenderType = ''; for my $Row ( reverse @SenderHistory ) { # fill up latest sender time (as initial value) if ( !$LastSenderTime ) { $LastSenderTime = $Row->{Created}; } # do not use locked tickets for calc. # last if $Ticket{Lock} eq 'lock'; # do not use /int/ article types for calc. next if $Row->{ArticleType} =~ /int/i; # do not use /agent|customer/ article types for calc. next if $Row->{SenderType} !~ /^(agent|customer)$/; # last if latest was customer and the next was not customer # otherwise use also next, older customer article as latest # customer followup for starting escalation if ( $Row->{SenderType} eq 'agent' && $LastSenderType eq 'customer' ) { last; } # start escalation on latest customer article if ( $Row->{SenderType} eq 'customer' ) { $LastSenderType = 'customer'; $LastSenderTime = $Row->{Created}; } # start escalation on latest agent article if ( $Row->{SenderType} eq 'agent' ) { $LastSenderTime = $Row->{Created}; last; } } if ($LastSenderTime) { my $DestinationTime = $Self->{TimeObject}->DestinationTime( StartTime => $Self->{TimeObject}->TimeStamp2SystemTime( String => $LastSenderTime, ), Time => $Escalation{UpdateTime} * 60, Calendar => $Escalation{Calendar}, ); # update update time to $DestinationTime $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_update_time = ? WHERE id = ?', Bind => [ \$DestinationTime, \$Ticket{TicketID}, ] ); # remember escalation time if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) { $EscalationTime = $DestinationTime; } } # else, no not escalate, because latest sender was agent else { $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_update_time = ? WHERE id = ?', Bind => [ \$UpdateTime, \$Ticket{TicketID}, ] ); } } # update solution my $SolutionTime = 0; if ( !$Escalation{SolutionTime} ) { $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_solution_time = ? WHERE id = ?', Bind => [ \$SolutionTime, \$Ticket{TicketID}, ] ); } else { # find solution time / first close time my @StateIDs = $Self->{StateObject}->StateGetStatesByType( StateType => ['closed'], Result => 'ID', ); $Self->{DBObject}->Prepare( SQL => "SELECT create_time FROM ticket_history " . " WHERE ticket_id = ? AND state_id IN (${\(join ', ', @StateIDs)}) " . " ORDER BY create_time", Bind => [ \$Ticket{TicketID} ], Limit => 1, ); my $SolutionDone = 0; while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $SolutionDone = 1; } # update solution time to 0 if ($SolutionDone) { $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_solution_time = ? WHERE id = ?', Bind => [ \$SolutionTime, \$Ticket{TicketID}, ] ); } else { my $DestinationTime = $Self->{TimeObject}->DestinationTime( StartTime => $Self->{TimeObject}->TimeStamp2SystemTime( String => $Ticket{Created} ), Time => $Escalation{SolutionTime} * 60, Calendar => $Escalation{Calendar}, ); # update solution time to $DestinationTime $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_solution_time = ? WHERE id = ?', Bind => [ \$DestinationTime, \$Ticket{TicketID}, ] ); # remember escalation time if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) { $EscalationTime = $DestinationTime; } } } # update escalation time (< escalation time) if ( defined $EscalationTime ) { $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET escalation_time = ? WHERE id = ?', Bind => [ \$EscalationTime, \$Ticket{TicketID}, ] ); } return 1; } =item TicketSLAList() to get all possible SLAs for a ticket (depends on workflow, if configured) my %SLAs = $TicketObject->TicketSLAList( ServiceID => 1, UserID => 123, ); my %SLAs = $TicketObject->TicketSLAList( QueueID => 123, ServiceID => 1, UserID => 123, ); my %SLAs = $TicketObject->TicketSLAList( TicketID => 123, ServiceID => 1, UserID => 123, ); =cut sub TicketSLAList { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!' ); return; } # check needed stuff if ( !$Param{QueueID} && !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need QueueID or TicketID!' ); return; } # return emty hash, if no service id is given if ( !$Param{ServiceID} ) { return (); } # get sla list my %SLAs = $Self->{SLAObject}->SLAList( ServiceID => $Param{ServiceID}, UserID => 1, ); # workflow if ( $Self->TicketAcl( %Param, ReturnType => 'Ticket', ReturnSubType => 'SLA', Data => \%SLAs, ) ) { return $Self->TicketAclData(); } return %SLAs; } =item TicketSLASet() to set a ticket service $TicketObject->TicketSLASet( SLAID => 123, TicketID => 123, UserID => 123, ); $TicketObject->TicketSLASet( SLA => 'SLA A', TicketID => 123, UserID => 123, ); =cut sub TicketSLASet { my ( $Self, %Param ) = @_; # sla lookup if ( $Param{SLA} && !$Param{SLAID} ) { $Param{SLAID} = $Self->{SLAObject}->SLALookup( Name => $Param{SLA} ); } # check needed stuff for (qw(TicketID SLAID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get current ticket my %Ticket = $Self->TicketGet(%Param); # update needed? return 1 if ( $Param{SLAID} eq $Ticket{SLAID} ); # permission check my %SLAList = $Self->TicketSLAList( %Param, ServiceID => $Ticket{ServiceID}, ); if ( $Param{UserID} != 1 && $Param{SLAID} ne '' && !$SLAList{ $Param{SLAID} } ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied on TicketID: $Param{TicketID}!", ); return; } # check database undef/NULL (set value to undef/NULL to prevent database errors) for (qw(ServiceID SLAID)) { if ( !$Param{$_} ) { $Param{$_} = undef; } } return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET sla_id = ? WHERE id = ?', Bind => [ \$Param{SLAID}, \$Param{TicketID} ], ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # get new ticket data my %TicketNew = $Self->TicketGet(%Param); $TicketNew{SLA} = $TicketNew{SLA} || 'NULL'; $Param{SLAID} = $Param{SLAID} || ''; $Ticket{SLA} = $Ticket{SLA} || 'NULL'; $Ticket{SLAID} = $Ticket{SLAID} || ''; # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, HistoryType => 'SLAUpdate', Name => "\%\%$TicketNew{SLA}\%\%$Param{SLAID}\%\%$Ticket{SLA}\%\%$Ticket{SLAID}", CreateUserID => $Param{UserID}, ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketSLAUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item SetCustomerData() Set customer data of ticket. $TicketObject->SetCustomerData( No => 'client123', User => 'client-user-123', TicketID => 123, UserID => 23, ); =cut sub SetCustomerData { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } if ( !defined $Param{No} && !defined $Param{User} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need User or No for update!' ); return; } # db customer id update if ( defined $Param{No} ) { my $Ok = $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET customer_id = ?, ' . ' change_time = current_timestamp, change_by = ? WHERE id = ?', Bind => [ \$Param{No}, \$Param{UserID}, \$Param{TicketID} ] ); if ($Ok) { $Param{History} = "CustomerID=$Param{No};"; } } # db customer user update if ( defined $Param{User} ) { my $Ok = $Self->{DBObject}->Do( SQL => 'UPDATE ticket SET customer_user_id = ?, ' . 'change_time = current_timestamp, change_by = ? WHERE id = ?', Bind => [ \$Param{User}, \$Param{UserID}, \$Param{TicketID} ], ); if ($Ok) { $Param{History} .= "CustomerUser=$Param{User};"; } } # if no change if ( !$Param{History} ) { return; } # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, HistoryType => 'CustomerUpdate', Name => "\%\%" . $Param{History}, CreateUserID => $Param{UserID}, ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # ticket event $Self->TicketEventHandlerPost( Event => 'TicketCustomerUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketFreeTextGet() get _possible_ ticket free text options Note: the current value is accessible over TicketGet() my $HashRef = $TicketObject->TicketFreeTextGet( Type => 'TicketFreeText3', TicketID => 123, UserID => 123, # or CustomerUserID ); my $HashRef = $TicketObject->TicketFreeTextGet( Type => 'TicketFreeText3', UserID => 123, # or CustomerUserID ); # fill up with existing values my $HashRef = $TicketObject->TicketFreeTextGet( Type => 'TicketFreeText3', FillUp => 1, UserID => 123, # or CustomerUserID ); =cut sub TicketFreeTextGet { my ( $Self, %Param ) = @_; my $Value = $Param{Value} || ''; my $Key = $Param{Key} || ''; # check needed stuff for (qw(Type)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!', ); return; } # get config my %Data = (); if ( ref $Self->{ConfigObject}->Get( $Param{Type} ) eq 'HASH' ) { %Data = %{ $Self->{ConfigObject}->Get( $Param{Type} ) }; } # check existing if ( $Param{FillUp} && %Data ) { my $TimeStart = $Self->{TimeObject}->SystemTime(); my $Counter = $Param{Type}; $Counter =~ s/^.+?(\d+?)$/$1/; # check cache my $CacheObject = Kernel::System::Cache->new( %{$Self} ); my $CacheData = $CacheObject->Get( Type => 'TicketFreeTextLookup', Key => $Param{Type}, ); # do cache lookup if ( $CacheData && ref $CacheData eq 'HASH' ) { %Data = ( %{$CacheData}, %Data ); } # do database lookup elsif ( $Param{Type} =~ /text/i ) { $Self->{DBObject}->Prepare( SQL => "SELECT distinct(freetext$Counter) FROM ticket" ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { if ( $Row[0] && !$Data{ $Row[0] } ) { $Data{ $Row[0] } = $Row[0]; } } } else { $Self->{DBObject}->Prepare( SQL => "SELECT distinct(freekey$Counter) FROM ticket" ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { if ( $Row[0] && !$Data{ $Row[0] } ) { $Data{ $Row[0] } = $Row[0]; } } } # fill cache if ( !$CacheData && %Data ) { my $TimeEnd = $Self->{TimeObject}->SystemTime(); my $TTL = 10 * 60; if ( $TimeEnd - $TimeStart > 2 ) { $TTL = 8 * 60 * 60; } elsif ( $TimeEnd - $TimeStart > 1 ) { $TTL = 4 * 60 * 60; } elsif ( $TimeEnd - $TimeStart > 0 ) { $TTL = 2 * 60 * 60; } $CacheObject->Set( Type => 'TicketFreeTextLookup', Key => $Param{Type}, Value => \%Data, TTL => $TTL, ); } } # workflow if ( $Self->TicketAcl( %Param, ReturnType => 'Ticket', ReturnSubType => $Param{Type}, Data => \%Data, ) ) { my %Hash = $Self->TicketAclData(); return \%Hash; } if ( !%Data ) { return; } return \%Data; } =item TicketFreeTextSet() Set ticket free text. $TicketObject->TicketFreeTextSet( Counter => 1, Key => 'Planet', # optional Value => 'Sun', # optional TicketID => 123, UserID => 23, ); =cut sub TicketFreeTextSet { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID UserID Counter)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # check if update is needed my %Ticket = $Self->TicketGet( TicketID => $Param{TicketID} ); my $Value = ''; my $Key = ''; if ( defined $Param{Value} ) { $Value = $Param{Value}; } else { $Value = $Ticket{ 'TicketFreeText' . $Param{Counter} }; } if ( defined $Param{Key} ) { $Key = $Param{Key}; } else { $Key = $Ticket{ 'TicketFreeKey' . $Param{Counter} }; } if ( $Value eq $Ticket{"TicketFreeText$Param{Counter}"} && $Key eq $Ticket{"TicketFreeKey$Param{Counter}"} ) { return 1; } # db quote for (qw(Counter)) { $Param{$_} = $Self->{DBObject}->Quote( $Param{$_}, 'Integer' ); } # db update return if !$Self->{DBObject}->Do( SQL => "UPDATE ticket SET freekey$Param{Counter} = ?, freetext$Param{Counter} = ?, " . " change_time = current_timestamp, change_by = ? WHERE id = ?", Bind => [ \$Key, \$Value, \$Param{UserID}, \$Param{TicketID} ], ); # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, QueueID => $Ticket{QueueID}, HistoryType => 'TicketFreeTextUpdate', Name => "\%\%FreeKey$Param{Counter}\%\%$Key\%\%FreeText$Param{Counter}\%\%$Value", CreateUserID => $Param{UserID}, ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # ticket event $Self->TicketEventHandlerPost( Event => 'TicketFreeTextUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketFreeTimeSet() Set ticket free text. $TicketObject->TicketFreeTimeSet( Counter => 1, Prefix => 'TicketFreeTime', TicketFreeTime1Year => 1900, TicketFreeTime1Month => 12, TicketFreeTime1Day => 24, TicketFreeTime1Hour => 22, TicketFreeTime1Minute => 01, TicketID => 123, UserID => 23, ); =cut sub TicketFreeTimeSet { my ( $Self, %Param ) = @_; my $Prefix = $Param{'Prefix'} || 'TicketFreeTime'; # check needed stuff for (qw(TicketID UserID Counter)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } for (qw(Year Month Day Hour Minute)) { if ( !defined $Param{ $Prefix . $Param{Counter} . $_ } ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $Prefix" . $Param{Counter} . "$_!", ); return; } } # generate time stamp to compare if update is needed my $TimeStamp = sprintf( "%04d-%02d-%02d %02d:%02d:00", $Param{ $Prefix . $Param{Counter} . 'Year' }, $Param{ $Prefix . $Param{Counter} . 'Month' }, $Param{ $Prefix . $Param{Counter} . 'Day' }, $Param{ $Prefix . $Param{Counter} . 'Hour' }, $Param{ $Prefix . $Param{Counter} . 'Minute' }, ); if ( $TimeStamp eq '0000-00-00 00:00:00' ) { $TimeStamp = ''; } # check if update is needed my %Ticket = $Self->TicketGet( TicketID => $Param{TicketID} ); if ( $TimeStamp eq $Ticket{"TicketFreeTime$Param{Counter}"} ) { return 1; } # db update for (qw(Counter)) { $Param{$_} = $Self->{DBObject}->Quote( $Param{$_}, 'Integer' ); } if ( !$TimeStamp || $TimeStamp eq '0000-00-00 00:00:00' ) { $TimeStamp = undef; } return if !$Self->{DBObject}->Do( SQL => "UPDATE ticket SET freetime$Param{Counter} = ?, " . "change_time = current_timestamp, change_by = ? WHERE id = ?", Bind => [ \$TimeStamp, \$Param{UserID}, \$Param{TicketID} ], ); if ( !$TimeStamp ) { $TimeStamp = ''; } # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, QueueID => $Ticket{QueueID}, HistoryType => 'TicketFreeTextUpdate', Name => "\%\%FreeTime$Param{Counter}\%\%$TimeStamp", CreateUserID => $Param{UserID}, ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # ticket event $Self->TicketEventHandlerPost( Event => 'TicketFreeTimeUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item Permission() returns if the agent has permissions or no my $Access = $TicketObject->Permission( Type => 'ro', TicketID => 123, UserID => 123, ); or without logging, for example for to check if a link/action should be shown my $Access = $TicketObject->Permission( Type => 'ro', TicketID => 123, LogNo => 1, UserID => 123, ); =cut sub Permission { my ( $Self, %Param ) = @_; # check needed stuff for (qw(Type TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # run all TicketPermission modules if ( ref $Self->{ConfigObject}->Get('Ticket::Permission') eq 'HASH' ) { my %Modules = %{ $Self->{ConfigObject}->Get('Ticket::Permission') }; for my $Module ( sort keys %Modules ) { # log try of load module if ( $Self->{Debug} > 1 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Try to load module: $Modules{$Module}->{Module}!", ); } # load module if ( !$Self->{MainObject}->Require( $Modules{$Module}->{Module} ) ) { next; } # create object my $ModuleObject = $Modules{$Module}->{Module}->new( ConfigObject => $Self->{ConfigObject}, LogObject => $Self->{LogObject}, DBObject => $Self->{DBObject}, TicketObject => $Self, QueueObject => $Self->{QueueObject}, UserObject => $Self->{UserObject}, GroupObject => $Self->{GroupObject}, Debug => $Self->{Debug}, ); # execute Run() my $AccessOk = $ModuleObject->Run(%Param); # check granted option (should I say ok) if ( $AccessOk && $Modules{$Module}->{Granted} ) { if ( $Self->{Debug} > 0 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Granted access '$Param{Type}' true for " . "TicketID '$Param{TicketID}' " . "through $Modules{$Module}->{Module} (no more checks)!", ); } # access ok return 1; } # return because access is false but it's required if ( !$AccessOk && $Modules{$Module}->{Required} ) { if ( !$Param{LogNo} ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied because module " . "($Modules{$Module}->{Module}) is required " . "(UserID: $Param{UserID} '$Param{Type}' on " . "TicketID: $Param{TicketID})!", ); } # access not ok return; } } } # don't grant access to the ticket if ( !$Param{LogNo} ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied (UserID: $Param{UserID} '$Param{Type}' " . "on TicketID: $Param{TicketID})!", ); } return; } =item CustomerPermission() returns if the agent has permissions or no my $Access = $TicketObject->CustomerPermission( Type => 'ro', TicketID => 123, UserID => 123, ); or without logging, for example for to check if a link/action should be shown my $Access = $TicketObject->CustomerPermission( Type => 'ro', TicketID => 123, LogNo => 1, UserID => 123, ); =cut sub CustomerPermission { my ( $Self, %Param ) = @_; # check needed stuff for (qw(Type TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # run all CustomerTicketPermission modules if ( ref $Self->{ConfigObject}->Get('CustomerTicket::Permission') eq 'HASH' ) { my %Modules = %{ $Self->{ConfigObject}->Get('CustomerTicket::Permission') }; for my $Module ( sort keys %Modules ) { # log try of load module if ( $Self->{Debug} > 1 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Try to load module: $Modules{$Module}->{Module}!", ); } # load module if ( !$Self->{MainObject}->Require( $Modules{$Module}->{Module} ) ) { next; } # create object my $ModuleObject = $Modules{$Module}->{Module}->new( ConfigObject => $Self->{ConfigObject}, LogObject => $Self->{LogObject}, DBObject => $Self->{DBObject}, TicketObject => $Self, QueueObject => $Self->{QueueObject}, CustomerUserObject => $Self->{CustomerUserObject}, CustomerGroupObject => $Self->{CustomerGroupObject}, Debug => $Self->{Debug}, ); # execute Run() my $AccessOk = $ModuleObject->Run(%Param); # check granted option (should I say ok) if ( $AccessOk && $Modules{$Module}->{Granted} ) { if ( $Self->{Debug} > 0 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Granted access '$Param{Type}' true for " . "TicketID '$Param{TicketID}' " . "through $Modules{$Module}->{Module} (no more checks)!", ); } # access ok return 1; } # return because access is false but it's required if ( !$AccessOk && $Modules{$Module}->{Required} ) { if ( !$Param{LogNo} ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied because module " . "($Modules{$Module}->{Module}) is required " . "(UserID: $Param{UserID} '$Param{Type}' on " . "TicketID: $Param{TicketID})!", ); } # access not ok return; } } } # don't grant access to the ticket if ( !$Param{LogNo} ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied (UserID: $Param{UserID} '$Param{Type}' on " . "TicketID: $Param{TicketID})!", ); } return; } =item GetSubscribedUserIDsByQueueID() returns a array of user ids which selected the given queue id as custom queue. my @UserIDs = $TicketObject->GetSubscribedUserIDsByQueueID( QueueID => 123, ); =cut sub GetSubscribedUserIDsByQueueID { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{QueueID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need QueueID!' ); return; } # get group of queue my %Queue = $Self->{QueueObject}->QueueGet( ID => $Param{QueueID} ); # fetch all queues my @UserIDs = (); $Self->{DBObject}->Prepare( SQL => 'SELECT user_id FROM personal_queues WHERE queue_id = ?', Bind => [ \$Param{QueueID} ], ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { push @UserIDs, $Row[0]; } # check if user is valid and check permissions my @CleanUserIDs = (); for my $UserID (@UserIDs) { my %User = $Self->{UserObject}->GetUserData( UserID => $UserID, Valid => 1 ); if (%User) { # just send emails to permitted agents my %GroupMember = $Self->{GroupObject}->GroupMemberList( UserID => $UserID, Type => 'ro', Result => 'HASH', ); if ( $GroupMember{ $Queue{GroupID} } ) { push @CleanUserIDs, $UserID; } } } return @CleanUserIDs; } =item TicketPendingTimeSet() set ticket pending time $TicketObject->TicketPendingTimeSet( Year => 2003, Month => 08, Day => 14, Hour => 22, Minute => 05, TicketID => 123, UserID => 23, ); or use a time stamp $TicketObject->TicketPendingTimeSet( String => '2003-08-14 22:05:00', TicketID => 123, UserID => 23, ); =cut sub TicketPendingTimeSet { my ( $Self, %Param ) = @_; my $Time; # check needed stuff if ( !$Param{String} ) { for (qw(Year Month Day Hour Minute TicketID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } } # get system time from string/params if ( $Param{String} ) { $Time = $Self->{TimeObject}->TimeStamp2SystemTime( String => $Param{String}, ); ( $Param{Sec}, $Param{Minute}, $Param{Hour}, $Param{Day}, $Param{Month}, $Param{Year} ) = $Self->{TimeObject}->SystemTime2Date( SystemTime => $Time, ); } else { $Time = $Self->{TimeObject}->TimeStamp2SystemTime( String => "$Param{Year}-$Param{Month}-$Param{Day} $Param{Hour}:$Param{Minute}:00", ); } # return if no convert is possible if ( !$Time ) { return; } # db update return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET ' . ' until_time = ?, change_time = current_timestamp, change_by = ? WHERE id = ?', Bind => [ \$Time, \$Param{UserID}, \$Param{TicketID} ], ); # history insert $Self->HistoryAdd( TicketID => $Param{TicketID}, HistoryType => 'SetPendingTime', Name => '%%' . sprintf( "%02d", $Param{Year} ) . '-' . sprintf( "%02d", $Param{Month} ) . '-' . sprintf( "%02d", $Param{Day} ) . ' ' . sprintf( "%02d", $Param{Hour} ) . ':' . sprintf( "%02d", $Param{Minute} ) . '', CreateUserID => $Param{UserID}, ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # ticket event $Self->TicketEventHandlerPost( Event => 'TicketPendingTimeUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketSearch() To find tickets in your system. my @TicketIDs = $TicketObject->TicketSearch( # result (required) Result => 'ARRAY' || 'HASH', # result limit Limit => 100, # ticket number (optional) as STRING or as ARRAYREF TicketNumber => '%123546%', TicketNumber => ['%123546%', '%123666%'], # ticket title (optional) as STRING or as ARRAYREF Title => '%SomeText%', Title => ['%SomeTest1%', '%SomeTest2%'], Queues => ['system queue', 'other queue'], QueueIDs => [1, 42, 512], # use also sub queues of Queue|Queues in search UseSubQueues => 0, # You can use types like normal, ... Types => ['normal', 'change', 'incident'], TypeIDs => [3, 4], # You can use states like new, open, pending reminder, ... States => ['new', 'open'], StateIDs => [3, 4], # (Open|Closed) tickets for all closed or open tickets. StateType => 'Open', # You also can use real state types like new, open, closed, # pending reminder, pending auto, removed and merged. StateType => ['open', 'new'], StateTypeIDs => [1, 2, 3], Priorities => ['1 very low', '2 low', '3 normal'], PriorityIDs => [1, 2, 3], Services => ['Service A', 'Service B'], ServiceIDs => [1, 2, 3], SLAs => ['SLA A', 'SLA B'], SLAIDs => [1, 2, 3], Locks => ['unlock'], LockIDs => [1, 2, 3], OwnerIDs => [1, 12, 455, 32] ResponsibleIDs => [1, 12, 455, 32] WatchUserIDs => [1, 12, 455, 32] # CustomerID (optional) as STRING or as ARRAYREF CustomerID => '123', CustomerID => ['123', 'ABC'], # CustomerUserLogin (optional) as STRING as ARRAYREF CustomerUserLogin => 'uid123', CustomerUserLogin => ['uid123', 'uid777'], # create ticket properties (optional) CreatedUserIDs => [1, 12, 455, 32] CreatedTypes => ['normal', 'change', 'incident'], CreatedTypeIDs => [1, 2, 3], CreatedPriorities => ['1 very low', '2 low', '3 normal'], CreatedPriorityIDs => [1, 2, 3], CreatedStates => ['new', 'open'], CreatedStateIDs => [3, 4], CreatedQueues => ['system queue', 'other queue'], CreatedQueueIDs => [1, 42, 512], # 1..16 (optional) TicketFreeKey1 => 'Product', TicketFreeText1 => 'adasd', # or with multi options as array ref or string possible TicketFreeKey2 => ['Product', 'Product2'], TicketFreeText2 => ['Browser', 'Sound', 'Mouse'], # 1..6 (optional) # tickets with free time after ... (optional) TicketFreeTime1NewerDate => '2006-01-09 00:00:01', # tickets with free time before then ... (optional) TicketFreeTime1OlderDate => '2006-01-19 23:59:59', # article stuff (optional) From => '%spam@example.com%', To => '%support@example.com%', Cc => '%client@example.com%', Subject => '%VIRUS 32%', Body => '%VIRUS 32%', # use full text index if configured (optional, default off) FullTextIndex => 1, # content search (AND or OR) (optional) ContentSearch => 'AND', # content conditions for From,To,Cc,Subject,Body,TicketNumber, # Title,CustomerID and CustomerUserLogin (all optional) ConditionInline => 1, # tickets created after 60 minutes (ticket newer then 60 minutes) (optional) TicketCreateTimeOlderMinutes => 60, # tickets created before 120 minutes (ticket older 120 minutes) (optional) TicketCreateTimeNewerMinutes => 120, # tickets with created time after ... (ticket newer then this date) (optional) TicketCreateTimeNewerDate => '2006-01-09 00:00:01', # tickets with created time before then ... (ticket older this date) (optional) TicketCreateTimeOlderDate => '2006-01-19 23:59:59', # tickets closed after 60 minutes (ticket closed newer then 60 minutes) (optional) TicketCloseTimeOlderMinutes => 60, # tickets closed before 120 minutes (ticket closed older 120 minutes) (optional) TicketCloseTimeNewerMinutes => 120, # tickets with closed time after ... (ticket closed newer then this date) (optional) TicketCloseTimeNewerDate => '2006-01-09 00:00:01', # tickets with closed time before then ... (ticket closed older this date) (optional) TicketCloseTimeOlderDate => '2006-01-19 23:59:59', # tickets pending after 60 minutes (optional) TicketPendingTimeOlderMinutes => 60, # tickets pending before 120 minutes (optional) TicketPendingTimeNewerMinutes => 120, # tickets with pending time after ... (optional) TicketPendingTimeNewerDate => '2006-01-09 00:00:01', # tickets with pending time before then ... (optional) TicketPendingTimeOlderDate => '2006-01-19 23:59:59', # you can use all following escalation options with this four different ways of escalations # TicketEscalationTime... # TicketEscalationUpdateTime... # TicketEscalationResponseTime... # TicketEscalationSolutionTime... # ticket escalations over 60 minutes (optional) TicketEscalationTimeOlderMinutes => -60, # ticket escalations in 120 minutes (optional) TicketEscalationTimeNewerMinutes => -120, # tickets with escalation time after ... (optional) TicketEscalationTimeNewerDate => '2006-01-09 00:00:01', # tickets with escalation time before ... (optional) TicketEscalationTimeOlderDate => '2006-01-09 23:59:59', # OrderBy and SortBy (optional) OrderBy => 'Down', # Down|Up SortBy => 'Age', # Owner|CustomerID|State|Ticket|Queue|Priority|Age|Type|Lock # Service|SLA|EscalationTime # EscalationUpdateTime|EscalationResponseTime|EscalationSolutionTime # TicketFreeTime1-2|TicketFreeKey1-16|TicketFreeText1-16 # OrderBy and SortBy as ARRAY for sub sorting (optional) OrderBy => ['Down', 'Up'], SortBy => ['Priority', 'Age'], # user search (UserID is required) UserID => 123, Permission => 'ro' || 'rw', # customer search (CustomerUserID is required) CustomerUserID => 123, Permission => 'ro' || 'rw', ); =cut sub TicketSearch { my ( $Self, %Param ) = @_; my $Result = $Param{Result} || 'HASH'; my $OrderBy = $Param{OrderBy} || 'Down'; my $SortBy = $Param{SortBy} || 'Age'; my $Limit = $Param{Limit} || 10000; if ( !$Param{ContentSearch} ) { $Param{ContentSearch} = 'AND'; } my %SortOptions = ( Owner => 'st.user_id', Responsible => 'st.responsible_user_id', CustomerID => 'st.customer_id', State => 'st.ticket_state_id', Lock => 'st.ticket_lock_id', Ticket => 'st.tn', Title => 'st.title', Queue => 'sq.name', Type => 'st.type_id', Priority => 'st.ticket_priority_id', Age => 'st.create_time_unix', Service => 'st.service_id', SLA => 'st.sla_id', TicketEscalation => 'st.escalation_time', EscalationTime => 'st.escalation_time', EscalationUpdateTime => 'st.escalation_update_time', EscalationResponseTime => 'st.escalation_response_time', EscalationSolutionTime => 'st.escalation_solution_time', TicketFreeTime1 => 'st.freetime1', TicketFreeTime2 => 'st.freetime2', TicketFreeTime3 => 'st.freetime3', TicketFreeTime4 => 'st.freetime4', TicketFreeTime5 => 'st.freetime5', TicketFreeTime6 => 'st.freetime6', TicketFreeKey1 => 'st.freekey1', TicketFreeText1 => 'st.freetext1', TicketFreeKey2 => 'st.freekey2', TicketFreeText2 => 'st.freetext2', TicketFreeKey3 => 'st.freekey3', TicketFreeText3 => 'st.freetext3', TicketFreeKey4 => 'st.freekey4', TicketFreeText4 => 'st.freetext4', TicketFreeKey5 => 'st.freekey5', TicketFreeText5 => 'st.freetext5', TicketFreeKey6 => 'st.freekey6', TicketFreeText6 => 'st.freetext6', TicketFreeKey7 => 'st.freekey7', TicketFreeText7 => 'st.freetext7', TicketFreeKey8 => 'st.freekey8', TicketFreeText8 => 'st.freetext8', TicketFreeKey9 => 'st.freekey9', TicketFreeText9 => 'st.freetext9', TicketFreeKey10 => 'st.freekey10', TicketFreeText10 => 'st.freetext10', TicketFreeKey11 => 'st.freekey11', TicketFreeText11 => 'st.freetext11', TicketFreeKey12 => 'st.freekey12', TicketFreeText12 => 'st.freetext12', TicketFreeKey13 => 'st.freekey13', TicketFreeText13 => 'st.freetext13', TicketFreeKey14 => 'st.freekey14', TicketFreeText14 => 'st.freetext14', TicketFreeKey15 => 'st.freekey15', TicketFreeText15 => 'st.freetext15', TicketFreeKey16 => 'st.freekey16', TicketFreeText16 => 'st.freetext16', ); # check required params if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID params for permission check!', ); return; } # check sort/order by options my @SortByArray; my @OrderByArray; if ( ref $SortBy eq 'ARRAY' ) { @SortByArray = @{$SortBy}; @OrderByArray = @{$OrderBy}; } else { @SortByArray = ($SortBy); @OrderByArray = ($OrderBy); } for my $Count ( 0 .. $#SortByArray ) { if ( !$SortOptions{ $SortByArray[$Count] } ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need valid SortBy (' . $SortByArray[$Count] . ')!', ); return; } if ( $OrderByArray[$Count] ne 'Down' && $OrderByArray[$Count] ne 'Up' ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need valid OrderBy (' . $OrderByArray[$Count] . ')!', ); return; } } # sql my $SQLExt = ''; my $SQL = 'SELECT DISTINCT st.id, st.tn'; for my $SortBy (@SortByArray) { $SQL .= ', ' . $SortOptions{$SortBy}; } $SQL .= ' FROM ticket st, queue sq '; # sql, use also article table if needed my ( $ArticleSQL, $ArticleSQLExt ) = $Self->_ArticleIndexQuerySQL( Data => \%Param ); $SQL .= $ArticleSQL; $SQLExt .= $ArticleSQLExt; # use also history table if required for ( keys %Param ) { if ( $_ =~ /^(Ticket(Close)Time(Newer|Older)(Date|Minutes)|Created.+?)/ ) { $SQL .= ', ticket_history th '; $SQLExt .= ' AND st.id = th.ticket_id'; last; } } if ( $Param{WatchUserIDs} ) { $SQL .= ', ticket_watcher tw '; $SQLExt .= ' AND st.id = tw.ticket_id'; } $SQLExt = ' WHERE sq.id = st.queue_id' . $SQLExt; # current type lookup if ( $Param{Types} && ref $Param{Types} eq 'ARRAY' ) { for ( @{ $Param{Types} } ) { if ( $Self->{TypeObject}->TypeLookup( Type => $_ ) ) { push( @{ $Param{TypeIDs} }, $Self->{TypeObject}->TypeLookup( Type => $_ ) ); } else { return; } } } # type ids if ( $Param{TypeIDs} && ref $Param{TypeIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.type_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{TypeIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # created types lookup if ( $Param{CreatedTypes} && ref $Param{CreatedTypes} eq 'ARRAY' ) { for ( @{ $Param{CreatedTypes} } ) { if ( $Self->{TypeObject}->TypeLookup( Type => $_ ) ) { push( @{ $Param{CreatedTypeIDs} }, $Self->{TypeObject}->TypeLookup( Type => $_ ) ); } else { return; } } } # create types id if ( $Param{CreatedTypeIDs} && ref $Param{CreatedTypeIDs} eq 'ARRAY' ) { my $ID = $Self->HistoryTypeLookup( Type => 'NewTicket' ); if ($ID) { $SQLExt .= ' AND th.type_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{CreatedTypeIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ") AND th.history_type_id = $ID "; } } # current state lookup if ( $Param{States} && ref $Param{States} eq 'ARRAY' ) { for ( @{ $Param{States} } ) { my %State = $Self->{StateObject}->StateGet( Name => $_ ); if ( $State{ID} ) { push( @{ $Param{StateIDs} }, $State{ID} ); } else { return; } } } # state ids if ( $Param{StateIDs} && ref $Param{StateIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.ticket_state_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{StateIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # created states lookup if ( $Param{CreatedStates} && ref $Param{CreatedStates} eq 'ARRAY' ) { for ( @{ $Param{CreatedStates} } ) { my %State = $Self->{StateObject}->StateGet( Name => $_ ); if ( $State{ID} ) { push( @{ $Param{CreatedStateIDs} }, $State{ID} ); } else { return; } } } # create states id if ( $Param{CreatedStateIDs} && ref $Param{CreatedStateIDs} eq 'ARRAY' ) { my $ID = $Self->HistoryTypeLookup( Type => 'NewTicket' ); if ($ID) { $SQLExt .= ' AND th.state_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{CreatedStateIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ") AND th.history_type_id = $ID "; } } # current ticket state type if ( $Param{StateType} && $Param{StateType} eq 'Open' ) { my @ViewableStateIDs = $Self->{StateObject}->StateGetStatesByType( Type => 'Viewable', Result => 'ID', ); $SQLExt .= " AND st.ticket_state_id IN ( ${\(join ', ', sort @ViewableStateIDs)} ) "; } elsif ( $Param{StateType} && $Param{StateType} eq 'Closed' ) { my @ViewableStateIDs = $Self->{StateObject}->StateGetStatesByType( Type => 'Viewable', Result => 'ID', ); $SQLExt .= " AND st.ticket_state_id NOT IN ( ${\(join ', ', sort @ViewableStateIDs)} ) "; } elsif ( $Param{StateType} ) { my @StateIDs = $Self->{StateObject}->StateGetStatesByType( StateType => $Param{StateType}, Result => 'ID', ); $SQLExt .= " AND st.ticket_state_id IN ( ${\(join ', ', sort {$a <=> $b} @StateIDs)} ) "; } if ( $Param{StateTypeIDs} && ref $Param{StateTypeIDs} eq 'ARRAY' ) { my %StateTypeList = $Self->{StateObject}->StateTypeList( UserID => $Param{UserID} || 1, ); my @StateTypes = map { $StateTypeList{$_} } @{ $Param{StateTypeIDs} }; my @StateIDs = $Self->{StateObject}->StateGetStatesByType( StateType => \@StateTypes, Result => 'ID', ); $SQLExt .= " AND st.ticket_state_id IN ( ${\(join ', ', sort {$a <=> $b} @StateIDs)} ) "; } # current lock lookup if ( $Param{Locks} && ref $Param{Locks} eq 'ARRAY' ) { for ( @{ $Param{Locks} } ) { if ( $Self->{LockObject}->LockLookup( Lock => $_ ) ) { push( @{ $Param{LockIDs} }, $Self->{LockObject}->LockLookup( Lock => $_ ) ); } else { return; } } } # lock ids if ( $Param{LockIDs} && ref $Param{LockIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.ticket_lock_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{LockIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # current owners user ids if ( $Param{OwnerIDs} && ref $Param{OwnerIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.user_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{OwnerIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # current owners user ids if ( $Param{ResponsibleIDs} && ref $Param{ResponsibleIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.responsible_user_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{ResponsibleIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # created owner user ids if ( $Param{CreatedUserIDs} && ref $Param{CreatedUserIDs} eq 'ARRAY' ) { my $ID = $Self->HistoryTypeLookup( Type => 'NewTicket' ); if ($ID) { $SQLExt .= ' AND th.create_by IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{CreatedUserIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ") AND th.history_type_id = $ID "; } } # current queue lookup if ( $Param{Queues} && ref $Param{Queues} eq 'ARRAY' ) { for ( @{ $Param{Queues} } ) { if ( $Self->{QueueObject}->QueueLookup( Queue => $_ ) ) { push( @{ $Param{QueueIDs} }, $Self->{QueueObject}->QueueLookup( Queue => $_ ) ); } else { return; } } } # current sub queue ids if ( $Param{UseSubQueues} && $Param{QueueIDs} && ref $Param{QueueIDs} eq 'ARRAY' ) { my @SubQueueIDs = (); my %Queues = $Self->{QueueObject}->GetAllQueues(); for my $QueueID ( @{ $Param{QueueIDs} } ) { my $Queue = $Self->{QueueObject}->QueueLookup( QueueID => $QueueID ); for my $QueuesID ( keys %Queues ) { if ( $Queues{$QueuesID} =~ /^\Q$Queue\E::/i ) { push( @SubQueueIDs, $QueuesID ); } } } push( @{ $Param{QueueIDs} }, @SubQueueIDs ); } # current queue ids if ( $Param{QueueIDs} && ref $Param{QueueIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.queue_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{QueueIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # created queue lookup if ( $Param{CreatedQueues} && ref $Param{CreatedQueues} eq 'ARRAY' ) { for ( @{ $Param{CreatedQueues} } ) { if ( $Self->{QueueObject}->QueueLookup( Queue => $_ ) ) { push( @{ $Param{CreatedQueueIDs} }, $Self->{QueueObject}->QueueLookup( Queue => $_ ) ); } else { return; } } } # create queue ids if ( $Param{CreatedQueueIDs} && ref $Param{CreatedQueueIDs} eq 'ARRAY' ) { my $ID = $Self->HistoryTypeLookup( Type => 'NewTicket' ); if ($ID) { $SQLExt .= ' AND th.queue_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{CreatedQueueIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ") AND th.history_type_id = $ID "; } } my @GroupIDs; # user groups if ( $Param{UserID} && $Param{UserID} != 1 ) { # get users groups @GroupIDs = $Self->{GroupObject}->GroupMemberList( UserID => $Param{UserID}, Type => $Param{Permission} || 'ro', Result => 'ID', Cached => 1, ); # return if we have no permissions if ( !@GroupIDs ) { return; } } # customer groups elsif ( $Param{CustomerUserID} ) { @GroupIDs = $Self->{CustomerGroupObject}->GroupMemberList( UserID => $Param{CustomerUserID}, Type => $Param{Permission} || 'ro', Result => 'ID', Cached => 1, ); # return if we have no permissions if ( !@GroupIDs ) { return; } # get secondary customer ids my @CustomerIDs = $Self->{CustomerUserObject}->CustomerIDs( User => $Param{CustomerUserID}, ); # add own customer id my %CustomerData = $Self->{CustomerUserObject}->CustomerUserDataGet( User => $Param{CustomerUserID}, ); if ( $CustomerData{UserCustomerID} ) { push @CustomerIDs, $CustomerData{UserCustomerID}; } $SQLExt .= ' AND (LOWER(st.customer_id) IN ('; my $Exists = 0; for (@CustomerIDs) { if ($Exists) { $SQLExt .= ', '; } else { $Exists = 1; } $SQLExt .= "LOWER('" . $Self->{DBObject}->Quote($_) . "')"; } $SQLExt .= ') OR ' . " st.customer_user_id = '" . $Self->{DBObject}->Quote( $Param{CustomerUserID} ) . "') "; } # add group ids to sql string if (@GroupIDs) { $SQLExt .= " AND sq.group_id IN (${\(join ', ' , sort {$a <=> $b} @GroupIDs)}) "; } # current priority lookup if ( $Param{Priorities} && ref $Param{Priorities} eq 'ARRAY' ) { for ( @{ $Param{Priorities} } ) { my $ID = $Self->{PriorityObject}->PriorityLookup( Priority => $_ ); if ($ID) { push( @{ $Param{PriorityIDs} }, $ID ); } else { return; } } } # priority ids if ( $Param{PriorityIDs} && ref $Param{PriorityIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.ticket_priority_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{PriorityIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # created priority lookup if ( $Param{CreatedPriorities} && ref $Param{CreatedPriorities} eq 'ARRAY' ) { for ( @{ $Param{CreatedPriorities} } ) { my $ID = $Self->{PriorityObject}->PriorityLookup( Priority => $_ ); if ($ID) { push( @{ $Param{CreatedPriorityIDs} }, $ID ); } else { return; } } } # created priority ids if ( $Param{CreatedPriorityIDs} && ref $Param{CreatedPriorityIDs} eq 'ARRAY' ) { my $ID = $Self->HistoryTypeLookup( Type => 'NewTicket' ); if ($ID) { $SQLExt .= ' AND th.priority_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{CreatedPriorityIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ") AND th.history_type_id = $ID "; } } # current service lookup if ( $Param{Services} && ref $Param{Services} eq 'ARRAY' ) { for ( @{ $Param{Services} } ) { my $ID = $Self->{ServiceObject}->ServiceLookup( Name => $_ ); if ($ID) { push( @{ $Param{ServiceIDs} }, $ID ); } else { return; } } } # service ids if ( $Param{ServiceIDs} && ref $Param{ServiceIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.service_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{ServiceIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # current sla lookup if ( $Param{SLAs} && ref $Param{SLAs} eq 'ARRAY' ) { for ( @{ $Param{SLAs} } ) { my $ID = $Self->{SLAObject}->SLALookup( Name => $_ ); if ($ID) { push( @{ $Param{SLAIDs} }, $ID ); } else { return; } } } # sla ids if ( $Param{SLAIDs} && ref $Param{SLAIDs} eq 'ARRAY' ) { $SQLExt .= ' AND st.sla_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{SLAIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # watch user ids if ( $Param{WatchUserIDs} && ref $Param{WatchUserIDs} eq 'ARRAY' ) { $SQLExt .= ' AND tw.user_id IN ('; my $Exists = 0; for ( sort { $a <=> $b } @{ $Param{WatchUserIDs} } ) { if ($Exists) { $SQLExt .= ','; } $SQLExt .= $Self->{DBObject}->Quote($_); $Exists = 1; } $SQLExt .= ')'; } # other ticket stuff my %FieldSQLMap = ( TicketNumber => 'st.tn', Title => 'st.title', CustomerID => 'st.customer_id', CustomerUserLogin => 'st.customer_user_id', ); for my $Key ( sort keys %FieldSQLMap ) { if ( ref $Param{$Key} eq 'ARRAY' ) { $SQLExt .= " AND LOWER($FieldSQLMap{$Key}) IN ("; my $Exists = 0; for my $Key ( @{ $Param{$Key} } ) { $Key =~ s/\*/%/gi; # check search attribute, we do not need to search for * next if $Key =~ /^\%{1,3}$/; if ($Exists) { $SQLExt .= ','; } else { $Exists = 1; } $SQLExt .= "LOWER('" . $Self->{DBObject}->Quote($Key) . "')"; } $SQLExt .= ' )'; } elsif ( $Param{$Key} ) { $Param{$Key} =~ s/\*/%/gi; # check search attribute, we do not need to search for * next if $Param{$Key} =~ /^\%{1,3}$/; # check if search condition extention is used if ( $Param{ConditionInline} && $Param{$Key} =~ /(&&|\|\||\!|\+|AND|OR)/ ) { $SQLExt .= ' AND ' . $Self->{DBObject}->QueryCondition( Key => $FieldSQLMap{$Key}, Value => $Param{$Key}, SearchPrefix => '*', SearchSuffix => '*', ); } elsif ( $Param{$Key} !~ /%/ ) { $SQLExt .= " AND LOWER($FieldSQLMap{$Key}) = LOWER('" . $Self->{DBObject}->Quote( $Param{$Key} ) . "')"; } else { $SQLExt .= " AND LOWER($FieldSQLMap{$Key}) LIKE LOWER('" . $Self->{DBObject}->Quote( $Param{$Key}, 'Like' ) . "')"; } } } # search article attributes my $ArticleIndexSQLExt = $Self->_ArticleIndexQuerySQLExt( Data => \%Param ); $SQLExt .= $ArticleIndexSQLExt; # ticket free text for ( 1 .. 16 ) { if ( $Param{"TicketFreeKey$_"} && ref $Param{"TicketFreeKey$_"} eq '' ) { $Param{"TicketFreeKey$_"} =~ s/\*/%/gi; # check search attribute, we do not need to search for * next if $Param{"TicketFreeKey$_"} =~ /^\%{1,3}$/; $SQLExt .= " AND LOWER(st.freekey$_) LIKE LOWER('" . $Self->{DBObject}->Quote( $Param{"TicketFreeKey$_"}, 'Like' ) . "')"; } elsif ( $Param{"TicketFreeKey$_"} && ref $Param{"TicketFreeKey$_"} eq 'ARRAY' ) { my $SQLExtSub = ' AND ('; my $Counter = 0; for my $Key ( @{ $Param{"TicketFreeKey$_"} } ) { if ( defined $Key && $Key ne '' ) { $Key =~ s/\*/%/gi; # check search attribute, we do not need to search for * next if $Key =~ /^\%{1,3}$/; $SQLExtSub .= ' OR ' if ($Counter); $SQLExtSub .= " LOWER(st.freekey$_) LIKE LOWER('" . $Self->{DBObject}->Quote( $Key, 'Like' ) . "')"; $Counter++; } } $SQLExtSub .= ')'; if ($Counter) { $SQLExt .= $SQLExtSub; } } } for ( 1 .. 16 ) { if ( $Param{"TicketFreeText$_"} && ref $Param{"TicketFreeText$_"} eq '' ) { $Param{"TicketFreeText$_"} =~ s/\*/%/gi; # check search attribute, we do not need to search for * next if $Param{"TicketFreeText$_"} =~ /^\%{1,3}$/; $SQLExt .= " AND LOWER(st.freetext$_) LIKE LOWER('" . $Self->{DBObject}->Quote( $Param{"TicketFreeText$_"}, 'Like' ) . "')"; } elsif ( $Param{"TicketFreeText$_"} && ref $Param{"TicketFreeText$_"} eq 'ARRAY' ) { my $SQLExtSub = ' AND ('; my $Counter = 0; for my $Text ( @{ $Param{"TicketFreeText$_"} } ) { if ( defined $Text && $Text ne '' ) { $Text =~ s/\*/%/gi; # check search attribute, we do not need to search for * next if $Text =~ /^\%{1,3}$/; $SQLExtSub .= ' OR ' if ($Counter); $SQLExtSub .= " LOWER(st.freetext$_) LIKE LOWER('" . $Self->{DBObject}->Quote( $Text, 'Like' ) . "')"; $Counter++; } } $SQLExtSub .= ')'; if ($Counter) { $SQLExt .= $SQLExtSub; } } } for ( 1 .. 6 ) { # get tickets older then xxxx-xx-xx xx:xx date if ( $Param{ 'TicketFreeTime' . $_ . 'OlderDate' } ) { # check time format if ( $Param{ 'TicketFreeTime' . $_ . 'OlderDate' } !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '" . $Param{ 'TicketFreeTime' . $_ . 'OlderDate' } . "'!", ); return; } $SQLExt .= " AND st.freetime$_ <= '" . $Self->{DBObject}->Quote( $Param{ 'TicketFreeTime' . $_ . 'OlderDate' } ) . "'"; } # get tickets newer then xxxx-xx-xx xx:xx date if ( $Param{ 'TicketFreeTime' . $_ . 'NewerDate' } ) { if ( $Param{ 'TicketFreeTime' . $_ . 'NewerDate' } !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '" . $Param{ 'TicketFreeTime' . $_ . 'NewerDate' } . "'!", ); return; } $SQLExt .= " AND st.freetime$_ >= '" . $Self->{DBObject}->Quote( $Param{ 'TicketFreeTime' . $_ . 'NewerDate' } ) . "'"; } } # get tickets created/escalated older then x minutes my %TicketTime = ( TicketCreateTime => 'st.create_time_unix', TicketEscalationTime => 'st.escalation_time', TicketEscalationUpdateTime => 'st.escalation_update_time', TicketEscalationResponseTime => 'st.escalation_response_time', TicketEscalationSolutionTime => 'st.escalation_solution_time', ); for my $Key ( keys %TicketTime ) { # get tickets created older then x minutes if ( $Param{ $Key . 'OlderMinutes' } ) { # exclude tickets with no escalation if ( $Key =~ m{ \A TicketEscalation }xms ) { $SQLExt .= " AND $TicketTime{$Key} != 0"; } my $Time = $Self->{TimeObject}->SystemTime() - ( $Param{ $Key . 'OlderMinutes' } * 60 ); $SQLExt .= " AND $TicketTime{$Key} <= $Time"; } # get tickets created newer then x minutes if ( $Param{ $Key . 'NewerMinutes' } ) { # exclude tickets with no escalation if ( $Key =~ m{ \A TicketEscalation }xms ) { $SQLExt .= " AND $TicketTime{$Key} != 0"; } my $Time = $Self->{TimeObject}->SystemTime() - ( $Param{ $Key . 'NewerMinutes' } * 60 ); $SQLExt .= " AND $TicketTime{$Key} >= $Time"; } } # get tickets created/escalated older then xxxx-xx-xx xx:xx date for my $Key ( keys %TicketTime ) { if ( $Param{ $Key . 'OlderDate' } ) { # check time format if ( $Param{ $Key . 'OlderDate' } !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '" . $Param{ $Key . 'OlderDate' } . "'!", ); return; } # exclude tickets with no escalation if ( $Key =~ m{ \A TicketEscalation }xms ) { $SQLExt .= " AND $TicketTime{$Key} != 0"; } my $Time = $Self->{TimeObject}->TimeStamp2SystemTime( String => $Param{ $Key . 'OlderDate' }, ); $SQLExt .= " AND $TicketTime{$Key} <= $Time"; } # get tickets created newer then xxxx-xx-xx xx:xx date if ( $Param{ $Key . 'NewerDate' } ) { if ( $Param{ $Key . 'NewerDate' } !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '" . $Param{ $Key . 'NewerDate' } . "'!", ); return; } # exclude tickets with no escalation if ( $Key =~ m{ \A TicketEscalation }xms ) { $SQLExt .= " AND $TicketTime{$Key} != 0"; } my $Time = $Self->{TimeObject}->TimeStamp2SystemTime( String => $Param{ $Key . 'NewerDate' }, ); $SQLExt .= " AND $TicketTime{$Key} >= $Time"; } } # get tickets closed older then x minutes if ( $Param{TicketCloseTimeOlderMinutes} ) { my $TimeStamp = $Self->{TimeObject}->SystemTime() - ( $Param{TicketCloseTimeOlderMinutes} * 60 ); $Param{TicketCloseTimeOlderDate} = $Self->{TimeObject}->SystemTime2TimeStamp( SystemTime => $TimeStamp, ); } # get tickets closed newer then x minutes if ( $Param{TicketCloseTimeNewerMinutes} ) { my $TimeStamp = $Self->{TimeObject}->SystemTime() - ( $Param{TicketCloseTimeNewerMinutes} * 60 ); $Param{TicketCloseTimeNewerDate} = $Self->{TimeObject}->SystemTime2TimeStamp( SystemTime => $TimeStamp, ); } # get tickets closed older then xxxx-xx-xx xx:xx date if ( $Param{TicketCloseTimeOlderDate} ) { # check time format if ( $Param{TicketCloseTimeOlderDate} !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '$Param{TicketCloseTimeOlderDate}'!", ); return; } # get close state ids my @List = $Self->{StateObject}->StateGetStatesByType( StateType => ['closed'], Result => 'ID', ); my @StateID = ( $Self->HistoryTypeLookup( Type => 'NewTicket' ) ); push( @StateID, $Self->HistoryTypeLookup( Type => 'StateUpdate' ) ); if (@StateID) { $SQLExt .= " AND th.history_type_id IN (${\(join ', ', sort @StateID)}) AND " . " th.state_id IN (${\(join ', ', sort @List)}) AND " . "th.create_time <= '" . $Self->{DBObject}->Quote( $Param{TicketCloseTimeOlderDate} ) . "'"; } } # get tickets closed newer then xxxx-xx-xx xx:xx date if ( $Param{TicketCloseTimeNewerDate} ) { if ( $Param{TicketCloseTimeNewerDate} !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '$Param{TicketCloseTimeNewerDate}'!", ); return; } # get close state ids my @List = $Self->{StateObject}->StateGetStatesByType( StateType => ['closed'], Result => 'ID', ); my @StateID = ( $Self->HistoryTypeLookup( Type => 'NewTicket' ) ); push( @StateID, $Self->HistoryTypeLookup( Type => 'StateUpdate' ) ); if (@StateID) { $SQLExt .= " AND th.history_type_id IN (${\(join ', ', sort @StateID)}) AND " . " th.state_id IN (${\(join ', ', sort @List)}) AND " . " th.create_time >= '" . $Self->{DBObject}->Quote( $Param{TicketCloseTimeNewerDate} ) . "'"; } } # check if only pending states are used if ( $Param{TicketPendingTimeOlderMinutes} || $Param{TicketPendingTimeNewerMinutes} || $Param{TicketPendingTimeOlderDate} || $Param{TicketPendingTimeNewerDate} ) { # get close state ids my @List = $Self->{StateObject}->StateGetStatesByType( StateType => [ 'pending reminder', 'pending auto' ], Result => 'ID', ); if (@List) { $SQLExt .= " AND st.ticket_state_id IN (${\(join ', ', sort @List)}) "; } } # get tickets pending older then x minutes if ( $Param{TicketPendingTimeOlderMinutes} ) { my $Time = $Self->{TimeObject}->SystemTime(); $Param{TicketPendingTimeOlderDate} = $Self->{TimeObject}->SystemTime2TimeStamp( SystemTime => $Time - ( $Param{TicketPendingTimeOlderMinutes} * 60 ), ); } # get tickets pending newer then x minutes if ( $Param{TicketPendingTimeNewerMinutes} ) { my $Time = $Self->{TimeObject}->SystemTime(); $Param{TicketPendingTimeNewerDate} = $Self->{TimeObject}->SystemTime2TimeStamp( SystemTime => $Time - ( $Param{TicketPendingTimeNewerMinutes} * 60 ), ); } # get pending tickets older then xxxx-xx-xx xx:xx date if ( $Param{TicketPendingTimeOlderDate} ) { # check time format if ( $Param{TicketPendingTimeOlderDate} !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '$Param{TicketPendingTimeOlderDate}'!", ); return; } my $TimeStamp = $Self->{TimeObject}->TimeStamp2SystemTime( String => $Param{TicketPendingTimeOlderDate}, ); $SQLExt .= " AND st.until_time <= '" . $TimeStamp . "'"; } # get pending tickets newer then xxxx-xx-xx xx:xx date if ( $Param{TicketPendingTimeNewerDate} ) { if ( $Param{TicketPendingTimeNewerDate} !~ /\d\d\d\d-(\d\d|\d)-(\d\d|\d) (\d\d|\d):(\d\d|\d):(\d\d|\d)/ ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No valid time format '$Param{TicketPendingTimeNewerDate}'!", ); return; } my $TimeStamp = $Self->{TimeObject}->TimeStamp2SystemTime( String => $Param{TicketPendingTimeNewerDate}, ); $SQLExt .= " AND st.until_time >= '" . $Self->{DBObject}->Quote($TimeStamp) . "'"; } # database query for sort/order by option $SQLExt .= ' ORDER BY'; for my $Count ( 0 .. $#SortByArray ) { if ( $Count > 0 ) { $SQLExt .= ','; } $SQLExt .= ' ' . $SortOptions{ $SortByArray[$Count] }; if ( $OrderByArray[$Count] eq 'Up' ) { $SQLExt .= ' ASC'; } else { $SQLExt .= ' DESC'; } } # check cache my $CacheObject; if ( $ArticleIndexSQLExt && $Param{FullTextIndex} ) { $CacheObject = Kernel::System::Cache->new( %{$Self} ); my $CacheData = $CacheObject->Get( Type => 'TicketSearch', Key => $SQL . $SQLExt . $Result . $Limit, ); if ($CacheData) { if ( ref $CacheData eq 'HASH' ) { return %{$CacheData}; } elsif ( ref $CacheData eq 'ARRAY' ) { return @{$CacheData}; } else { $Self->{LogObject}->Log( Priority => 'error', Message => 'Invalid ref ' . ref($CacheData) . '!' ); return; } } } # database query my %Tickets = (); my @TicketIDs = (); $Self->{DBObject}->Prepare( SQL => $SQL . $SQLExt, Limit => $Limit ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Tickets{ $Row[0] } = $Row[1]; push @TicketIDs, $Row[0]; } if ( $Result eq 'HASH' ) { # fill cache if ($CacheObject) { $CacheObject->Set( Type => 'TicketSearch', Key => $SQL . $SQLExt . $Result . $Limit, Value => \%Tickets, TTL => 60 * 5, ); } return %Tickets; } else { # fill cache if ($CacheObject) { $CacheObject->Set( Type => 'TicketSearch', Key => $SQL . $SQLExt . $Result . $Limit, Value => \@TicketIDs, TTL => 60 * 5, ); } return @TicketIDs; } } =item LockIsTicketLocked() check if a ticket is locked or not if ($TicketObject->LockIsTicketLocked(TicketID => 123)) { print "Ticket not locked!\n"; } else { print "Ticket is not locked!\n"; } =cut sub LockIsTicketLocked { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TicketID!' ); return; } my %Ticket = $Self->TicketGet(%Param); # check lock state if ( $Ticket{Lock} =~ /^lock$/i ) { return 1; } return; } =item LockSet() to set a ticket lock or unlock $TicketObject->LockSet( Lock => 'lock', TicketID => 123, UserID => 123, ); $TicketObject->LockSet( LockID => 1, TicketID => 123, UserID => 123, ); Optional attribute: SendNoNotification, disable or enable agent and customer notification for this action. Otherwise a notification will be send to agent and cusomer. For example: SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) =cut sub LockSet { my ( $Self, %Param ) = @_; # lookup! if ( ( !$Param{LockID} ) && ( $Param{Lock} ) ) { $Param{LockID} = $Self->{LockObject}->LockLookup( Lock => $Param{Lock} ); } if ( ( $Param{LockID} ) && ( !$Param{Lock} ) ) { $Param{Lock} = $Self->{LockObject}->LockLookup( LockID => $Param{LockID} ); } # check needed stuff for (qw(TicketID UserID LockID Lock)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } if ( !$Param{Lock} && !$Param{LockID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need LockID or Lock!' ); return; } # check if update is needed if ( ( $Self->LockIsTicketLocked( TicketID => $Param{TicketID} ) && $Param{Lock} eq 'lock' ) || ( !$Self->LockIsTicketLocked( TicketID => $Param{TicketID} ) && $Param{Lock} eq 'unlock' ) ) { # update not needed return 1; } # db update return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET ticket_lock_id = ?, ' . ' change_time = current_timestamp, change_by = ? WHERE id = ?', Bind => [ \$Param{LockID}, \$Param{UserID}, \$Param{TicketID} ], ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # update ticket view index $Self->TicketAcceleratorUpdate( TicketID => $Param{TicketID} ); # add history my $HistoryType = ''; if ( $Param{Lock} =~ /^unlock$/i ) { $HistoryType = 'Unlock'; } elsif ( $Param{Lock} =~ /^lock$/i ) { $HistoryType = 'Lock'; } else { $HistoryType = 'Misc'; } if ($HistoryType) { $Self->HistoryAdd( TicketID => $Param{TicketID}, CreateUserID => $Param{UserID}, HistoryType => $HistoryType, Name => "\%\%$Param{Lock}", ); } # set unlock time it event is 'lock' if ( $Param{Lock} eq 'lock' ) { $Self->TicketUnlockTimeoutUpdate( UnlockTimeout => $Self->{TimeObject}->SystemTime(), TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } # send unlock notify if ( $Param{Lock} =~ /^unlock$/i ) { my %Ticket = $Self->TicketGet(%Param); # check if the current user is the current owner, if not send a notify my $To = ''; my $Notification = defined $Param{Notification} ? $Param{Notification} : 1; if ( !$Param{SendNoNotification} && $Ticket{OwnerID} ne $Param{UserID} && $Notification ) { # get user data of owner my %Preferences = $Self->{UserObject}->GetUserData( UserID => $Ticket{OwnerID}, Valid => 1, ); if ( $Preferences{UserSendLockTimeoutNotification} ) { # send $Self->SendAgentNotification( Type => 'LockTimeout', UserData => \%Preferences, CustomerMessageParams => {}, TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } } } # ticket event $Self->TicketEventHandlerPost( Event => 'TicketLockUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item StateSet() to set a ticket state $TicketObject->StateSet( State => 'open', TicketID => 123, UserID => 123, ); $TicketObject->StateSet( StateID => 3, TicketID => 123, UserID => 123, ); Optional attribute: SendNoNotification, disable or enable agent and customer notification for this action. Otherwise a notification will be send to agent and cusomer. For example: SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) =cut sub StateSet { my ( $Self, %Param ) = @_; my %State = (); my $ArticleID = $Param{ArticleID} || ''; # check needed stuff for (qw(TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } if ( !$Param{State} && !$Param{StateID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need StateID or State!' ); return; } # state id lookup if ( !$Param{StateID} ) { %State = $Self->{StateObject}->StateGet( Name => $Param{State} ); } # state lookup if ( !$Param{State} ) { %State = $Self->{StateObject}->StateGet( ID => $Param{StateID} ); } if ( !%State ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need StateID or State!' ); return; } # check if update is needed my %Ticket = $Self->TicketGet( TicketID => $Param{TicketID} ); if ( $State{Name} eq $Ticket{State} ) { # update is not needed return 1; } # permission check my %StateList = $Self->StateList(%Param); if ( !$StateList{ $State{ID} } ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied on TicketID: $Param{TicketID} / StateID: $State{ID}!", ); return; } # db update return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET ticket_state_id = ?, ' . ' change_time = current_timestamp, change_by = ? WHERE id = ?', Bind => [ \$State{ID}, \$Param{UserID}, \$Param{TicketID} ], ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # update ticket view index $Self->TicketAcceleratorUpdate( TicketID => $Param{TicketID} ); # add history $Self->HistoryAdd( TicketID => $Param{TicketID}, ArticleID => $ArticleID, QueueID => $Ticket{QueueID}, Name => "\%\%$Ticket{State}\%\%$State{Name}\%\%", HistoryType => 'StateUpdate', CreateUserID => $Param{UserID}, ); # reset escalation time if ticket will be reopend if ( $State{TypeName} ne 'closed' && $Ticket{StateType} eq 'closed' ) { # $Self->TicketEscalationStartUpdate( # EscalationStartTime => $Self->{TimeObject}->SystemTime(), # TicketID => $Param{TicketID}, # UserID => $Param{UserID}, # ); } # send customer notification email if ( !$Param{SendNoNotification} ) { $Self->SendCustomerNotification( Type => 'StateUpdate', CustomerMessageParams => \%Param, TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } # ticket event $Self->TicketEventHandlerPost( Event => 'TicketStateUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item StateList() to get the state list for a ticket (depends on workflow, if configured) my %States = $TicketObject->StateList( TicketID => 123, UserID => 123, ); my %States = $TicketObject->StateList( QueueID => 123, UserID => 123, ); my %States = $TicketObject->StateList( TicketID => 123, Type => 'open', UserID => 123, ); =cut sub StateList { my ( $Self, %Param ) = @_; my %States = (); # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!' ); return; } # check needed stuff if ( !$Param{QueueID} && !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need QueueID, TicketID!' ); return; } # get states by type if ( $Param{Type} ) { %States = $Self->{StateObject}->StateGetStatesByType( Type => $Param{Type}, Result => 'HASH', ); } elsif ( $Param{Action} ) { if ( ref $Self->{ConfigObject}->Get("Ticket::Frontend::$Param{Action}")->{StateType} ne 'ARRAY' ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need config for Ticket::Frontend::$Param{Action}->StateType!" ); return; } my @StateType = @{ $Self->{ConfigObject}->Get("Ticket::Frontend::$Param{Action}")->{StateType} }; %States = $Self->{StateObject}->StateGetStatesByType( StateType => \@StateType, Result => 'HASH', ); } # get whole states list else { %States = $Self->{StateObject}->StateList( UserID => $Param{UserID}, ); } # workflow if ( $Self->TicketAcl( %Param, ReturnType => 'Ticket', ReturnSubType => 'State', Data => \%States, ) ) { return $Self->TicketAclData(); } return %States; } =item OwnerCheck() to get the ticket owner my ($OwnerID, $Owner) = $TicketObject->OwnerCheck( TicketID => 123, ); or for access control my $AccessOk = $TicketObject->OwnerCheck( TicketID => 123, OwnerID => 321, ); =cut sub OwnerCheck { my ( $Self, %Param ) = @_; my $SQL = ''; # check needed stuff if ( !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TicketID!' ); return; } # db quote for (qw(TicketID OwnerID)) { $Param{$_} = $Self->{DBObject}->Quote( $Param{$_}, 'Integer' ); } # db query if ( $Param{OwnerID} ) { # check cache if ( defined $Self->{OwnerCheck}->{ $Param{OwnerID} } ) { return $Self->{OwnerCheck}->{ $Param{OwnerID} }; } $SQL = "SELECT user_id FROM ticket " . " WHERE " . " id = $Param{TicketID} AND " . " (user_id = $Param{OwnerID} OR responsible_user_id = $Param{OwnerID})"; } else { $SQL = "SELECT st.user_id, su.$Self->{ConfigObject}->{DatabaseUserTableUser} " . " FROM " . " ticket st, $Self->{ConfigObject}->{DatabaseUserTable} su " . " WHERE " . " st.id = $Param{TicketID} AND " . " st.user_id = su.$Self->{ConfigObject}->{DatabaseUserTableUserID}"; } $Self->{DBObject}->Prepare( SQL => $SQL ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Param{SearchUserID} = $Row[0]; $Param{SearchUser} = $Row[1]; } if ( $Param{OwnerID} ) { if ( $Param{SearchUserID} ) { # fill cache $Self->{OwnerCheck}->{ $Param{OwnerID} } = 1; return 1; } else { # fill cache $Self->{OwnerCheck}->{ $Param{OwnerID} } = 0; return; } } if ( $Param{SearchUserID} ) { return $Param{SearchUserID}, $Param{SearchUser}; } else { return; } } =item OwnerSet() to set the ticket owner (notification to the new owner will be sent) $TicketObject->OwnerSet( TicketID => 123, NewUserID => 555, UserID => 123, ); Optional attribute: SendNoNotification, disable or enable agent and customer notification for this action. Otherwise a notification will be send to agent and cusomer. For example: SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) =cut sub OwnerSet { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } if ( !$Param{NewUserID} && !$Param{NewUser} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need NewUserID or NewUser!' ); return; } # lookup if no NewUserID is given if ( !$Param{NewUserID} ) { $Param{NewUserID} = $Self->{UserObject}->UserLookup( UserLogin => $Param{NewUser} ); } # lookup if no NewUser is given if ( !$Param{NewUser} ) { $Param{NewUser} = $Self->{UserObject}->UserLookup( UserID => $Param{NewUserID} ); } # check if update is needed! my ( $OwnerID, $Owner ) = $Self->OwnerCheck( TicketID => $Param{TicketID} ); if ( $OwnerID eq $Param{NewUserID} ) { # update is "not" needed! return 1; } # db update return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET ' . ' user_id = ?, change_time = current_timestamp, change_by = ? WHERE id = ?', Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ], ); # add history $Self->HistoryAdd( TicketID => $Param{TicketID}, CreateUserID => $Param{UserID}, HistoryType => 'OwnerUpdate', Name => "\%\%$Param{NewUser}\%\%$Param{NewUserID}", ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # send agent notify if ( !$Param{SendNoNotification} ) { if ( $Param{UserID} ne $Param{NewUserID} && $Param{NewUserID} ne $Self->{ConfigObject}->Get('PostmasterUserID') ) { if ( !$Param{Comment} ) { $Param{Comment} = ''; } # get user data my %Preferences = $Self->{UserObject}->GetUserData( UserID => $Param{NewUserID} ); # send agent notification $Self->SendAgentNotification( Type => 'OwnerUpdate', UserData => \%Preferences, CustomerMessageParams => \%Param, TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } # send customer notification email my %Preferences = $Self->{UserObject}->GetUserData( UserID => $Param{NewUserID} ); $Self->SendCustomerNotification( Type => 'OwnerUpdate', CustomerMessageParams => \%Preferences, TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } # ticket event $Self->TicketEventHandlerPost( Event => 'TicketOwnerUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item OwnerList() returns the owner in the past as array with hash ref of the owner data (name, email, ...) my @Owner = $TicketObject->OwnerList( TicketID => 123, ); =cut sub OwnerList { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # db query my @User = (); my $LastOwner = 1; $Self->{DBObject}->Prepare( SQL => 'SELECT sh.name, ht.name, sh.create_by FROM ' . ' ticket_history sh, ticket_history_type ht WHERE ' . ' sh.ticket_id = ? AND ' . ' ht.name IN (\'OwnerUpdate\', \'NewTicket\') AND ' . ' ht.id = sh.history_type_id ORDER BY sh.id', Bind => [ \$Param{TicketID} ], ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { # store result if ( $Row[1] eq 'NewTicket' && $Row[2] ne '1' && $LastOwner ne $Row[2] ) { $LastOwner = $Row[2]; push @User, $Row[2]; } elsif ( $Row[1] eq 'OwnerUpdate' ) { if ( $Row[0] =~ /^New Owner is '(.+?)' \(ID=(.+?)\)/ || $Row[0] =~ /^\%\%(.+?)\%\%(.+?)$/ ) { if ( $2 ne 1 ) { $LastOwner = $2; push @User, $2; } } } } my @UserInfo = (); for (@User) { my %User = $Self->{UserObject}->GetUserData( UserID => $_, Cache => 1, Valid => 1 ); if (%User) { push @UserInfo, \%User; } } return @UserInfo; } =item ResponsibleSet() to set the ticket responsible (notification to the new responsible will be sent) $TicketObject->ResponsibleSet( TicketID => 123, NewUserID => 555, UserID => 213, ); Optional attribute: SendNoNotification, disable or enable agent and customer notification for this action. Otherwise a notification will be send to agent and cusomer. For example: SendNoNotification => 0, # optional 1|0 (send no agent and customer notification) =cut sub ResponsibleSet { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } if ( !$Param{NewUserID} && !$Param{NewUser} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need NewUserID or NewUser!' ); return; } # lookup if no NewUserID is given if ( !$Param{NewUserID} ) { $Param{NewUserID} = $Self->{UserObject}->UserLookup( UserLogin => $Param{NewUser} ) || return; } # lookup if no NewUser is given if ( !$Param{NewUser} ) { $Param{NewUser} = $Self->{UserObject}->UserLookup( UserID => $Param{NewUserID} ) || return; } # check if update is needed! my %Ticket = $Self->TicketGet( TicketID => $Param{TicketID}, UserID => $Param{NewUserID} ); if ( $Ticket{ResponsibleID} eq $Param{NewUserID} ) { # update is "not" needed! # return 1; } # db update return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET responsible_user_id = ?, ' . ' change_time = current_timestamp, change_by = ? ' . ' WHERE id = ?', Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ], ); # add history $Self->HistoryAdd( TicketID => $Param{TicketID}, CreateUserID => $Param{UserID}, HistoryType => 'ResponsibleUpdate', Name => "\%\%$Param{NewUser}\%\%$Param{NewUserID}", ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # send agent notify if ( !$Param{SendNoNotification} ) { if ( $Param{UserID} ne $Param{NewUserID} && $Param{NewUserID} ne $Self->{ConfigObject}->Get('PostmasterUserID') ) { if ( !$Param{Comment} ) { $Param{Comment} = ''; } # get user data my %Preferences = $Self->{UserObject}->GetUserData( UserID => $Param{NewUserID} ); # send agent notification $Self->SendAgentNotification( Type => 'ResponsibleUpdate', UserData => \%Preferences, CustomerMessageParams => \%Param, TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); } # # send customer notification email # my %Preferences = $Self->{UserObject}->GetUserData(UserID => $Param{NewUserID}); # $Self->SendCustomerNotification( # Type => 'ResponsibleUpdate', # CustomerMessageParams => \%Preferences, # TicketID => $Param{TicketID}, # UserID => $Param{UserID}, # ); } # ticket event $Self->TicketEventHandlerPost( Event => 'TicketResponsibleUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item ResponsibleList() returns the responsible in the past as array with hash ref of the owner data (name, email, ...) my @Responsible = $TicketObject->ResponsibleList( TicketID => 123, ); =cut sub ResponsibleList { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # db query my @User = (); my $LastResponsible = 1; $Self->{DBObject}->Prepare( SQL => 'SELECT sh.name, ht.name, sh.create_by FROM ' . ' ticket_history sh, ticket_history_type ht WHERE ' . ' sh.ticket_id = ? AND ' . ' ht.name IN (\'ResponsibleUpdate\', \'NewTicket\') AND ' . ' ht.id = sh.history_type_id ORDER BY sh.id', Bind => [ \$Param{TicketID} ], ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { # store result if ( $Row[1] eq 'NewTicket' && $Row[2] ne '1' && $LastResponsible ne $Row[2] ) { $LastResponsible = $Row[2]; push @User, $Row[2]; } elsif ( $Row[1] eq 'ResponsibleUpdate' ) { if ( $Row[0] =~ /^New Responsible is '(.+?)' \(ID=(.+?)\)/ || $Row[0] =~ /^\%\%(.+?)\%\%(.+?)$/ ) { $LastResponsible = $2; push @User, $2; } } } my @UserInfo = (); for (@User) { my %User = $Self->{UserObject}->GetUserData( UserID => $_, Cache => 1 ); push @UserInfo, \%User; } return @UserInfo; } =item InvolvedAgents() returns array with hash ref of involved agents of a ticket my @InvolvedAgents = $TicketObject->InvolvedAgents( TicketID => 123, ); =cut sub InvolvedAgents { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # db query my @User = (); my %UsedOwner = (); $Self->{DBObject}->Prepare( SQL => 'SELECT sh.name, sh.create_by FROM ' . ' ticket_history sh, ticket_history_type ht WHERE ' . ' sh.ticket_id = ? AND ht.id = sh.history_type_id' . ' ORDER BY sh.id', Bind => [ \$Param{TicketID} ], ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { # store result if ( $Row[1] ne 1 && !$UsedOwner{ $Row[1] } ) { $UsedOwner{ $Row[1] } = $Row[1]; push @User, $Row[1]; } } my @UserInfo = (); for (@User) { my %User = $Self->{UserObject}->GetUserData( UserID => $_, Valid => 1, Cache => 1, ); if (%User) { push @UserInfo, \%User; } } return @UserInfo; } =item PrioritySet() to set the ticket priority $TicketObject->PrioritySet( TicketID => 123, Priority => 'low', UserID => 213, ); $TicketObject->PrioritySet( TicketID => 123, PriorityID => 2, UserID => 213, ); =cut sub PrioritySet { my ( $Self, %Param ) = @_; # lookup! if ( !$Param{PriorityID} && $Param{Priority} ) { $Param{PriorityID} = $Self->{PriorityObject}->PriorityLookup( Priority => $Param{Priority}, ); } if ( $Param{PriorityID} && !$Param{Priority} ) { $Param{Priority} = $Self->{PriorityObject}->PriorityLookup( PriorityID => $Param{PriorityID}, ); } # check needed stuff for (qw(TicketID UserID PriorityID Priority)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } my %Ticket = $Self->TicketGet(%Param); # check if update is needed if ( $Ticket{Priority} eq $Param{Priority} ) { # update not needed return 1; } # permission check my %PriorityList = $Self->PriorityList(%Param); if ( !$PriorityList{ $Param{PriorityID} } ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "Permission denied on TicketID: $Param{TicketID}!", ); return; } # db update return if !$Self->{DBObject}->Do( SQL => 'UPDATE ticket SET ticket_priority_id = ?, ' . ' change_time = current_timestamp, change_by = ?' . ' WHERE id = ?', Bind => [ \$Param{PriorityID}, \$Param{UserID}, \$Param{TicketID} ], ); # add history $Self->HistoryAdd( TicketID => $Param{TicketID}, QueueID => $Ticket{QueueID}, CreateUserID => $Param{UserID}, HistoryType => 'PriorityUpdate', Name => "\%\%$Ticket{Priority}\%\%$Ticket{PriorityID}" . "\%\%$Param{Priority}\%\%$Param{PriorityID}", ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # ticket event $Self->TicketEventHandlerPost( Event => 'TicketPriorityUpdate', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item PriorityList() to get the priority list for a ticket (depends on workflow, if configured) my %Priorities = $TicketObject->PriorityList( TicketID => 123, UserID => 123, ); my %Priorities = $TicketObject->PriorityList( QueueID => 123, UserID => 123, ); =cut sub PriorityList { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!' ); return; } # check needed stuff # if (!$Param{QueueID} && !$Param{TicketID}) { # $Self->{LogObject}->Log(Priority => 'error', Message => 'Need QueueID or TicketID!'); # return; # } # sql my %Data = $Self->{PriorityObject}->PriorityList(%Param); # workflow if ( $Self->TicketAcl( %Param, ReturnType => 'Ticket', ReturnSubType => 'Priority', Data => \%Data, ) ) { return $Self->TicketAclData(); } return %Data; } =item HistoryTicketStatusGet() get a hash with ticket id as key and a hash ref (result of HistoryTicketGet) of all affected tickets in this time area. my %Tickets = $TicketObject->HistoryTicketStatusGet( StartDay => 12, StartMonth => 1, StartYear => 2006, StopDay => 18, StopMonth => 1, StopYear => 2006, Force => 0, ); =cut sub HistoryTicketStatusGet { my ( $Self, %Param ) = @_; my %Ticket = (); # check needed stuff for (qw(StopYear StopMonth StopDay StartYear StartMonth StartDay)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # format month and day params for (qw(StopMonth StopDay StartMonth StartDay)) { $Param{$_} = sprintf( "%02d", $Param{$_} ); } my $SQLExt = ''; for ( qw(NewTicket FollowUp OwnerUpdate PriorityUpdate CustomerUpdate StateUpdate TicketFreeTextUpdate PhoneCallCustomer Forward Bounce SendAnswer EmailCustomer PhoneCallAgent WebRequestCustomer) ) { my $ID = $Self->HistoryTypeLookup( Type => $_ ); if ( !$SQLExt ) { $SQLExt = "AND history_type_id IN ($ID"; } else { $SQLExt .= ",$ID"; } } if ($SQLExt) { $SQLExt .= ')'; } $Self->{DBObject}->Prepare( SQL => "SELECT DISTINCT(th.ticket_id), th.create_time FROM " . "ticket_history th WHERE " . "th.create_time <= '$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} 23:59:59' " . "AND " . "th.create_time >= '$Param{StartYear}-$Param{StartMonth}-$Param{StartDay} 00:00:01' " . "$SQLExt ORDER BY th.create_time DESC", Limit => 150000, ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Ticket{ $Row[0] } = 1; } for my $TicketID ( keys %Ticket ) { my %TicketData = $Self->HistoryTicketGet( TicketID => $TicketID, StopYear => $Param{StopYear}, StopMonth => $Param{StopMonth}, StopDay => $Param{StopDay}, Force => $Param{Force} || 0, ); if (%TicketData) { $Ticket{$TicketID} = \%TicketData; } else { $Ticket{$TicketID} = {}; } } return %Ticket; } =item HistoryTicketGet() returns a hash of the ticket history info at this time. my %HistoryData = $TicketObject->HistoryTicketGet( StopYear => 2003, StopMonth => 12, StopDay => 24, TicketID => 123, Force => 0, ); returns TicketNumber TicketID Type TypeID Queue QueueID Priority PriorityID State StateID Owner OwnerID CreateUserID CreateTime (timestamp) CreateOwnerID CreatePriority CreatePriorityID CreateState CreateStateID CreateQueue CreateQueueID LockFirst (timestamp) LockLast (timestamp) UnlockFirst (timestamp) UnlockLast (timestamp) =cut sub HistoryTicketGet { my ( $Self, %Param ) = @_; my %Ticket = (); # check needed stuff for (qw(TicketID StopYear StopMonth StopDay)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # format month and day params for (qw(StopMonth StopDay)) { $Param{$_} = sprintf( "%02d", $Param{$_} ); } # check cache my $Path = $Self->{ConfigObject}->Get('Home') . "/var/tmp/TicketHistoryCache/$Param{StopYear}/$Param{StopMonth}/"; my $File = $Self->{MainObject}->FilenameCleanUp( Filename => "TicketHistoryCache-$Param{TicketID}-$Param{StopYear}-$Param{StopMonth}-$Param{StopDay}.cache", Type => 'local', ); # write cache if ( !$Param{Force} && -f "$Path/$File" ) { my $ContentARRAYRef = $Self->{MainObject}->FileRead( Directory => $Path, Filename => $File, Result => 'ARRAY', # optional - SCALAR|ARRAY ); if ($ContentARRAYRef) { for my $Line ( @{$ContentARRAYRef} ) { if ( $Line =~ /^(.+?):(.+?)$/ ) { $Ticket{$1} = $2; } } return %Ticket; } } # db access my $Time = "$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} 23:59:59"; $Self->{DBObject}->Prepare( SQL => 'SELECT th.name, tht.name, th.create_time, th.create_by, th.ticket_id, ' . 'th.article_id, th.queue_id, th.state_id, th.priority_id, th.owner_id, th.type_id ' . ' FROM ticket_history th, ticket_history_type tht WHERE ' . 'th.history_type_id = tht.id AND th.ticket_id = ? AND th.create_time <= ? ' . 'ORDER BY th.create_time, th.id ASC', Bind => [ \$Param{TicketID}, \$Time ], Limit => 3000, ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { if ( $Row[1] eq 'NewTicket' ) { if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/ || $Row[0] =~ /Ticket=\[(.+?)\],.+?Q\=(.+?);P\=(.+?);S\=(.+?)/ ) { $Ticket{TicketNumber} = $1; $Ticket{Queue} = $2; $Ticket{CreateQueue} = $2; $Ticket{Priority} = $3; $Ticket{CreatePriority} = $3; $Ticket{State} = $4; $Ticket{CreateState} = $4; $Ticket{TicketID} = $Row[4]; $Ticket{Owner} = 'root'; $Ticket{CreateUserID} = $Row[3]; $Ticket{CreateTime} = $Row[2]; } else { # COMPAT: compat to 1.1 # NewTicket $Ticket{TicketVersion} = '1.1'; $Ticket{TicketID} = $Row[4]; $Ticket{CreateUserID} = $Row[3]; $Ticket{CreateTime} = $Row[2]; } $Ticket{CreateOwnerID} = $Row[9] || ''; $Ticket{CreatePriorityID} = $Row[8] || ''; $Ticket{CreateStateID} = $Row[7] || ''; $Ticket{CreateQueueID} = $Row[6] || ''; } # COMPAT: compat to 1.1 elsif ( $Row[1] eq 'PhoneCallCustomer' ) { $Ticket{TicketVersion} = '1.1'; $Ticket{TicketID} = $Row[4]; $Ticket{CreateUserID} = $Row[3]; $Ticket{CreateTime} = $Row[2]; } elsif ( $Row[1] eq 'Move' ) { if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ || $Row[0] =~ /^Ticket moved to Queue '(.+?)'/ ) { $Ticket{Queue} = $1; } } elsif ( $Row[1] eq 'StateUpdate' || $Row[1] eq 'Close successful' || $Row[1] eq 'Close unsuccessful' || $Row[1] eq 'Open' || $Row[1] eq 'Misc' ) { if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)(\%\%|)$/ || $Row[0] =~ /^Old: '(.+?)' New: '(.+?)'/ || $Row[0] =~ /^Changed Ticket State from '(.+?)' to '(.+?)'/ ) { $Ticket{State} = $2; $Ticket{StateTime} = $Row[2]; } } elsif ( $Row[1] eq 'TicketFreeTextUpdate' ) { if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/ ) { $Ticket{ 'Ticket' . $1 } = $2; $Ticket{ 'Ticket' . $3 } = $4; $Ticket{$1} = $2; $Ticket{$3} = $4; } } elsif ( $Row[1] eq 'PriorityUpdate' ) { if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) { $Ticket{Priority} = $3; } } elsif ( $Row[1] eq 'OwnerUpdate' ) { if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)/ || $Row[0] =~ /^New Owner is '(.+?)'/ ) { $Ticket{Owner} = $1; } } elsif ( $Row[1] eq 'Lock' ) { if ( !$Ticket{LockFirst} ) { $Ticket{LockFirst} = $Row[2]; } $Ticket{LockLast} = $Row[2]; } elsif ( $Row[1] eq 'Unlock' ) { if ( !$Ticket{UnlockFirst} ) { $Ticket{UnlockFirst} = $Row[2]; } $Ticket{UnlockLast} = $Row[2]; } # get default options $Ticket{TypeID} = $Row[10] || ''; $Ticket{OwnerID} = $Row[9] || ''; $Ticket{PriorityID} = $Row[8] || ''; $Ticket{StateID} = $Row[7] || ''; $Ticket{QueueID} = $Row[6] || ''; } if ( !%Ticket ) { $Self->{LogObject}->Log( Priority => 'notice', Message => "No such TicketID in ticket history till " . "'$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} 23:59:59' ($Param{TicketID})!", ); return; } else { # update old ticket info my %CurrentTicketData = $Self->TicketGet( TicketID => $Ticket{TicketID} ); for (qw(State Priority Queue TicketNumber)) { if ( !$Ticket{$_} ) { $Ticket{$_} = $CurrentTicketData{$_}; } if ( !$Ticket{"Create$_"} ) { $Ticket{"Create$_"} = $CurrentTicketData{$_}; } } # check if we should cache this ticket data my ( $Sec, $Min, $Hour, $Day, $Month, $Year, $WDay ) = $Self->{TimeObject}->SystemTime2Date( SystemTime => $Self->{TimeObject}->SystemTime(), ); # if the request is for the last month or older, cache it if ( $Year <= $Param{StopYear} && $Month > $Param{StopMonth} ) { # create sub directory if needed if ( !-e $Path && !File::Path::mkpath( [$Path], 0, '0775' ) ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Can't create directory: $Path: $!", ); } # write cache file my $Content = ''; for my $Key ( keys %Ticket ) { $Content .= "$Key:$Ticket{$Key}\n"; } $Self->{MainObject}->FileWrite( Directory => $Path, Filename => $File, Content => \$Content, ); } return %Ticket; } } =item HistoryTypeLookup() returns the id of the requested history type. my $ID = $TicketObject->HistoryTypeLookup(Type => 'Move'); =cut sub HistoryTypeLookup { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{Type} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need Type!' ); return; } # check if we ask the same request? my $CacheKey = 'Ticket::History::HistoryTypeLookup::' . $Param{Type}; if ( $Self->{$CacheKey} ) { return $Self->{$CacheKey}; } # db query $Self->{DBObject}->Prepare( SQL => 'SELECT id FROM ticket_history_type WHERE name = ?', Bind => [ \$Param{Type} ], ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Self->{$CacheKey} = $Row[0]; } # check if data exists if ( !$Self->{$CacheKey} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "No TypeID for $Param{Type} found!", ); return; } return $Self->{$CacheKey}; } =item HistoryAdd() add a history entry to an ticket $TicketObject->HistoryAdd( Name => 'Some Comment', HistoryType => 'Move', # see system tables TicketID => 123, ArticleID => 1234, # not required! QueueID => 123, # not required! TypeID => 123, # not required! CreateUserID => 123, ); =cut sub HistoryAdd { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{Name} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need Name!' ); return; } # lookup! if ( !$Param{HistoryTypeID} && $Param{HistoryType} ) { $Param{HistoryTypeID} = $Self->HistoryTypeLookup( Type => $Param{HistoryType} ); } # check needed stuff for (qw(TicketID CreateUserID HistoryTypeID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get ValidID! if ( !$Param{ValidID} ) { $Param{ValidID} = $Self->{ValidObject}->ValidIDsGet(); } # get QueueID if ( !$Param{QueueID} ) { $Param{QueueID} = $Self->TicketQueueID( TicketID => $Param{TicketID} ); } # get type if ( !$Param{TypeID} ) { my %Ticket = $Self->TicketGet(%Param); $Param{TypeID} = $Ticket{TypeID}; } # get owner if ( !$Param{OwnerID} ) { my %Ticket = $Self->TicketGet(%Param); $Param{OwnerID} = $Ticket{OwnerID}; } # get priority if ( !$Param{PriorityID} ) { my %Ticket = $Self->TicketGet(%Param); $Param{PriorityID} = $Ticket{PriorityID}; } # get state if ( !$Param{StateID} ) { my %Ticket = $Self->TicketGet(%Param); $Param{StateID} = $Ticket{StateID}; } # limit name to 200 chars if ( $Param{Name} ) { $Param{Name} = substr( $Param{Name}, 0, 200 ); } # db quote if ( !$Param{ArticleID} ) { $Param{ArticleID} = undef; } # db insert return if !$Self->{DBObject}->Do( SQL => 'INSERT INTO ticket_history ' . ' (name, history_type_id, ticket_id, article_id, queue_id, owner_id, ' . ' priority_id, state_id, type_id, valid_id, ' . ' create_time, create_by, change_time, change_by) ' . 'VALUES ' . '(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)', Bind => [ \$Param{Name}, \$Param{HistoryTypeID}, \$Param{TicketID}, \$Param{ArticleID}, \$Param{QueueID}, \$Param{OwnerID}, \$Param{PriorityID}, \$Param{StateID}, \$Param{TypeID}, \$Param{ValidID}, \$Param{CreateUserID}, \$Param{CreateUserID}, ], ); # ticket event $Self->TicketEventHandlerPost( Event => 'HistoryAdd', UserID => $Param{CreateUserID}, TicketID => $Param{TicketID}, ); return 1; } =item HistoryGet() get ticket history as array with hashes (TicketID, ArticleID, Name, CreateBy, CreateTime and HistoryType) my @HistoryLines = $TicketObject->HistoryGet( TicketID => 123, UserID => 123, ); =cut sub HistoryGet { my ( $Self, %Param ) = @_; my @Lines; # check needed stuff for (qw(TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } $Self->{DBObject}->Prepare( SQL => 'SELECT sh.name, sh.article_id, sh.create_time, sh.create_by, ht.name ' . ' FROM ticket_history sh, ticket_history_type ht WHERE ' . ' sh.ticket_id = ? AND ht.id = sh.history_type_id' . ' ORDER BY sh.create_time, sh.id', Bind => [ \$Param{TicketID} ], ); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { my %Data; $Data{TicketID} = $Param{TicketID}; $Data{ArticleID} = $Row[1] || 0; $Data{Name} = $Row[0]; $Data{CreateBy} = $Row[3]; $Data{CreateTime} = $Row[2]; $Data{HistoryType} = $Row[4]; push @Lines, \%Data; } # get user data for my $Data (@Lines) { my %UserInfo = $Self->{UserObject}->GetUserData( UserID => $Data->{CreateBy}, Cached => 1 ); %{$Data} = ( %{$Data}, %UserInfo ); } return @Lines; } =item HistoryDelete() delete a ticket history (from storage) $TicketObject->HistoryDelete( TicketID => 123, UserID => 123, ); =cut sub HistoryDelete { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # delete from db return if !$Self->{DBObject}->Do( SQL => 'DELETE FROM ticket_history WHERE ticket_id = ?', Bind => [ \$Param{TicketID} ], ); # ticket event $Self->TicketEventHandlerPost( Event => 'HistoryDelete', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketAccountedTimeGet() returns the accounted time of a ticket. my $AccountedTime = $TicketObject->TicketAccountedTimeGet(TicketID => 1234); =cut sub TicketAccountedTimeGet { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{TicketID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need TicketID!' ); return; } # db query $Self->{DBObject}->Prepare( SQL => 'SELECT time_unit FROM time_accounting WHERE ticket_id = ?', Bind => [ \$Param{TicketID} ], ); my $AccountedTime = 0; while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Row[0] =~ s/,/./g; $AccountedTime = $AccountedTime + $Row[0]; } return $AccountedTime; } =item TicketAccountTime() account time to a ticket. $TicketObject->TicketAccountTime( TicketID => 1234, ArticleID => 23542, TimeUnit => '4.5', UserID => 1, ); =cut sub TicketAccountTime { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID ArticleID TimeUnit UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # check some wrong formats $Param{TimeUnit} =~ s/,/\./g; $Param{TimeUnit} =~ s/ //g; $Param{TimeUnit} =~ s/^(\d{1,10}\.\d\d).+?$/$1/g; chomp $Param{TimeUnit}; # db quote for (qw(TimeUnit)) { $Param{$_} = $Self->{DBObject}->Quote( $Param{$_}, 'Number' ); } # db update return if !$Self->{DBObject}->Do( SQL => "INSERT INTO time_accounting " . " (ticket_id, article_id, time_unit, create_time, create_by, change_time, change_by) " . " VALUES (?, ?, $Param{TimeUnit}, current_timestamp, ?, current_timestamp, ?)", Bind => [ \$Param{TicketID}, \$Param{ArticleID}, \$Param{UserID}, \$Param{UserID}, ], ); # add history my $AccountedTime = $Self->TicketAccountedTimeGet( TicketID => $Param{TicketID} ); $Self->HistoryAdd( TicketID => $Param{TicketID}, ArticleID => $Param{ArticleID}, CreateUserID => $Param{UserID}, HistoryType => 'TimeAccounting', Name => "\%\%$Param{TimeUnit}\%\%$AccountedTime", ); # clear ticket cache delete $Self->{ 'Cache::GetTicket' . $Param{TicketID} }; # ticket event $Self->TicketEventHandlerPost( Event => 'TicketAccountTime', UserID => $Param{UserID}, TicketID => $Param{TicketID}, ); return 1; } =item TicketMerge() merge two tickets $TicketObject->TicketMerge( MainTicketID => 412, MergeTicketID => 123, UserID => 123, ); =cut sub TicketMerge { my ( $Self, %Param ) = @_; # check needed stuff for (qw(MainTicketID MergeTicketID UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # change ticket id of merge ticket to main ticket return if !$Self->{DBObject}->Do( SQL => 'UPDATE article SET ticket_id = ? WHERE ticket_id = ?', Bind => [ \$Param{MainTicketID}, \$Param{MergeTicketID} ], ); my %MainTicket = $Self->TicketGet( TicketID => $Param{MainTicketID} ); my %MergeTicket = $Self->TicketGet( TicketID => $Param{MergeTicketID} ); my $Body = $Self->{ConfigObject}->Get('Ticket::Frontend::AutomaticMergeText'); $Body =~ s{}{$MergeTicket{TicketNumber}}xms; $Body =~ s{}{$MainTicket{TicketNumber}}xms; # add merge article to merge ticket $Self->ArticleCreate( TicketID => $Param{MergeTicketID}, SenderType => 'agent', ArticleType => 'note-external', ContentType => "text/plain; charset=ascii", UserID => $Param{UserID}, HistoryType => 'AddNote', HistoryComment => '%%Note', Subject => 'Ticket Merged', Body => $Body, NoAgentNotify => 1, ); # add merge history to merge ticket $Self->HistoryAdd( TicketID => $Param{MergeTicketID}, HistoryType => 'Merged', Name => "Merged Ticket ($MergeTicket{TicketNumber}/$Param{MergeTicketID}) to ($MainTicket{TicketNumber}/$Param{MainTicketID})", CreateUserID => $Param{UserID}, ); # add merge history to main ticket $Self->HistoryAdd( TicketID => $Param{MainTicketID}, HistoryType => 'Merged', Name => "Merged Ticket ($MergeTicket{TicketNumber}/$Param{MergeTicketID}) to ($MainTicket{TicketNumber}/$Param{MainTicketID})", CreateUserID => $Param{UserID}, ); # link tickets $Self->{LinkObject}->LinkAdd( SourceObject => 'Ticket', SourceKey => $Param{MainTicketID}, TargetObject => 'Ticket', TargetKey => $Param{MergeTicketID}, Type => 'ParentChild', State => 'Valid', UserID => $Param{UserID}, ); # set new state of merge ticket $Self->StateSet( State => 'merged', TicketID => $Param{MergeTicketID}, UserID => $Param{UserID}, ); # unlock ticket $Self->LockSet( Lock => 'unlock', TicketID => $Param{MergeTicketID}, UserID => $Param{UserID}, ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketMerge', TicketID => $Param{MergeTicketID}, UserID => $Param{UserID}, ); return 1; } =item TicketWatchGet() to get all additional attributes of an watched ticket my %Watch = $TicketObject->TicketWatchGet( TicketID => 111, UserID => 123 ); =cut sub TicketWatchGet { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # get all attributes of an watched ticket $Self->{DBObject}->Prepare( SQL => 'SELECT create_time, create_by, change_time, change_by' . ' FROM ticket_watcher WHERE ticket_id = ? AND user_id = ?', Bind => [ \$Param{TicketID}, \$Param{UserID} ], ); # fetch the result my %Data = (); while ( my @Row = $Self->{DBObject}->FetchrowArray() ) { $Data{CreateTime} = $Row[0]; $Data{CreateBy} = $Row[1]; $Data{ChangeTime} = $Row[2]; $Data{ChangeBy} = $Row[3]; } return %Data; } =item TicketWatchSubscribe() to subscribe a ticket to watch it $TicketObject->TicketWatchSubscribe( TicketID => 111, WatchUserID => 123, UserID => 123, ); =cut sub TicketWatchSubscribe { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID WatchUserID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # db access return if !$Self->{DBObject}->Do( SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ? AND user_id = ?', Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ], ); return if !$Self->{DBObject}->Do( SQL => 'INSERT INTO ticket_watcher (ticket_id, user_id, create_time, create_by, change_time, change_by) ' . ' VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)', Bind => [ \$Param{TicketID}, \$Param{WatchUserID}, \$Param{UserID}, \$Param{UserID} ], ); # get user data my %User = $Self->{UserObject}->GetUserData( UserID => $Param{WatchUserID}, Cached => 1, ); # add history $Self->HistoryAdd( TicketID => $Param{TicketID}, CreateUserID => $Param{UserID}, HistoryType => 'Subscribe', Name => "\%\%$User{UserFirstname} $User{UserLastname} ($User{UserLogin})", ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketSubscribe', TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); return 1; } =item TicketWatchUnsubscribe() to remove a subscribtion of a ticket $TicketObject->TicketWatchUnsubscribe( TicketID => 111, WatchUserID => 123, UserID => 123, ); =cut sub TicketWatchUnsubscribe { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID WatchUserID UserID)) { if ( !defined $Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # db access return if !$Self->{DBObject}->Do( SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ? AND user_id = ?', Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ], ); # get user data my %User = $Self->{UserObject}->GetUserData( UserID => $Param{WatchUserID}, Cached => 1, ); # add history $Self->HistoryAdd( TicketID => $Param{TicketID}, CreateUserID => $Param{UserID}, HistoryType => 'Unsubscribe', Name => "\%\%$User{UserFirstname} $User{UserLastname} ($User{UserLogin})", ); # ticket event $Self->TicketEventHandlerPost( Event => 'TicketUnsubscribe', TicketID => $Param{TicketID}, UserID => $Param{UserID}, ); return 1; } =item TicketAcl() prepare ACL execution of current state $TicketObject->TicketAcl( Data => '-', Action => 'AgentTicketZoom', TicketID => 123, ReturnType => 'Action', ReturnSubType => '-', UserID => 123, ); or $TicketObject->TicketAcl( Data => { 1 => 'new', 2 => 'open', # ... }, ReturnType => 'Ticket', ReturnSubType => 'State', ) =cut sub TicketAcl { my ( $Self, %Param ) = @_; # check needed stuff if ( !$Param{UserID} && !$Param{CustomerUserID} ) { $Self->{LogObject}->Log( Priority => 'error', Message => 'Need UserID or CustomerUserID!' ); return; } # check needed stuff for (qw(ReturnSubType ReturnType Data)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # check if workflows are configured, if not, just return if ( ( !$Self->{ConfigObject}->Get('TicketAcl') && !$Self->{ConfigObject}->Get('Ticket::Acl::Module') && !$Self->{ConfigObject}->Get('Ticket::EventModulePost') ) || ( $Param{UserID} && $Param{UserID} == 1 ) ) { return; } my %Checks = (); # match also frontend options if ( $Param{Action} ) { undef $Self->{TicketAclActionData}; $Checks{Frontend} = { Action => $Param{Action}, }; } # use ticket data if ticket id is given if ( $Param{TicketID} ) { my %Ticket = $Self->TicketGet(%Param); $Checks{Ticket} = \%Ticket; } # use user data if ( $Param{UserID} ) { my %User = $Self->{UserObject}->GetUserData( UserID => $Param{UserID}, Cached => 1, ); for my $Type ( @{ $Self->{ConfigObject}->Get('System::Permission') } ) { my @Groups = $Self->{GroupObject}->GroupMemberList( UserID => $Param{UserID}, Result => 'Name', Type => $Type, Cached => 1, ); $User{"Group_$Type"} = \@Groups; } $Checks{User} = \%User; } # use customer user data if ( $Param{CustomerUserID} ) { my %CustomerUser = $Self->{CustomerUserObject}->CustomerUserDataGet( User => $Param{CustomerUserID}, ); for my $Type ( @{ $Self->{ConfigObject}->Get('System::Customer::Permission') } ) { my @Groups = $Self->{CustomerGroupObject}->GroupMemberList( UserID => $Param{CustomerUserID}, Result => 'Name', Type => $Type, Cached => 1, ); $CustomerUser{"Group_$Type"} = \@Groups; } $Checks{CustomerUser} = \%CustomerUser; } # use queue data (if given) if ( $Param{QueueID} ) { my %Queue = $Self->{QueueObject}->QueueGet( ID => $Param{QueueID} ); $Checks{Queue} = \%Queue; } elsif ( $Param{Queue} ) { my %Queue = $Self->{QueueObject}->QueueGet( Name => $Param{Queue} ); $Checks{Queue} = \%Queue; } # use queue data (if given) if ( $Param{ServiceID} ) { my %Service = $Self->{ServiceObject}->ServiceGet( ServiceID => $Param{ServiceID}, UserID => 1 ); $Checks{Service} = \%Service; } elsif ( $Param{Service} ) { my %Service = $Self->{ServiceObject}->ServiceGet( Name => $Param{Service}, UserID => 1 ); $Checks{Service} = \%Service; } # check acl config my %Acls = (); if ( $Self->{ConfigObject}->Get('TicketAcl') ) { %Acls = %{ $Self->{ConfigObject}->Get('TicketAcl') }; } # check acl module my $Modules = $Self->{ConfigObject}->Get('Ticket::Acl::Module'); if ($Modules) { for my $Module ( sort keys %{$Modules} ) { next if !$Self->{MainObject}->Require( $Modules->{$Module}->{Module} ); my $Generic = $Modules->{$Module}->{Module}->new( %{$Self}, TicketObject => $Self, ); $Generic->Run( %Param, Acl => \%Acls, Checks => \%Checks, Config => $Modules->{$Module}, ); } } # get used data my %Data = (); if ( ref $Param{Data} ) { undef $Self->{TicketAclActionData}; %Data = %{ $Param{Data} }; } my %NewData = (); my $UseNewMasterParams = 0; for my $Acl ( sort keys %Acls ) { my %Step = %{ $Acls{$Acl} }; # check force match my $ForceMatch = 1; for ( keys %{ $Step{Properties} } ) { $ForceMatch = 0; } # set match params my $Match = 1; my $Match3 = 0; my $UseNewParams = 0; for my $Key ( keys %{ $Step{Properties} } ) { for my $Data ( keys %{ $Step{Properties}->{$Key} } ) { my $Match2 = 0; for my $Item ( @{ $Step{Properties}->{$Key}->{$Data} } ) { if ( ref $Checks{$Key}->{$Data} eq 'ARRAY' ) { my $Match4 = 0; for my $Array ( @{ $Checks{$Key}->{$Data} } ) { # eq match if ( $Item eq $Array ) { $Match4 = 1; } # regexp match elsif ( $Item =~ /^\[RegExp\](.+?)$/ ) { if ( $Array =~ /$1/ ) { $Match4 = 1; } } if ($Match4) { $Match2 = 1; # debug log if ( $Self->{Debug} > 4 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Workflow '$Acl/$Key/$Data' MatchedARRAY ($Item eq $Array)", ); } } } } elsif ( defined $Checks{$Key}->{$Data} ) { my $Match4 = 0; # eq match if ( $Item eq $Checks{$Key}->{$Data} ) { $Match4 = 1; } # regexp match elsif ( $Item =~ /^\[RegExp\](.+?)$/ ) { if ( $Checks{$Key}->{$Data} =~ /$1/ ) { $Match4 = 1; } } if ($Match4) { $Match2 = 1; # debug if ( $Self->{Debug} > 4 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Workflow '$Acl/$Key/$Data' Matched ($Item eq $Checks{$Key}->{$Data})", ); } } } } if ( !$Match2 ) { $Match = 0; } $Match3 = 1; } } # check force option if ($ForceMatch) { $Match = 1; $Match3 = 1; } # debug log my %NewTmpData = (); if ( $Match && $Match3 ) { if ( $Self->{Debug} > 2 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Matched Workflow '$Acl'->'$Param{ReturnSubType}'", ); } } # build new action data hash if ( %Checks && $Match && $Match3 && $Param{ReturnType} eq 'Action' && $Step{Possible}->{ $Param{ReturnType} } ) { $Self->{TicketAclActionData} = { %{ $Self->{ConfigObject}->Get('TicketACL::Default::Action') }, %{ $Step{Possible}->{ $Param{ReturnType} } }, }; } # build new ticket data hash if ( %Checks && $Match && $Match3 && $Step{Possible}->{Ticket}->{ $Param{ReturnSubType} } ) { $UseNewParams = 1; # debug log if ( $Self->{Debug} > 3 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Workflow '$Acl' used with Possible:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } # possible list for my $ID ( keys %Data ) { my $Match = 0; for my $New ( @{ $Step{Possible}->{Ticket}->{ $Param{ReturnSubType} } } ) { # eq match if ( $Data{$ID} eq $New ) { $Match = 1; } # regexp match elsif ( $New =~ /^\[RegExp\](.+?)$/ ) { if ( $Data{$ID} =~ /$1/ ) { $Match = 1; } } if ($Match) { $NewTmpData{$ID} = $Data{$ID}; if ( $Self->{Debug} > 4 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Workflow '$Acl' param '$Data{$ID}' used with Possible:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } } if ( %Checks && $Match && $Match3 && $Step{PossibleNot}->{Ticket}->{ $Param{ReturnSubType} } ) { $UseNewParams = 1; # debug log if ( $Self->{Debug} > 3 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Workflow '$Acl' used with PossibleNot:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } # not possible list for my $ID ( keys %Data ) { my $Match = 1; for my $New ( @{ $Step{PossibleNot}->{Ticket}->{ $Param{ReturnSubType} } } ) { # eq match if ( $Data{$ID} eq $New ) { $Match = 0; } # regexp match elsif ( $New =~ /^\[RegExp\](.+?)$/ ) { if ( $Data{$ID} =~ /$1/ ) { $Match = 0; } } } if ($Match) { $NewTmpData{$ID} = $Data{$ID}; if ( $Self->{Debug} > 4 ) { $Self->{LogObject}->Log( Priority => 'debug', Message => "Workflow '$Acl' param '$Data{$ID}' in not used with PossibleNot:'$Param{ReturnType}:$Param{ReturnSubType}'", ); } } } } # remember to new params if given if ($UseNewParams) { %NewData = %NewTmpData; $UseNewMasterParams = 1; } # return new params if stop after this step if ( $UseNewParams && $Step{StopAfterMatch} ) { $Self->{TicketAclData} = \%NewData; return 1; } } # return if no new param exists return if !$UseNewMasterParams; $Self->{TicketAclData} = \%NewData; return 1; } =item TicketAclData() return the current ACL data hash after TicketAcl() my %Acl = $TicketObject->TicketAclData(); =cut sub TicketAclData { my ( $Self, %Param ) = @_; return %{ $Self->{TicketAclData} }; } =item TicketAclActionData() return the current ACL action data hash after TicketAcl() my %AclAction = $TicketObject->TicketAclActionData(); =cut sub TicketAclActionData { my ( $Self, %Param ) = @_; if ( $Self->{TicketAclActionData} ) { return %{ $Self->{TicketAclActionData} }; } else { return %{ $Self->{ConfigObject}->Get('TicketACL::Default::Action') }; } } =item TicketEventHandlerPost() call ticket event post handler, returns true if it's executed successfully $TicketObject->TicketEventHandlerPost( TicketID => 123, Event => 'TicketStateUpdate', UserID => 123, ); events available: TicketCreate, TicketDelete, TicketTitleUpdate, TicketUnlockTimeoutUpdate, TicketQueueUpdate, TicketTypeUpdate, TicketServiceUpdate, TicketSLAUpdate, TicketCustomerUpdate, TicketFreeTextUpdate, TicketFreeTimeUpdate, TicketPendingTimeUpdate, TicketLockUpdate, TicketStateUpdate, TicketOwnerUpdate, TicketResponsibleUpdate, TicketPriorityUpdate, HistoryAdd, HistoryDelete, TicketAccountTime, TicketMerge, ArticleCreate, ArticleFreeTextUpdate, ArticleUpdate, ArticleSend, ArticleBounce, ArticleAgentNotification, ArticleCustomerNotification, ArticleAutoResponse, ArticleFlagSet, ArticleFlagDelete; =cut sub TicketEventHandlerPost { my ( $Self, %Param ) = @_; # check needed stuff for (qw(TicketID Event UserID)) { if ( !$Param{$_} ) { $Self->{LogObject}->Log( Priority => 'error', Message => "Need $_!" ); return; } } # load ticket event module my $Modules = $Self->{ConfigObject}->Get('Ticket::EventModulePost'); return if !$Modules; for my $Module ( sort keys %{$Modules} ) { if ( !$Modules->{$Module}->{Event} || $Param{Event} =~ /$Modules->{$Module}->{Event}/i ) { if ( $Self->{MainObject}->Require( $Modules->{$Module}->{Module} ) ) { my $Generic = $Modules->{$Module}->{Module}->new( %{$Self}, TicketObject => $Self, ); $Generic->Run( %Param, Config => $Modules->{$Module}, ); } } } # COMPAT: compat to 2.0 if ( 1 && !$Param{CompatOff} ) { if ( $Param{Event} eq 'TicketStateUpdate' ) { $Param{Event} = 'StateSet'; } elsif ( $Param{Event} eq 'TicketPriorityUpdate' ) { $Param{Event} = 'PrioritySet'; } elsif ( $Param{Event} eq 'TicketLockUpdate' ) { $Param{Event} = 'LockSet'; } elsif ( $Param{Event} eq 'TicketOwnerUpdate' ) { $Param{Event} = 'OwnerSet'; } elsif ( $Param{Event} eq 'TicketQueueUpdate' ) { $Param{Event} = 'MoveTicket'; } elsif ( $Param{Event} eq 'TicketCustomerUpdate' ) { $Param{Event} = 'SetCustomerData'; } elsif ( $Param{Event} eq 'TicketFreeTextUpdate' ) { $Param{Event} = 'TicketFreeTextSet'; } elsif ( $Param{Event} eq 'TicketFreeTimeUpdate' ) { $Param{Event} = 'TicketFreeTimeSet'; } elsif ( $Param{Event} eq 'TicketPendingTimeUpdate' ) { $Param{Event} = 'TicketPendingTimeSet'; } elsif ( $Param{Event} eq 'ArticleFreeTextUpdate' ) { $Param{Event} = 'ArticleFreeTextSet'; } elsif ( $Param{Event} eq 'ArticleAgentNotification' ) { $Param{Event} = 'SendAgentNotification'; } elsif ( $Param{Event} eq 'ArticleCustomerNotification' ) { $Param{Event} = 'SendCustomerNotification'; } elsif ( $Param{Event} eq 'ArticleAutoResponse' ) { $Param{Event} = 'SendAutoResponse'; } else { return 1; } return $Self->TicketEventHandlerPost( %Param, CompatOff => 1 ); } return 1; } 1; =back =head1 TERMS AND CONDITIONS This software is part of the OTRS project (http://otrs.org/). 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 http://www.gnu.org/licenses/gpl-2.0.txt. =head1 VERSION $Revision: 1.346.2.6 $ $Date: 2008/12/10 12:49:33 $ =cut