1# --
2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/
3# --
4# This software comes with ABSOLUTELY NO WARRANTY. For details, see
5# the enclosed file COPYING for license information (GPL). If you
6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
7# --
8
9package Kernel::System::Ticket;
10
11use strict;
12use warnings;
13
14use File::Path;
15use utf8;
16use Encode ();
17
18use parent qw(
19    Kernel::System::EventHandler
20    Kernel::System::Ticket::TicketSearch
21    Kernel::System::Ticket::TicketACL
22);
23
24use Kernel::Language qw(Translatable);
25use Kernel::System::VariableCheck qw(:all);
26
27our @ObjectDependencies = (
28    'Kernel::Config',
29    'Kernel::System::Cache',
30    'Kernel::System::Calendar',
31    'Kernel::System::CustomerUser',
32    'Kernel::System::DB',
33    'Kernel::System::DynamicField',
34    'Kernel::System::DynamicField::Backend',
35    'Kernel::System::DynamicFieldValue',
36    'Kernel::System::Email',
37    'Kernel::System::Group',
38    'Kernel::System::HTMLUtils',
39    'Kernel::System::LinkObject',
40    'Kernel::System::Lock',
41    'Kernel::System::Log',
42    'Kernel::System::Main',
43    'Kernel::System::PostMaster::LoopProtection',
44    'Kernel::System::Priority',
45    'Kernel::System::Queue',
46    'Kernel::System::Service',
47    'Kernel::System::SLA',
48    'Kernel::System::State',
49    'Kernel::System::TemplateGenerator',
50    'Kernel::System::DateTime',
51    'Kernel::System::Ticket::Article',
52    'Kernel::System::Type',
53    'Kernel::System::User',
54    'Kernel::System::Valid',
55    'Kernel::Language',
56);
57
58=head1 NAME
59
60Kernel::System::Ticket - Functions to create, modify and delete tickets as well as related helper functions
61
62=head1 SYNOPSIS
63
64Create ticket object
65
66    use Kernel::System::ObjectManager;
67    local $Kernel::OM = Kernel::System::ObjectManager->new();
68    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
69
70Create a new ticket
71
72    my $TicketID = $TicketObject->TicketCreate(
73        Title        => 'Some Ticket Title',
74        Queue        => 'Raw',
75        Lock         => 'unlock',
76        Priority     => '3 normal',
77        State        => 'new',
78        CustomerID   => '12345',
79        CustomerUser => 'customer@example.com',
80        OwnerID      => 1,
81        UserID       => 1,
82    );
83
84Lock the ticket
85
86    my $Success = $TicketObject->TicketLockSet(
87        Lock     => 'lock',
88        TicketID => $TicketID,
89        UserID   => 1,
90    );
91
92
93Update the title
94
95    my $Success = $TicketObject->TicketTitleUpdate(
96        Title    => 'Some Title',
97        TicketID => $TicketID,
98        UserID   => 1,
99    );
100
101Move ticket to another queue
102
103    my $Success = $TicketObject->TicketQueueSet(
104        Queue    => 'Some Queue Name',
105        TicketID => $TicketID,
106        UserID   => 1,
107    );
108
109Set a ticket type
110
111    my $Success = $TicketObject->TicketTypeSet(
112        Type     => 'Incident',
113        TicketID => $TicketID,
114        UserID   => 1,
115    );
116
117Assign another customer
118
119    my $Success = $TicketObject->TicketCustomerSet(
120        No       => '12345',
121        User     => 'customer@company.org',
122        TicketID => $TicketID,
123        UserID   => 1,
124    );
125
126Update the state
127
128    my $Success = $TicketObject->TicketStateSet(
129        State     => 'pending reminder',
130        TicketID => $TicketID,
131        UserID   => 1,
132    );
133
134Update pending time (only for pending states)
135
136    my $Success = $TicketObject->TicketPendingTimeSet(
137        String   => '2019-08-14 22:05:00',
138        TicketID => $TicketID,
139        UserID   => 1,
140    );
141
142Set a new priority
143
144    my $Success = $TicketObject->TicketPrioritySet(
145        TicketID => $TicketID,
146        Priority => 'low',
147        UserID   => 1,
148    );
149
150Assign to another agent
151
152    my $Success = $TicketObject->TicketOwnerSet(
153        TicketID  => $TicketID,
154        NewUserID => 2,
155        UserID    => 1,
156    );
157
158Set a responsible
159
160    my $Success = $TicketObject->TicketResponsibleSet(
161        TicketID  => $TicketID,
162        NewUserID => 3,
163        UserID    => 1,
164    );
165
166Add something to the history
167
168    my $Success = $TicketObject->HistoryAdd(
169        Name         => 'Some Comment',
170        HistoryType  => 'Move',
171        TicketID     => $TicketID,
172        CreateUserID => 1,
173    );
174
175Get the complete ticket history
176
177    my @HistoryLines = $TicketObject->HistoryGet(
178        TicketID => $TicketID,
179        UserID   => 1,
180    );
181
182Get current ticket attributes
183
184    my %Ticket = $TicketObject->TicketGet(
185        TicketID      => $TicketID,
186        UserID        => 1,
187    );
188
189Delete the ticket
190
191    my $Success = $TicketObject->TicketDelete(
192        TicketID => $TicketID,
193        UserID   => 1,
194    );
195
196=head1 PUBLIC INTERFACE
197
198=head2 new()
199
200Don't use the constructor directly, use the ObjectManager instead:
201
202    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
203
204=cut
205
206sub new {
207    my ( $Type, %Param ) = @_;
208
209    # allocate new hash for object
210    my $Self = {};
211    bless( $Self, $Type );
212
213    # 0=off; 1=on;
214    $Self->{Debug} = $Param{Debug} || 0;
215
216    $Self->{CacheType} = 'Ticket';
217    $Self->{CacheTTL}  = 60 * 60 * 24 * 20;
218
219    # init of event handler
220    $Self->EventHandlerInit(
221        Config => 'Ticket::EventModulePost',
222    );
223
224    # get needed objects
225    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
226    my $MainObject   = $Kernel::OM->Get('Kernel::System::Main');
227
228    # load ticket extension modules
229    my $CustomModule = $ConfigObject->Get('Ticket::CustomModule');
230    if ($CustomModule) {
231
232        my %ModuleList;
233        if ( ref $CustomModule eq 'HASH' ) {
234            %ModuleList = %{$CustomModule};
235        }
236        else {
237            $ModuleList{Init} = $CustomModule;
238        }
239
240        MODULEKEY:
241        for my $ModuleKey ( sort keys %ModuleList ) {
242
243            my $Module = $ModuleList{$ModuleKey};
244
245            next MODULEKEY if !$Module;
246            next MODULEKEY if !$MainObject->RequireBaseClass($Module);
247        }
248    }
249
250    return $Self;
251}
252
253=head2 TicketCreateNumber()
254
255creates a new ticket number
256
257    my $TicketNumber = $TicketObject->TicketCreateNumber();
258
259=cut
260
261sub TicketCreateNumber {
262    my ( $Self, %Param ) = @_;
263
264    my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::NumberGenerator')
265        || 'Kernel::System::Ticket::Number::AutoIncrement';
266
267    return $Kernel::OM->Get($GeneratorModule)->TicketCreateNumber(%Param);
268}
269
270=head2 GetTNByString()
271
272creates a new ticket number
273
274    my $TicketNumber = $TicketObject->GetTNByString($Subject);
275
276=cut
277
278sub GetTNByString {
279    my ( $Self, $String ) = @_;
280
281    my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::NumberGenerator')
282        || 'Kernel::System::Ticket::Number::AutoIncrement';
283
284    return $Kernel::OM->Get($GeneratorModule)->GetTNByString($String);
285}
286
287=head2 TicketCheckNumber()
288
289checks if ticket number exists, returns ticket id if number exists.
290
291returns the merged ticket id if ticket was merged.
292only into a depth of maximum 10 merges
293
294    my $TicketID = $TicketObject->TicketCheckNumber(
295        Tn => '200404051004575',
296    );
297
298=cut
299
300sub TicketCheckNumber {
301    my ( $Self, %Param ) = @_;
302
303    # check needed stuff
304    if ( !$Param{Tn} ) {
305        $Kernel::OM->Get('Kernel::System::Log')->Log(
306            Priority => 'error',
307            Message  => 'Need TN!'
308        );
309        return;
310    }
311
312    # get database object
313    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
314
315    # db query
316    return if !$DBObject->Prepare(
317        SQL   => 'SELECT id FROM ticket WHERE tn = ?',
318        Bind  => [ \$Param{Tn} ],
319        Limit => 1,
320    );
321
322    my $TicketID;
323    while ( my @Row = $DBObject->FetchrowArray() ) {
324        $TicketID = $Row[0];
325    }
326
327    # get main ticket id if ticket has been merged
328    return if !$TicketID;
329
330    # do not check deeper than 10 merges
331    my $Limit = 10;
332    my $Count = 1;
333    MERGELOOP:
334    for ( 1 .. $Limit ) {
335        my %Ticket = $Self->TicketGet(
336            TicketID      => $TicketID,
337            DynamicFields => 0,
338        );
339
340        return $TicketID if $Ticket{StateType} ne 'merged';
341
342        # get ticket history
343        my @Lines = $Self->HistoryGet(
344            TicketID => $TicketID,
345            UserID   => 1,
346        );
347
348        HISTORYLINE:
349        for my $Data ( reverse @Lines ) {
350            next HISTORYLINE if $Data->{HistoryType} ne 'Merged';
351            if ( $Data->{Name} =~ /^.*%%\d+?%%(\d+?)$/ ) {
352                $TicketID = $1;
353                $Count++;
354                next MERGELOOP if ( $Count <= $Limit );
355
356                # returns no found Ticket after 10 deep-merges, so it should create a new one
357                return;
358            }
359        }
360
361        return $TicketID;
362    }
363    return;
364}
365
366=head2 TicketCreate()
367
368creates a new ticket
369
370    my $TicketID = $TicketObject->TicketCreate(
371        Title        => 'Some Ticket Title',
372        Queue        => 'Raw',            # or QueueID => 123,
373        Lock         => 'unlock',
374        Priority     => '3 normal',       # or PriorityID => 2,
375        State        => 'new',            # or StateID => 5,
376        CustomerID   => '123465',
377        CustomerUser => 'customer@example.com',
378        OwnerID      => 123,
379        UserID       => 123,
380    );
381
382or
383
384    my $TicketID = $TicketObject->TicketCreate(
385        TN            => $TicketObject->TicketCreateNumber(), # optional
386        Title         => 'Some Ticket Title',
387        Queue         => 'Raw',              # or QueueID => 123,
388        Lock          => 'unlock',
389        Priority      => '3 normal',         # or PriorityID => 2,
390        State         => 'new',              # or StateID => 5,
391        Type          => 'Incident',         # or TypeID = 1 or Ticket type default (Ticket::Type::Default), not required
392        Service       => 'Service A',        # or ServiceID => 1, not required
393        SLA           => 'SLA A',            # or SLAID => 1, not required
394        CustomerID    => '123465',
395        CustomerUser  => 'customer@example.com',
396        OwnerID       => 123,
397        ResponsibleID => 123,                # not required
398        ArchiveFlag   => 'y',                # (y|n) not required
399        UserID        => 123,
400    );
401
402Events:
403    TicketCreate
404
405=cut
406
407sub TicketCreate {
408    my ( $Self, %Param ) = @_;
409
410    # check needed stuff
411    for my $Needed (qw(OwnerID UserID)) {
412        if ( !$Param{$Needed} ) {
413            $Kernel::OM->Get('Kernel::System::Log')->Log(
414                Priority => 'error',
415                Message  => "Need $Needed!"
416            );
417            return;
418        }
419    }
420
421    my $ArchiveFlag = 0;
422    if ( $Param{ArchiveFlag} && $Param{ArchiveFlag} eq 'y' ) {
423        $ArchiveFlag = 1;
424    }
425
426    $Param{ResponsibleID} ||= 1;
427
428    # get type object
429    my $TypeObject = $Kernel::OM->Get('Kernel::System::Type');
430
431    if ( !$Param{TypeID} && !$Param{Type} ) {
432
433        # get default ticket type
434        my $DefaultTicketType = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Type::Default');
435
436        # check if default ticket type exists
437        my %AllTicketTypes = reverse $TypeObject->TypeList();
438
439        if ( $AllTicketTypes{$DefaultTicketType} ) {
440            $Param{Type} = $DefaultTicketType;
441        }
442        else {
443            $Param{TypeID} = 1;
444        }
445    }
446
447    # TypeID/Type lookup!
448    if ( !$Param{TypeID} && $Param{Type} ) {
449        $Param{TypeID} = $TypeObject->TypeLookup( Type => $Param{Type} );
450    }
451    elsif ( $Param{TypeID} && !$Param{Type} ) {
452        $Param{Type} = $TypeObject->TypeLookup( TypeID => $Param{TypeID} );
453    }
454    if ( !$Param{TypeID} ) {
455        $Kernel::OM->Get('Kernel::System::Log')->Log(
456            Priority => 'error',
457            Message  => "No TypeID for '$Param{Type}'!",
458        );
459        return;
460    }
461
462    # get queue object
463    my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
464
465    # QueueID/Queue lookup!
466    if ( !$Param{QueueID} && $Param{Queue} ) {
467        $Param{QueueID} = $QueueObject->QueueLookup( Queue => $Param{Queue} );
468    }
469    elsif ( !$Param{Queue} ) {
470        $Param{Queue} = $QueueObject->QueueLookup( QueueID => $Param{QueueID} );
471    }
472    if ( !$Param{QueueID} ) {
473        $Kernel::OM->Get('Kernel::System::Log')->Log(
474            Priority => 'error',
475            Message  => "No QueueID for '$Param{Queue}'!",
476        );
477        return;
478    }
479
480    # get state object
481    my $StateObject = $Kernel::OM->Get('Kernel::System::State');
482
483    # StateID/State lookup!
484    if ( !$Param{StateID} ) {
485        my %State = $StateObject->StateGet( Name => $Param{State} );
486        $Param{StateID} = $State{ID};
487    }
488    elsif ( !$Param{State} ) {
489        my %State = $StateObject->StateGet( ID => $Param{StateID} );
490        $Param{State} = $State{Name};
491    }
492    if ( !$Param{StateID} ) {
493        $Kernel::OM->Get('Kernel::System::Log')->Log(
494            Priority => 'error',
495            Message  => "No StateID for '$Param{State}'!",
496        );
497        return;
498    }
499
500    # LockID lookup!
501    if ( !$Param{LockID} && $Param{Lock} ) {
502
503        $Param{LockID} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
504            Lock => $Param{Lock},
505        );
506    }
507    if ( !$Param{LockID} && !$Param{Lock} ) {
508
509        $Kernel::OM->Get('Kernel::System::Log')->Log(
510            Priority => 'error',
511            Message  => 'No LockID and no LockType!',
512        );
513        return;
514    }
515
516    # get priority object
517    my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority');
518
519    # PriorityID/Priority lookup!
520    if ( !$Param{PriorityID} && $Param{Priority} ) {
521        $Param{PriorityID} = $PriorityObject->PriorityLookup(
522            Priority => $Param{Priority},
523        );
524    }
525    elsif ( $Param{PriorityID} && !$Param{Priority} ) {
526        $Param{Priority} = $PriorityObject->PriorityLookup(
527            PriorityID => $Param{PriorityID},
528        );
529    }
530    if ( !$Param{PriorityID} ) {
531        $Kernel::OM->Get('Kernel::System::Log')->Log(
532            Priority => 'error',
533            Message  => 'No PriorityID (invalid Priority Name?)!',
534        );
535        return;
536    }
537
538    # get service object
539    my $ServiceObject = $Kernel::OM->Get('Kernel::System::Service');
540
541    # ServiceID/Service lookup!
542    if ( !$Param{ServiceID} && $Param{Service} ) {
543        $Param{ServiceID} = $ServiceObject->ServiceLookup(
544            Name => $Param{Service},
545        );
546    }
547    elsif ( $Param{ServiceID} && !$Param{Service} ) {
548        $Param{Service} = $ServiceObject->ServiceLookup(
549            ServiceID => $Param{ServiceID},
550        );
551    }
552
553    # get sla object
554    my $SLAObject = $Kernel::OM->Get('Kernel::System::SLA');
555
556    # SLAID/SLA lookup!
557    if ( !$Param{SLAID} && $Param{SLA} ) {
558        $Param{SLAID} = $SLAObject->SLALookup( Name => $Param{SLA} );
559    }
560    elsif ( $Param{SLAID} && !$Param{SLA} ) {
561        $Param{SLA} = $SLAObject->SLALookup( SLAID => $Param{SLAID} );
562    }
563
564    # create ticket number if none is given
565    if ( !$Param{TN} ) {
566        $Param{TN} = $Self->TicketCreateNumber();
567    }
568
569    # check ticket title
570    if ( !defined $Param{Title} ) {
571        $Param{Title} = '';
572    }
573
574    # substitute title if needed
575    else {
576        $Param{Title} = substr( $Param{Title}, 0, 255 );
577    }
578
579    # check database undef/NULL (set value to undef/NULL to prevent database errors)
580    $Param{ServiceID} ||= undef;
581    $Param{SLAID}     ||= undef;
582
583    # create db record
584    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
585        SQL => '
586            INSERT INTO ticket (tn, title, type_id, queue_id, ticket_lock_id,
587                user_id, responsible_user_id, ticket_priority_id, ticket_state_id,
588                escalation_time, escalation_update_time, escalation_response_time,
589                escalation_solution_time, timeout, service_id, sla_id, until_time,
590                archive_flag, create_time, create_by, change_time, change_by)
591            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, 0, ?, ?, 0, ?,
592                current_timestamp, ?, current_timestamp, ?)',
593        Bind => [
594            \$Param{TN}, \$Param{Title}, \$Param{TypeID}, \$Param{QueueID},
595            \$Param{LockID},     \$Param{OwnerID}, \$Param{ResponsibleID},
596            \$Param{PriorityID}, \$Param{StateID}, \$Param{ServiceID},
597            \$Param{SLAID}, \$ArchiveFlag, \$Param{UserID}, \$Param{UserID},
598        ],
599    );
600
601    # get ticket id
602    my $TicketID = $Self->TicketIDLookup(
603        TicketNumber => $Param{TN},
604        UserID       => $Param{UserID},
605    );
606
607    # add history entry
608    $Self->HistoryAdd(
609        TicketID     => $TicketID,
610        QueueID      => $Param{QueueID},
611        HistoryType  => 'NewTicket',
612        Name         => "\%\%$Param{TN}\%\%$Param{Queue}\%\%$Param{Priority}\%\%$Param{State}\%\%$TicketID",
613        CreateUserID => $Param{UserID},
614    );
615
616    if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service') ) {
617
618        # history insert for service so that initial values can be seen
619        my $HistoryService   = $Param{Service}   || 'NULL';
620        my $HistoryServiceID = $Param{ServiceID} || '';
621        $Self->HistoryAdd(
622            TicketID     => $TicketID,
623            HistoryType  => 'ServiceUpdate',
624            Name         => "\%\%$HistoryService\%\%$HistoryServiceID\%\%NULL\%\%",
625            CreateUserID => $Param{UserID},
626        );
627
628        # history insert for SLA
629        my $HistorySLA   = $Param{SLA}   || 'NULL';
630        my $HistorySLAID = $Param{SLAID} || '';
631        $Self->HistoryAdd(
632            TicketID     => $TicketID,
633            HistoryType  => 'SLAUpdate',
634            Name         => "\%\%$HistorySLA\%\%$HistorySLAID\%\%NULL\%\%",
635            CreateUserID => $Param{UserID},
636        );
637    }
638
639    if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Type') ) {
640
641        # Insert history record for ticket type, so that initial value can be seen.
642        #   Please see bug#12702 for more information.
643        $Self->HistoryAdd(
644            TicketID     => $TicketID,
645            HistoryType  => 'TypeUpdate',
646            Name         => "\%\%$Param{Type}\%\%$Param{TypeID}",
647            CreateUserID => $Param{UserID},
648        );
649    }
650
651    # set customer data if given
652    if ( $Param{CustomerNo} || $Param{CustomerID} || $Param{CustomerUser} ) {
653        $Self->TicketCustomerSet(
654            TicketID => $TicketID,
655            No       => $Param{CustomerNo} || $Param{CustomerID} || '',
656            User     => $Param{CustomerUser} || '',
657            UserID   => $Param{UserID},
658        );
659    }
660
661    # update ticket view index
662    $Self->TicketAcceleratorAdd( TicketID => $TicketID );
663
664    # log ticket creation
665    $Kernel::OM->Get('Kernel::System::Log')->Log(
666        Priority => 'info',
667        Message  => "New Ticket [$Param{TN}/" . substr( $Param{Title}, 0, 15 ) . "] created "
668            . "(TicketID=$TicketID,Queue=$Param{Queue},Priority=$Param{Priority},State=$Param{State})",
669    );
670
671    # trigger event
672    $Self->EventHandler(
673        Event => 'TicketCreate',
674        Data  => {
675            TicketID => $TicketID,
676        },
677        UserID => $Param{UserID},
678    );
679
680    return $TicketID;
681}
682
683=head2 TicketDelete()
684
685deletes a ticket with articles from storage
686
687    my $Success = $TicketObject->TicketDelete(
688        TicketID => 123,
689        UserID   => 123,
690    );
691
692Events:
693    TicketDelete
694
695=cut
696
697sub TicketDelete {
698    my ( $Self, %Param ) = @_;
699
700    # check needed stuff
701    for my $Needed (qw(TicketID UserID)) {
702        if ( !$Param{$Needed} ) {
703            $Kernel::OM->Get('Kernel::System::Log')->Log(
704                Priority => 'error',
705                Message  => "Need $Needed!",
706            );
707            return;
708        }
709    }
710
711    # Delete dynamic field values for this ticket.
712    $Kernel::OM->Get('Kernel::System::DynamicFieldValue')->ObjectValuesDelete(
713        ObjectType => 'Ticket',
714        ObjectID   => $Param{TicketID},
715        UserID     => $Param{UserID},
716    );
717
718    # clear ticket cache
719    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
720
721    # delete ticket links
722    $Kernel::OM->Get('Kernel::System::LinkObject')->LinkDeleteAll(
723        Object => 'Ticket',
724        Key    => $Param{TicketID},
725        UserID => $Param{UserID},
726    );
727
728    # update ticket index
729    return if !$Self->TicketAcceleratorDelete(%Param);
730
731    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
732
733    # delete ticket entries from article search index.
734    return if !$ArticleObject->ArticleSearchIndexDelete(
735        TicketID => $Param{TicketID},
736        UserID   => $Param{UserID},
737    );
738
739    # get database object
740    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
741
742    # remove ticket watcher
743    return if !$DBObject->Do(
744        SQL  => 'DELETE FROM ticket_watcher WHERE ticket_id = ?',
745        Bind => [ \$Param{TicketID} ],
746    );
747
748    # delete ticket flags
749    return if !$DBObject->Do(
750        SQL  => 'DELETE FROM ticket_flag WHERE ticket_id = ?',
751        Bind => [ \$Param{TicketID} ],
752    );
753
754    # delete ticket flag cache
755    $Kernel::OM->Get('Kernel::System::Cache')->Delete(
756        Type => $Self->{CacheType},
757        Key  => 'TicketFlag::' . $Param{TicketID},
758    );
759
760    # Delete calendar appointments linked to this ticket.
761    #   Please see bug#13642 for more information.
762    return if !$Kernel::OM->Get('Kernel::System::Calendar')->TicketAppointmentDelete(
763        TicketID => $Param{TicketID},
764    );
765
766    # delete ticket_history
767    return if !$Self->HistoryDelete(
768        TicketID => $Param{TicketID},
769        UserID   => $Param{UserID},
770    );
771
772    # Delete all articles and associated data.
773    my @Articles = $ArticleObject->ArticleList( TicketID => $Param{TicketID} );
774    for my $MetaArticle (@Articles) {
775        return if !$ArticleObject->BackendForArticle( %{$MetaArticle} )->ArticleDelete(
776            ArticleID => $MetaArticle->{ArticleID},
777            %Param,
778        );
779    }
780
781    # delete ticket
782    return if !$DBObject->Do(
783        SQL  => 'DELETE FROM ticket WHERE id = ?',
784        Bind => [ \$Param{TicketID} ],
785    );
786
787    # trigger event
788    $Self->EventHandler(
789        Event => 'TicketDelete',
790        Data  => {
791            TicketID => $Param{TicketID},
792        },
793        UserID => $Param{UserID},
794    );
795
796    # Clear ticket cache again, in case it was rebuilt in the meantime.
797    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
798
799    return 1;
800}
801
802=head2 TicketIDLookup()
803
804ticket id lookup by ticket number
805
806    my $TicketID = $TicketObject->TicketIDLookup(
807        TicketNumber => '2004040510440485',
808    );
809
810=cut
811
812sub TicketIDLookup {
813    my ( $Self, %Param ) = @_;
814
815    # check needed stuff
816    if ( !$Param{TicketNumber} ) {
817        $Kernel::OM->Get('Kernel::System::Log')->Log(
818            Priority => 'error',
819            Message  => 'Need TicketNumber!'
820        );
821        return;
822    }
823
824    # get database object
825    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
826
827    # db query
828    return if !$DBObject->Prepare(
829        SQL   => 'SELECT id FROM ticket WHERE tn = ?',
830        Bind  => [ \$Param{TicketNumber} ],
831        Limit => 1,
832    );
833
834    my $ID;
835    while ( my @Row = $DBObject->FetchrowArray() ) {
836        $ID = $Row[0];
837    }
838
839    return $ID;
840}
841
842=head2 TicketNumberLookup()
843
844ticket number lookup by ticket id
845
846    my $TicketNumber = $TicketObject->TicketNumberLookup(
847        TicketID => 123,
848    );
849
850=cut
851
852sub TicketNumberLookup {
853    my ( $Self, %Param ) = @_;
854
855    # check needed stuff
856    if ( !$Param{TicketID} ) {
857        $Kernel::OM->Get('Kernel::System::Log')->Log(
858            Priority => 'error',
859            Message  => 'Need TicketID!',
860        );
861        return;
862    }
863
864    # get database object
865    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
866
867    # db query
868    return if !$DBObject->Prepare(
869        SQL   => 'SELECT tn FROM ticket WHERE id = ?',
870        Bind  => [ \$Param{TicketID} ],
871        Limit => 1,
872    );
873
874    my $Number;
875    while ( my @Row = $DBObject->FetchrowArray() ) {
876        $Number = $Row[0];
877    }
878
879    return $Number;
880}
881
882=head2 TicketSubjectBuild()
883
884rebuild a new ticket subject
885
886This will generate a subject like C<RE: [Ticket# 2004040510440485] Some subject>
887
888    my $NewSubject = $TicketObject->TicketSubjectBuild(
889        TicketNumber => '2004040510440485',
890        Subject      => $OldSubject,
891        Action       => 'Reply',
892    );
893
894This will generate a subject like C<[Ticket# 2004040510440485] Some subject>
895(so without RE: )
896
897    my $NewSubject = $TicketObject->TicketSubjectBuild(
898        TicketNumber => '2004040510440485',
899        Subject      => $OldSubject,
900        Type         => 'New',
901        Action       => 'Reply',
902    );
903
904This will generate a subject like C<FWD: [Ticket# 2004040510440485] Some subject>
905
906    my $NewSubject = $TicketObject->TicketSubjectBuild(
907        TicketNumber => '2004040510440485',
908        Subject      => $OldSubject,
909        Action       => 'Forward', # Possible values are Reply and Forward, Reply is default.
910    );
911
912This will generate a subject like C<[Ticket# 2004040510440485] Re: Some subject>
913(so without clean-up of subject)
914
915    my $NewSubject = $TicketObject->TicketSubjectBuild(
916        TicketNumber => '2004040510440485',
917        Subject      => $OldSubject,
918        Type         => 'New',
919        NoCleanup    => 1,
920    );
921
922=cut
923
924sub TicketSubjectBuild {
925    my ( $Self, %Param ) = @_;
926
927    # check needed stuff
928    if ( !defined $Param{TicketNumber} ) {
929        $Kernel::OM->Get('Kernel::System::Log')->Log(
930            Priority => 'error',
931            Message  => "Need TicketNumber!"
932        );
933        return;
934    }
935    my $Subject = $Param{Subject} || '';
936    my $Action  = $Param{Action}  || 'Reply';
937
938    # cleanup of subject, remove existing ticket numbers and reply indentifier
939    if ( !$Param{NoCleanup} ) {
940        $Subject = $Self->TicketSubjectClean(%Param);
941    }
942
943    # get config object
944    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
945
946    # get config options
947    my $TicketHook          = $ConfigObject->Get('Ticket::Hook');
948    my $TicketHookDivider   = $ConfigObject->Get('Ticket::HookDivider');
949    my $TicketSubjectRe     = $ConfigObject->Get('Ticket::SubjectRe');
950    my $TicketSubjectFwd    = $ConfigObject->Get('Ticket::SubjectFwd');
951    my $TicketSubjectFormat = $ConfigObject->Get('Ticket::SubjectFormat') || 'Left';
952
953    # return subject for new tickets
954    if ( $Param{Type} && $Param{Type} eq 'New' ) {
955        if ( lc $TicketSubjectFormat eq 'right' ) {
956            return $Subject . " [$TicketHook$TicketHookDivider$Param{TicketNumber}]";
957        }
958        if ( lc $TicketSubjectFormat eq 'none' ) {
959            return $Subject;
960        }
961        return "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject;
962    }
963
964    # return subject for existing tickets
965    if ( $Action eq 'Forward' ) {
966        if ($TicketSubjectFwd) {
967            $TicketSubjectFwd .= ': ';
968        }
969        if ( lc $TicketSubjectFormat eq 'right' ) {
970            return $TicketSubjectFwd . $Subject
971                . " [$TicketHook$TicketHookDivider$Param{TicketNumber}]";
972        }
973        if ( lc $TicketSubjectFormat eq 'none' ) {
974            return $TicketSubjectFwd . $Subject;
975        }
976        return
977            $TicketSubjectFwd
978            . "[$TicketHook$TicketHookDivider$Param{TicketNumber}] "
979            . $Subject;
980    }
981    else {
982        if ($TicketSubjectRe) {
983            $TicketSubjectRe .= ': ';
984        }
985        if ( lc $TicketSubjectFormat eq 'right' ) {
986            return $TicketSubjectRe . $Subject
987                . " [$TicketHook$TicketHookDivider$Param{TicketNumber}]";
988        }
989        if ( lc $TicketSubjectFormat eq 'none' ) {
990            return $TicketSubjectRe . $Subject;
991        }
992        return $TicketSubjectRe . "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject;
993    }
994}
995
996=head2 TicketSubjectClean()
997
998strip/clean up a ticket subject
999
1000    my $NewSubject = $TicketObject->TicketSubjectClean(
1001        TicketNumber => '2004040510440485',
1002        Subject      => $OldSubject,
1003        Size         => $SubjectSizeToBeDisplayed   # optional, if 0 do not cut subject
1004    );
1005
1006=cut
1007
1008sub TicketSubjectClean {
1009    my ( $Self, %Param ) = @_;
1010
1011    # check needed stuff
1012    if ( !defined $Param{TicketNumber} ) {
1013        $Kernel::OM->Get('Kernel::System::Log')->Log(
1014            Priority => 'error',
1015            Message  => "Need TicketNumber!"
1016        );
1017        return;
1018    }
1019
1020    my $Subject = $Param{Subject} || '';
1021
1022    # get config object
1023    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1024
1025    # get config options
1026    my $TicketHook        = $ConfigObject->Get('Ticket::Hook');
1027    my $TicketHookDivider = $ConfigObject->Get('Ticket::HookDivider');
1028    my $TicketSubjectSize = $Param{Size};
1029    if ( !defined $TicketSubjectSize ) {
1030        $TicketSubjectSize = $ConfigObject->Get('Ticket::SubjectSize')
1031            || 120;
1032    }
1033    my $TicketSubjectRe  = $ConfigObject->Get('Ticket::SubjectRe');
1034    my $TicketSubjectFwd = $ConfigObject->Get('Ticket::SubjectFwd');
1035
1036    # remove all possible ticket hook formats with []
1037    $Subject =~ s/\[\s*\Q$TicketHook: $Param{TicketNumber}\E\s*\]\s*//g;
1038    $Subject =~ s/\[\s*\Q$TicketHook:$Param{TicketNumber}\E\s*\]\s*//g;
1039    $Subject =~ s/\[\s*\Q$TicketHook$TicketHookDivider$Param{TicketNumber}\E\s*\]\s*//g;
1040
1041    # remove all ticket numbers with []
1042    if ( $ConfigObject->Get('Ticket::SubjectCleanAllNumbers') ) {
1043        $Subject =~ s/\[\s*\Q$TicketHook$TicketHookDivider\E\d+?\s*\]\s*//g;
1044    }
1045
1046    # remove all possible ticket hook formats without []
1047    $Subject =~ s/\Q$TicketHook: $Param{TicketNumber}\E\s*//g;
1048    $Subject =~ s/\Q$TicketHook:$Param{TicketNumber}\E\s*//g;
1049    $Subject =~ s/\Q$TicketHook$TicketHookDivider$Param{TicketNumber}\E\s*//g;
1050
1051    # remove all ticket numbers without []
1052    if ( $ConfigObject->Get('Ticket::SubjectCleanAllNumbers') ) {
1053        $Subject =~ s/\Q$TicketHook$TicketHookDivider\E\d+?\s*//g;
1054    }
1055
1056    # remove leading number with configured "RE:\s" or "RE[\d+]:\s" e. g. "RE: " or "RE[4]: "
1057    $Subject =~ s/^($TicketSubjectRe(\[\d+\])?:\s)+//i;
1058
1059    # remove leading number with configured "Fwd:\s" or "Fwd[\d+]:\s" e. g. "Fwd: " or "Fwd[4]: "
1060    $Subject =~ s/^($TicketSubjectFwd(\[\d+\])?:\s)+//i;
1061
1062    # trim white space at the beginning or end
1063    $Subject =~ s/(^\s+|\s+$)//;
1064
1065    # resize subject based on config
1066    # do not cut subject, if size parameter was 0
1067    if ($TicketSubjectSize) {
1068        $Subject =~ s/^(.{$TicketSubjectSize}).*$/$1 [...]/;
1069    }
1070
1071    return $Subject;
1072}
1073
1074=head2 TicketGet()
1075
1076Get ticket info
1077
1078    my %Ticket = $TicketObject->TicketGet(
1079        TicketID      => 123,
1080        DynamicFields => 0,         # Optional, default 0. To include the dynamic field values for this ticket on the return structure.
1081        UserID        => 123,
1082        Silent        => 0,         # Optional, default 0. To suppress the warning if the ticket does not exist.
1083    );
1084
1085Returns:
1086
1087    %Ticket = (
1088        TicketNumber       => '20101027000001',
1089        Title              => 'some title',
1090        TicketID           => 123,
1091        State              => 'some state',
1092        StateID            => 123,
1093        StateType          => 'some state type',
1094        Priority           => 'some priority',
1095        PriorityID         => 123,
1096        Lock               => 'lock',
1097        LockID             => 123,
1098        Queue              => 'some queue',
1099        QueueID            => 123,
1100        CustomerID         => 'customer_id_123',
1101        CustomerUserID     => 'customer_user_id_123',
1102        Owner              => 'some_owner_login',
1103        OwnerID            => 123,
1104        Type               => 'some ticket type',
1105        TypeID             => 123,
1106        SLA                => 'some sla',
1107        SLAID              => 123,
1108        Service            => 'some service',
1109        ServiceID          => 123,
1110        Responsible        => 'some_responsible_login',
1111        ResponsibleID      => 123,
1112        Age                => 3456,
1113        Created            => '2010-10-27 20:15:00'
1114        CreateBy           => 123,
1115        Changed            => '2010-10-27 20:15:15',
1116        ChangeBy           => 123,
1117        ArchiveFlag        => 'y',
1118
1119        # If DynamicFields => 1 was passed, you'll get an entry like this for each dynamic field:
1120        DynamicField_X     => 'value_x',
1121
1122        # (time stamps of expected escalations)
1123        EscalationResponseTime           (unix time stamp of response time escalation)
1124        EscalationUpdateTime             (unix time stamp of update time escalation)
1125        EscalationSolutionTime           (unix time stamp of solution time escalation)
1126
1127        # (general escalation info of nearest escalation type)
1128        EscalationDestinationIn          (escalation in e. g. 1h 4m)
1129        EscalationDestinationTime        (date of escalation in unix time, e. g. 72193292)
1130        EscalationDestinationDate        (date of escalation, e. g. "2009-02-14 18:00:00")
1131        EscalationTimeWorkingTime        (seconds of working/service time till escalation, e. g. "1800")
1132        EscalationTime                   (seconds total till escalation of nearest escalation time type - response, update or solution time, e. g. "3600")
1133
1134        # (detailed escalation info about first response, update and solution time)
1135        FirstResponseTimeEscalation      (if true, ticket is escalated)
1136        FirstResponseTimeNotification    (if true, notify - x% of escalation has reached)
1137        FirstResponseTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
1138        FirstResponseTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
1139        FirstResponseTimeWorkingTime     (seconds of working/service time till escalation, e. g. "1800")
1140        FirstResponseTime                (seconds total till escalation, e. g. "3600")
1141
1142        UpdateTimeEscalation             (if true, ticket is escalated)
1143        UpdateTimeNotification           (if true, notify - x% of escalation has reached)
1144        UpdateTimeDestinationTime        (date of escalation in unix time, e. g. 72193292)
1145        UpdateTimeDestinationDate        (date of escalation, e. g. "2009-02-14 18:00:00")
1146        UpdateTimeWorkingTime            (seconds of working/service time till escalation, e. g. "1800")
1147        UpdateTime                       (seconds total till escalation, e. g. "3600")
1148
1149        SolutionTimeEscalation           (if true, ticket is escalated)
1150        SolutionTimeNotification         (if true, notify - x% of escalation has reached)
1151        SolutionTimeDestinationTime      (date of escalation in unix time, e. g. 72193292)
1152        SolutionTimeDestinationDate      (date of escalation, e. g. "2009-02-14 18:00:00")
1153        SolutionTimeWorkingTime          (seconds of working/service time till escalation, e. g. "1800")
1154        SolutionTime                     (seconds total till escalation, e. g. "3600")
1155    );
1156
1157To get extended ticket attributes, use C<Extended>
1158
1159    my %Ticket = $TicketObject->TicketGet(
1160        TicketID => 123,
1161        UserID   => 123,
1162        Extended => 1,
1163    );
1164
1165Additional parameters are:
1166
1167    %Ticket = (
1168        FirstResponse                   (timestamp of first response, first contact with customer)
1169        FirstResponseInMin              (minutes till first response)
1170        FirstResponseDiffInMin          (minutes till or over first response)
1171
1172        SolutionInMin                   (minutes till solution time)
1173        SolutionDiffInMin               (minutes till or over solution time)
1174
1175        FirstLock                       (timestamp of first lock)
1176    );
1177
1178=cut
1179
1180sub TicketGet {
1181    my ( $Self, %Param ) = @_;
1182
1183    # check needed stuff
1184    if ( !$Param{TicketID} ) {
1185        $Kernel::OM->Get('Kernel::System::Log')->Log(
1186            Priority => 'error',
1187            Message  => 'Need TicketID!'
1188        );
1189        return;
1190    }
1191    $Param{Extended} = $Param{Extended} ? 1 : 0;
1192
1193    # Caching TicketGet() is a bit more complex than usual.
1194    #   The full function result will be cached in an in-memory cache to
1195    #       speed up subsequent operations in one request, but not on disk,
1196    #       because there are dependencies to other objects such as queue which cannot
1197    #       easily be tracked.
1198    #   The SQL for fetching ticket data will be cached on disk as well because this cache
1199    #       can easily be invalidated on ticket changes.
1200
1201    # check cache
1202    my $FetchDynamicFields = $Param{DynamicFields} ? 1 : 0;
1203
1204    my $CacheKey = 'Cache::GetTicket' . $Param{TicketID};
1205    my $CacheKeyDynamicFields
1206        = 'Cache::GetTicket' . $Param{TicketID} . '::' . $Param{Extended} . '::' . $FetchDynamicFields;
1207
1208    my $CachedDynamicFields = $Kernel::OM->Get('Kernel::System::Cache')->Get(
1209        Type           => $Self->{CacheType},
1210        Key            => $CacheKeyDynamicFields,
1211        CacheInMemory  => 1,
1212        CacheInBackend => 0,
1213    );
1214
1215    # check if result is cached
1216    if ( ref $CachedDynamicFields eq 'HASH' ) {
1217        return %{$CachedDynamicFields};
1218    }
1219
1220    my %Ticket;
1221
1222    my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get(
1223        Type => $Self->{CacheType},
1224        Key  => $CacheKey,
1225    );
1226
1227    if ( ref $Cached eq 'HASH' ) {
1228        %Ticket = %{$Cached};
1229    }
1230    else {
1231
1232        # get database object
1233        my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
1234
1235        return if !$DBObject->Prepare(
1236            SQL => '
1237                SELECT st.id, st.queue_id, st.ticket_state_id, st.ticket_lock_id, st.ticket_priority_id,
1238                    st.create_time, st.create_time, st.tn, st.customer_id, st.customer_user_id,
1239                    st.user_id, st.responsible_user_id, st.until_time, st.change_time, st.title,
1240                    st.escalation_update_time, st.timeout, st.type_id, st.service_id, st.sla_id,
1241                    st.escalation_response_time, st.escalation_solution_time, st.escalation_time, st.archive_flag,
1242                    st.create_by, st.change_by
1243                FROM ticket st
1244                WHERE st.id = ?',
1245            Bind  => [ \$Param{TicketID} ],
1246            Limit => 1,
1247        );
1248
1249        while ( my @Row = $DBObject->FetchrowArray() ) {
1250            $Ticket{TicketID}   = $Row[0];
1251            $Ticket{QueueID}    = $Row[1];
1252            $Ticket{StateID}    = $Row[2];
1253            $Ticket{LockID}     = $Row[3];
1254            $Ticket{PriorityID} = $Row[4];
1255
1256            $Ticket{Created}        = $Row[5];
1257            $Ticket{TicketNumber}   = $Row[7];
1258            $Ticket{CustomerID}     = $Row[8];
1259            $Ticket{CustomerUserID} = $Row[9];
1260
1261            $Ticket{OwnerID}             = $Row[10];
1262            $Ticket{ResponsibleID}       = $Row[11] || 1;
1263            $Ticket{RealTillTimeNotUsed} = $Row[12];
1264            $Ticket{Changed}             = $Row[13];
1265            $Ticket{Title}               = $Row[14];
1266
1267            $Ticket{EscalationUpdateTime} = $Row[15];
1268            $Ticket{UnlockTimeout}        = $Row[16];
1269            $Ticket{TypeID}               = $Row[17] || 1;
1270            $Ticket{ServiceID}            = $Row[18] || '';
1271            $Ticket{SLAID}                = $Row[19] || '';
1272
1273            $Ticket{EscalationResponseTime} = $Row[20];
1274            $Ticket{EscalationSolutionTime} = $Row[21];
1275            $Ticket{EscalationTime}         = $Row[22];
1276            $Ticket{ArchiveFlag}            = $Row[23] ? 'y' : 'n';
1277
1278            $Ticket{CreateBy} = $Row[24];
1279            $Ticket{ChangeBy} = $Row[25];
1280        }
1281
1282        # use cache only when a ticket number is found otherwise a non-existant ticket
1283        # is cached. That can cause errors when the cache isn't expired and postmaster
1284        # creates that ticket
1285        if ( $Ticket{TicketID} ) {
1286            $Kernel::OM->Get('Kernel::System::Cache')->Set(
1287                Type => $Self->{CacheType},
1288                TTL  => $Self->{CacheTTL},
1289                Key  => $CacheKey,
1290
1291                # make a local copy of the ticket data to avoid it being altered in-memory later
1292                Value => {%Ticket},
1293            );
1294        }
1295    }
1296
1297    # check ticket
1298    if ( !$Ticket{TicketID} ) {
1299        if ( !$Param{Silent} ) {
1300            $Kernel::OM->Get('Kernel::System::Log')->Log(
1301                Priority => 'error',
1302                Message  => "No such TicketID ($Param{TicketID})!",
1303            );
1304        }
1305        return;
1306    }
1307
1308    # check if need to return DynamicFields
1309    if ($FetchDynamicFields) {
1310
1311        # get dynamic field objects
1312        my $DynamicFieldObject        = $Kernel::OM->Get('Kernel::System::DynamicField');
1313        my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
1314
1315        # get all dynamic fields for the object type Ticket
1316        my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet(
1317            ObjectType => 'Ticket'
1318        );
1319
1320        DYNAMICFIELD:
1321        for my $DynamicFieldConfig ( @{$DynamicFieldList} ) {
1322
1323            # validate each dynamic field
1324            next DYNAMICFIELD if !$DynamicFieldConfig;
1325            next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1326            next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
1327
1328            # get the current value for each dynamic field
1329            my $Value = $DynamicFieldBackendObject->ValueGet(
1330                DynamicFieldConfig => $DynamicFieldConfig,
1331                ObjectID           => $Ticket{TicketID},
1332            );
1333
1334            # set the dynamic field name and value into the ticket hash
1335            $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $Value;
1336        }
1337    }
1338
1339    my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet(
1340        ID => $Ticket{QueueID},
1341    );
1342
1343    $Ticket{Queue}   = $Queue{Name};
1344    $Ticket{GroupID} = $Queue{GroupID};
1345
1346    # fillup runtime values
1347    my $TicketCreatedDTObj = $Kernel::OM->Create(
1348        'Kernel::System::DateTime',
1349        ObjectParams => {
1350            String => $Ticket{Created}
1351        },
1352    );
1353
1354    my $Delta = $TicketCreatedDTObj->Delta( DateTimeObject => $Kernel::OM->Create('Kernel::System::DateTime') );
1355    $Ticket{Age} = $Delta->{AbsoluteSeconds};
1356
1357    $Ticket{Priority} = $Kernel::OM->Get('Kernel::System::Priority')->PriorityLookup(
1358        PriorityID => $Ticket{PriorityID},
1359    );
1360
1361    # get user object
1362    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
1363
1364    # get owner
1365    $Ticket{Owner} = $UserObject->UserLookup(
1366        UserID => $Ticket{OwnerID},
1367    );
1368
1369    # get responsible
1370    $Ticket{Responsible} = $UserObject->UserLookup(
1371        UserID => $Ticket{ResponsibleID},
1372    );
1373
1374    # get lock
1375    $Ticket{Lock} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
1376        LockID => $Ticket{LockID},
1377    );
1378
1379    # get type
1380    $Ticket{Type} = $Kernel::OM->Get('Kernel::System::Type')->TypeLookup( TypeID => $Ticket{TypeID} );
1381
1382    # get service
1383    if ( $Ticket{ServiceID} ) {
1384
1385        $Ticket{Service} = $Kernel::OM->Get('Kernel::System::Service')->ServiceLookup(
1386            ServiceID => $Ticket{ServiceID},
1387        );
1388    }
1389
1390    # get sla
1391    if ( $Ticket{SLAID} ) {
1392        $Ticket{SLA} = $Kernel::OM->Get('Kernel::System::SLA')->SLALookup(
1393            SLAID => $Ticket{SLAID},
1394        );
1395    }
1396
1397    # get state info
1398    my %StateData = $Kernel::OM->Get('Kernel::System::State')->StateGet(
1399        ID => $Ticket{StateID}
1400    );
1401
1402    $Ticket{StateType} = $StateData{TypeName};
1403    $Ticket{State}     = $StateData{Name};
1404
1405    if ( !$Ticket{RealTillTimeNotUsed} || lc $StateData{TypeName} eq 'pending' ) {
1406        $Ticket{UntilTime} = 0;
1407    }
1408    else {
1409        $Ticket{UntilTime} = $Ticket{RealTillTimeNotUsed} - $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch();
1410    }
1411
1412    # get escalation attributes
1413    my %Escalation = $Self->TicketEscalationDateCalculation(
1414        Ticket => \%Ticket,
1415        UserID => $Param{UserID} || 1,
1416    );
1417
1418    for my $Key ( sort keys %Escalation ) {
1419        $Ticket{$Key} = $Escalation{$Key};
1420    }
1421
1422    # do extended lookups
1423    if ( $Param{Extended} ) {
1424        my %TicketExtended = $Self->_TicketGetExtended(
1425            TicketID => $Param{TicketID},
1426            Ticket   => \%Ticket,
1427        );
1428        for my $Key ( sort keys %TicketExtended ) {
1429            $Ticket{$Key} = $TicketExtended{$Key};
1430        }
1431    }
1432
1433    # cache user result
1434    $Kernel::OM->Get('Kernel::System::Cache')->Set(
1435        Type => $Self->{CacheType},
1436        TTL  => $Self->{CacheTTL},
1437        Key  => $CacheKeyDynamicFields,
1438
1439        # make a local copy of the ticket data to avoid it being altered in-memory later
1440        Value          => {%Ticket},
1441        CacheInMemory  => 1,
1442        CacheInBackend => 0,
1443    );
1444
1445    return %Ticket;
1446}
1447
1448=head2 TicketTitleUpdate()
1449
1450update ticket title
1451
1452    my $Success = $TicketObject->TicketTitleUpdate(
1453        Title    => 'Some Title',
1454        TicketID => 123,
1455        UserID   => 1,
1456    );
1457
1458Events:
1459    TicketTitleUpdate
1460
1461=cut
1462
1463sub TicketTitleUpdate {
1464    my ( $Self, %Param ) = @_;
1465
1466    # check needed stuff
1467    for my $Needed (qw(Title TicketID UserID)) {
1468        if ( !defined $Param{$Needed} ) {
1469            $Kernel::OM->Get('Kernel::System::Log')->Log(
1470                Priority => 'error',
1471                Message  => "Need $Needed!"
1472            );
1473            return;
1474        }
1475    }
1476
1477    # check if update is needed
1478    my %Ticket = $Self->TicketGet(
1479        TicketID      => $Param{TicketID},
1480        UserID        => $Param{UserID},
1481        DynamicFields => 0,
1482    );
1483
1484    return 1 if defined $Ticket{Title} && $Ticket{Title} eq $Param{Title};
1485
1486    # db access
1487    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
1488        SQL => 'UPDATE ticket SET title = ?, change_time = current_timestamp, '
1489            . ' change_by = ? WHERE id = ?',
1490        Bind => [ \$Param{Title}, \$Param{UserID}, \$Param{TicketID} ],
1491    );
1492
1493    # clear ticket cache
1494    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
1495
1496    # truncate title
1497    my $Title = substr( $Param{Title}, 0, 50 );
1498    $Title .= '...' if length($Title) == 50;
1499
1500    # history insert
1501    $Self->HistoryAdd(
1502        TicketID     => $Param{TicketID},
1503        HistoryType  => 'TitleUpdate',
1504        Name         => "\%\%$Ticket{Title}\%\%$Title",
1505        CreateUserID => $Param{UserID},
1506    );
1507
1508    # trigger event
1509    $Self->EventHandler(
1510        Event => 'TicketTitleUpdate',
1511        Data  => {
1512            TicketID => $Param{TicketID},
1513        },
1514        UserID => $Param{UserID},
1515    );
1516
1517    return 1;
1518}
1519
1520=head2 TicketUnlockTimeoutUpdate()
1521
1522set the ticket unlock time to the passed time
1523
1524    my $Success = $TicketObject->TicketUnlockTimeoutUpdate(
1525        UnlockTimeout => $Epoch,
1526        TicketID      => 123,
1527        UserID        => 143,
1528    );
1529
1530Events:
1531    TicketUnlockTimeoutUpdate
1532
1533=cut
1534
1535sub TicketUnlockTimeoutUpdate {
1536    my ( $Self, %Param ) = @_;
1537
1538    # check needed stuff
1539    for my $Needed (qw(UnlockTimeout TicketID UserID)) {
1540        if ( !defined $Param{$Needed} ) {
1541            $Kernel::OM->Get('Kernel::System::Log')->Log(
1542                Priority => 'error',
1543                Message  => "Need $Needed!"
1544            );
1545            return;
1546        }
1547    }
1548
1549    # check if update is needed
1550    my %Ticket = $Self->TicketGet(
1551        %Param,
1552        DynamicFields => 0,
1553    );
1554
1555    return 1 if $Ticket{UnlockTimeout} eq $Param{UnlockTimeout};
1556
1557    # reset unlock time
1558    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
1559        SQL => 'UPDATE ticket SET timeout = ?, change_time = current_timestamp, '
1560            . ' change_by = ? WHERE id = ?',
1561        Bind => [ \$Param{UnlockTimeout}, \$Param{UserID}, \$Param{TicketID} ],
1562    );
1563
1564    # clear ticket cache
1565    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
1566
1567    # add history
1568    $Self->HistoryAdd(
1569        TicketID     => $Param{TicketID},
1570        CreateUserID => $Param{UserID},
1571        HistoryType  => 'Misc',
1572        Name         => Translatable('Reset of unlock time.'),
1573    );
1574
1575    # trigger event
1576    $Self->EventHandler(
1577        Event => 'TicketUnlockTimeoutUpdate',
1578        Data  => {
1579            TicketID => $Param{TicketID},
1580        },
1581        UserID => $Param{UserID},
1582    );
1583
1584    return 1;
1585}
1586
1587=head2 TicketQueueID()
1588
1589get ticket queue id
1590
1591    my $QueueID = $TicketObject->TicketQueueID(
1592        TicketID => 123,
1593    );
1594
1595=cut
1596
1597sub TicketQueueID {
1598    my ( $Self, %Param ) = @_;
1599
1600    # check needed stuff
1601    if ( !$Param{TicketID} ) {
1602        $Kernel::OM->Get('Kernel::System::Log')->Log(
1603            Priority => 'error',
1604            Message  => 'Need TicketID!'
1605        );
1606        return;
1607    }
1608
1609    # get ticket data
1610    my %Ticket = $Self->TicketGet(
1611        TicketID      => $Param{TicketID},
1612        DynamicFields => 0,
1613        UserID        => 1,
1614        Silent        => 1,
1615    );
1616
1617    return if !%Ticket;
1618
1619    return $Ticket{QueueID};
1620}
1621
1622=head2 TicketMoveList()
1623
1624to get the move queue list for a ticket (depends on workflow, if configured)
1625
1626    my %Queues = $TicketObject->TicketMoveList(
1627        Type   => 'create',
1628        UserID => 123,
1629    );
1630
1631    my %Queues = $TicketObject->TicketMoveList(
1632        Type           => 'create',
1633        CustomerUserID => 'customer_user_id_123',
1634    );
1635
1636
1637    my %Queues = $TicketObject->TicketMoveList(
1638        QueueID => 123,
1639        UserID  => 123,
1640    );
1641
1642    my %Queues = $TicketObject->TicketMoveList(
1643        TicketID => 123,
1644        UserID   => 123,
1645    );
1646
1647=cut
1648
1649sub TicketMoveList {
1650    my ( $Self, %Param ) = @_;
1651
1652    # check needed stuff
1653    if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
1654        $Kernel::OM->Get('Kernel::System::Log')->Log(
1655            Priority => 'error',
1656            Message  => 'Need UserID or CustomerUserID!',
1657        );
1658        return;
1659    }
1660
1661    # check needed stuff
1662    if ( !$Param{QueueID} && !$Param{TicketID} && !$Param{Type} ) {
1663        $Kernel::OM->Get('Kernel::System::Log')->Log(
1664            Priority => 'error',
1665            Message  => 'Need QueueID, TicketID or Type!',
1666        );
1667        return;
1668    }
1669
1670    # get queue object
1671    my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
1672
1673    my %Queues;
1674    if ( $Param{UserID} && $Param{UserID} eq 1 ) {
1675        %Queues = $QueueObject->GetAllQueues();
1676    }
1677    else {
1678        %Queues = $QueueObject->GetAllQueues(%Param);
1679    }
1680
1681    # workflow
1682    my $ACL = $Self->TicketAcl(
1683        %Param,
1684        ReturnType    => 'Ticket',
1685        ReturnSubType => 'Queue',
1686        Data          => \%Queues,
1687    );
1688    return $Self->TicketAclData() if $ACL;
1689    return %Queues;
1690}
1691
1692=head2 TicketQueueSet()
1693
1694to move a ticket (sends notification to agents of selected my queues, if ticket is not closed)
1695
1696    my $Success = $TicketObject->TicketQueueSet(
1697        QueueID  => 123,
1698        TicketID => 123,
1699        UserID   => 123,
1700    );
1701
1702    my $Success = $TicketObject->TicketQueueSet(
1703        Queue    => 'Some Queue Name',
1704        TicketID => 123,
1705        UserID   => 123,
1706    );
1707
1708    my $Success = $TicketObject->TicketQueueSet(
1709        Queue    => 'Some Queue Name',
1710        TicketID => 123,
1711        Comment  => 'some comment', # optional
1712        ForceNotificationToUserID => [1,43,56], # if you want to force somebody
1713        UserID   => 123,
1714    );
1715
1716Optional attribute:
1717SendNoNotification disables or enables agent and customer notification for this
1718action.
1719
1720For example:
1721
1722        SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
1723
1724Events:
1725    TicketQueueUpdate
1726
1727=cut
1728
1729sub TicketQueueSet {
1730    my ( $Self, %Param ) = @_;
1731
1732    # get queue object
1733    my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
1734
1735    # queue lookup
1736    if ( $Param{Queue} && !$Param{QueueID} ) {
1737        $Param{QueueID} = $QueueObject->QueueLookup( Queue => $Param{Queue} );
1738    }
1739
1740    # check needed stuff
1741    for my $Needed (qw(TicketID QueueID UserID)) {
1742        if ( !$Param{$Needed} ) {
1743            $Kernel::OM->Get('Kernel::System::Log')->Log(
1744                Priority => 'error',
1745                Message  => "Need $Needed!"
1746            );
1747            return;
1748        }
1749    }
1750
1751    # get current ticket
1752    my %Ticket = $Self->TicketGet(
1753        %Param,
1754        DynamicFields => 0,
1755    );
1756
1757    # move needed?
1758    if ( $Param{QueueID} == $Ticket{QueueID} && !$Param{Comment} ) {
1759
1760        # update not needed
1761        return 1;
1762    }
1763
1764    # permission check
1765    my %MoveList = $Self->MoveList( %Param, Type => 'move_into' );
1766    if ( !$MoveList{ $Param{QueueID} } ) {
1767        $Kernel::OM->Get('Kernel::System::Log')->Log(
1768            Priority => 'notice',
1769            Message  => "Permission denied on TicketID: $Param{TicketID}!",
1770        );
1771        return;
1772    }
1773
1774    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
1775        SQL => 'UPDATE ticket SET queue_id = ?, change_time = current_timestamp, '
1776            . ' change_by = ? WHERE id = ?',
1777        Bind => [ \$Param{QueueID}, \$Param{UserID}, \$Param{TicketID} ],
1778    );
1779
1780    # queue lookup
1781    my $Queue = $QueueObject->QueueLookup( QueueID => $Param{QueueID} );
1782
1783    # clear ticket cache
1784    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
1785
1786    # history insert
1787    $Self->HistoryAdd(
1788        TicketID     => $Param{TicketID},
1789        QueueID      => $Param{QueueID},
1790        HistoryType  => 'Move',
1791        Name         => "\%\%$Queue\%\%$Param{QueueID}\%\%$Ticket{Queue}\%\%$Ticket{QueueID}",
1792        CreateUserID => $Param{UserID},
1793    );
1794
1795    # send move notify to queue subscriber
1796    if ( !$Param{SendNoNotification} && $Ticket{StateType} ne 'closed' ) {
1797
1798        my @UserIDs;
1799
1800        if ( $Param{ForceNotificationToUserID} ) {
1801            push @UserIDs, @{ $Param{ForceNotificationToUserID} };
1802        }
1803
1804        # trigger notification event
1805        $Self->EventHandler(
1806            Event => 'NotificationMove',
1807            Data  => {
1808                TicketID              => $Param{TicketID},
1809                CustomerMessageParams => {
1810                    Queue => $Queue,
1811                },
1812                Recipients => \@UserIDs,
1813            },
1814            UserID => $Param{UserID},
1815        );
1816    }
1817
1818    # trigger event, OldTicketData is needed for escalation events
1819    $Self->EventHandler(
1820        Event => 'TicketQueueUpdate',
1821        Data  => {
1822            TicketID      => $Param{TicketID},
1823            OldTicketData => \%Ticket,
1824        },
1825        UserID => $Param{UserID},
1826    );
1827
1828    return 1;
1829}
1830
1831=head2 TicketMoveQueueList()
1832
1833returns a list of used queue ids / names
1834
1835    my @QueueIDList = $TicketObject->TicketMoveQueueList(
1836        TicketID => 123,
1837        Type     => 'ID',
1838    );
1839
1840Returns:
1841
1842    @QueueIDList = ( 1, 2, 3 );
1843
1844    my @QueueList = $TicketObject->TicketMoveQueueList(
1845        TicketID => 123,
1846        Type     => 'Name',
1847    );
1848
1849Returns:
1850
1851    @QueueList = ( 'QueueA', 'QueueB', 'QueueC' );
1852
1853=cut
1854
1855sub TicketMoveQueueList {
1856    my ( $Self, %Param ) = @_;
1857
1858    # check needed stuff
1859    if ( !$Param{TicketID} ) {
1860        $Kernel::OM->Get('Kernel::System::Log')->Log(
1861            Priority => 'error',
1862            Message  => "Need TicketID!"
1863        );
1864        return;
1865    }
1866
1867    # get database object
1868    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
1869
1870    # db query
1871    return if !$DBObject->Prepare(
1872        SQL => 'SELECT sh.name, ht.name, sh.create_by, sh.queue_id FROM '
1873            . 'ticket_history sh, ticket_history_type ht WHERE '
1874            . 'sh.ticket_id = ? AND ht.name IN (\'Move\', \'NewTicket\') AND '
1875            . 'ht.id = sh.history_type_id ORDER BY sh.id',
1876        Bind => [ \$Param{TicketID} ],
1877    );
1878
1879    my @QueueID;
1880    while ( my @Row = $DBObject->FetchrowArray() ) {
1881
1882        # store result
1883        if ( $Row[1] eq 'NewTicket' ) {
1884            if ( $Row[3] ) {
1885                push @QueueID, $Row[3];
1886            }
1887        }
1888        elsif ( $Row[1] eq 'Move' ) {
1889            if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) {
1890                push @QueueID, $2;
1891            }
1892            elsif ( $Row[0] =~ /^Ticket moved to Queue '.+?' \(ID=(.+?)\)/ ) {
1893                push @QueueID, $1;
1894            }
1895        }
1896    }
1897
1898    # get queue object
1899    my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
1900
1901    # queue lookup
1902    my @QueueName;
1903    for my $QueueID (@QueueID) {
1904
1905        my $Queue = $QueueObject->QueueLookup( QueueID => $QueueID );
1906
1907        push @QueueName, $Queue;
1908    }
1909
1910    if ( $Param{Type} && $Param{Type} eq 'Name' ) {
1911        return @QueueName;
1912    }
1913    else {
1914        return @QueueID;
1915    }
1916}
1917
1918=head2 TicketTypeList()
1919
1920to get all possible types for a ticket (depends on workflow, if configured)
1921
1922    my %Types = $TicketObject->TicketTypeList(
1923        UserID => 123,
1924    );
1925
1926    my %Types = $TicketObject->TicketTypeList(
1927        CustomerUserID => 'customer_user_id_123',
1928    );
1929
1930    my %Types = $TicketObject->TicketTypeList(
1931        QueueID => 123,
1932        UserID  => 123,
1933    );
1934
1935    my %Types = $TicketObject->TicketTypeList(
1936        TicketID => 123,
1937        UserID   => 123,
1938    );
1939
1940Returns:
1941
1942    %Types = (
1943        1 => 'default',
1944        2 => 'request',
1945        3 => 'offer',
1946    );
1947
1948=cut
1949
1950sub TicketTypeList {
1951    my ( $Self, %Param ) = @_;
1952
1953    # check needed stuff
1954    if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
1955        $Kernel::OM->Get('Kernel::System::Log')->Log(
1956            Priority => 'error',
1957            Message  => 'Need UserID or CustomerUserID!'
1958        );
1959        return;
1960    }
1961
1962    my %Types = $Kernel::OM->Get('Kernel::System::Type')->TypeList( Valid => 1 );
1963
1964    # workflow
1965    my $ACL = $Self->TicketAcl(
1966        %Param,
1967        ReturnType    => 'Ticket',
1968        ReturnSubType => 'Type',
1969        Data          => \%Types,
1970    );
1971
1972    return $Self->TicketAclData() if $ACL;
1973    return %Types;
1974}
1975
1976=head2 TicketTypeSet()
1977
1978to set a ticket type
1979
1980    my $Success = $TicketObject->TicketTypeSet(
1981        TypeID   => 123,
1982        TicketID => 123,
1983        UserID   => 123,
1984    );
1985
1986    my $Success = $TicketObject->TicketTypeSet(
1987        Type     => 'normal',
1988        TicketID => 123,
1989        UserID   => 123,
1990    );
1991
1992Events:
1993    TicketTypeUpdate
1994
1995=cut
1996
1997sub TicketTypeSet {
1998    my ( $Self, %Param ) = @_;
1999
2000    # type lookup
2001    if ( $Param{Type} && !$Param{TypeID} ) {
2002        $Param{TypeID} = $Kernel::OM->Get('Kernel::System::Type')->TypeLookup( Type => $Param{Type} );
2003    }
2004
2005    # check needed stuff
2006    for my $Needed (qw(TicketID TypeID UserID)) {
2007        if ( !$Param{$Needed} ) {
2008            $Kernel::OM->Get('Kernel::System::Log')->Log(
2009                Priority => 'error',
2010                Message  => "Need $Needed!"
2011            );
2012            return;
2013        }
2014    }
2015
2016    # get current ticket
2017    my %Ticket = $Self->TicketGet(
2018        %Param,
2019        DynamicFields => 0,
2020    );
2021
2022    # update needed?
2023    return 1 if $Param{TypeID} == $Ticket{TypeID};
2024
2025    # permission check
2026    my %TypeList = $Self->TicketTypeList(%Param);
2027    if ( !$TypeList{ $Param{TypeID} } ) {
2028        $Kernel::OM->Get('Kernel::System::Log')->Log(
2029            Priority => 'notice',
2030            Message  => "Permission denied on TicketID: $Param{TicketID}!",
2031        );
2032        return;
2033    }
2034
2035    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
2036        SQL => 'UPDATE ticket SET type_id = ?, change_time = current_timestamp, '
2037            . ' change_by = ? WHERE id = ?',
2038        Bind => [ \$Param{TypeID}, \$Param{UserID}, \$Param{TicketID} ],
2039    );
2040
2041    # clear ticket cache
2042    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
2043
2044    # get new ticket data
2045    my %TicketNew = $Self->TicketGet(
2046        %Param,
2047        DynamicFields => 0,
2048    );
2049    $TicketNew{Type} = $TicketNew{Type} || 'NULL';
2050    $Param{TypeID}   = $Param{TypeID}   || '';
2051    $Ticket{Type}    = $Ticket{Type}    || 'NULL';
2052    $Ticket{TypeID}  = $Ticket{TypeID}  || '';
2053
2054    # history insert
2055    $Self->HistoryAdd(
2056        TicketID     => $Param{TicketID},
2057        HistoryType  => 'TypeUpdate',
2058        Name         => "\%\%$TicketNew{Type}\%\%$Param{TypeID}\%\%$Ticket{Type}\%\%$Ticket{TypeID}",
2059        CreateUserID => $Param{UserID},
2060    );
2061
2062    # trigger event
2063    $Self->EventHandler(
2064        Event => 'TicketTypeUpdate',
2065        Data  => {
2066            TicketID => $Param{TicketID},
2067        },
2068        UserID => $Param{UserID},
2069    );
2070
2071    return 1;
2072}
2073
2074=head2 TicketServiceList()
2075
2076to get all possible services for a ticket (depends on workflow, if configured)
2077
2078    my %Services = $TicketObject->TicketServiceList(
2079        QueueID        => 123,
2080        UserID         => 123,
2081    );
2082
2083    my %Services = $TicketObject->TicketServiceList(
2084        CustomerUserID => 123,
2085        QueueID        => 123,
2086    );
2087
2088    my %Services = $TicketObject->TicketServiceList(
2089        CustomerUserID => 123,
2090        TicketID       => 123,
2091        UserID         => 123,
2092    );
2093
2094Returns:
2095
2096    %Services = (
2097        1 => 'ServiceA',
2098        2 => 'ServiceB',
2099        3 => 'ServiceC',
2100    );
2101
2102=cut
2103
2104sub TicketServiceList {
2105    my ( $Self, %Param ) = @_;
2106
2107    # check needed stuff
2108    if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
2109        $Kernel::OM->Get('Kernel::System::Log')->Log(
2110            Priority => 'error',
2111            Message  => 'UserID or CustomerUserID is needed!',
2112        );
2113        return;
2114    }
2115
2116    # check needed stuff
2117    if ( !$Param{QueueID} && !$Param{TicketID} ) {
2118        $Kernel::OM->Get('Kernel::System::Log')->Log(
2119            Priority => 'error',
2120            Message  => 'QueueID or TicketID is needed!',
2121        );
2122        return;
2123    }
2124
2125    my $ServiceObject = $Kernel::OM->Get('Kernel::System::Service');
2126
2127    # Return all Services, filtering by KeepChildren config.
2128    my %AllServices = $ServiceObject->ServiceList(
2129        UserID => 1,
2130        KeepChildren =>
2131            $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service::KeepChildren'),
2132    );
2133
2134    my %Services;
2135    if ( $Param{CustomerUserID} ) {
2136
2137        # Return all Services in relation with CustomerUser.
2138        my %CustomerServices = $ServiceObject->CustomerUserServiceMemberList(
2139            Result            => 'HASH',
2140            CustomerUserLogin => $Param{CustomerUserID},
2141            UserID            => 1,
2142        );
2143
2144        # Filter Services based on relation with CustomerUser and KeepChildren config.
2145        %Services = map { $_ => $CustomerServices{$_} } grep { defined $CustomerServices{$_} } sort keys %AllServices;
2146    }
2147    else {
2148        %Services = %AllServices;
2149    }
2150
2151    # workflow
2152    my $ACL = $Self->TicketAcl(
2153        %Param,
2154        ReturnType    => 'Ticket',
2155        ReturnSubType => 'Service',
2156        Data          => \%Services,
2157    );
2158
2159    return $Self->TicketAclData() if $ACL;
2160    return %Services;
2161}
2162
2163=head2 TicketServiceSet()
2164
2165to set a ticket service
2166
2167    my $Success = $TicketObject->TicketServiceSet(
2168        ServiceID => 123,
2169        TicketID  => 123,
2170        UserID    => 123,
2171    );
2172
2173    my $Success = $TicketObject->TicketServiceSet(
2174        Service  => 'Service A',
2175        TicketID => 123,
2176        UserID   => 123,
2177    );
2178
2179Events:
2180    TicketServiceUpdate
2181
2182=cut
2183
2184sub TicketServiceSet {
2185    my ( $Self, %Param ) = @_;
2186
2187    # service lookup
2188    if ( $Param{Service} && !$Param{ServiceID} ) {
2189        $Param{ServiceID} = $Kernel::OM->Get('Kernel::System::Service')->ServiceLookup(
2190            Name => $Param{Service},
2191        );
2192    }
2193
2194    # check needed stuff
2195    for my $Needed (qw(TicketID ServiceID UserID)) {
2196        if ( !defined $Param{$Needed} ) {
2197            $Kernel::OM->Get('Kernel::System::Log')->Log(
2198                Priority => 'error',
2199                Message  => "Need $Needed!"
2200            );
2201            return;
2202        }
2203    }
2204
2205    # get current ticket
2206    my %Ticket = $Self->TicketGet(
2207        %Param,
2208        DynamicFields => 0,
2209    );
2210
2211    # update needed?
2212    return 1 if $Param{ServiceID} eq $Ticket{ServiceID};
2213
2214    # permission check
2215    my %ServiceList = $Self->TicketServiceList(%Param);
2216    if ( $Param{ServiceID} ne '' && !$ServiceList{ $Param{ServiceID} } ) {
2217        $Kernel::OM->Get('Kernel::System::Log')->Log(
2218            Priority => 'notice',
2219            Message  => "Permission denied on TicketID: $Param{TicketID}!",
2220        );
2221        return;
2222    }
2223
2224    # check database undef/NULL (set value to undef/NULL to prevent database errors)
2225    for my $Parameter (qw(ServiceID SLAID)) {
2226        if ( !$Param{$Parameter} ) {
2227            $Param{$Parameter} = undef;
2228        }
2229    }
2230
2231    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
2232        SQL => 'UPDATE ticket SET service_id = ?, change_time = current_timestamp, '
2233            . ' change_by = ? WHERE id = ?',
2234        Bind => [ \$Param{ServiceID}, \$Param{UserID}, \$Param{TicketID} ],
2235    );
2236
2237    # clear ticket cache
2238    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
2239
2240    # get new ticket data
2241    my %TicketNew = $Self->TicketGet(
2242        %Param,
2243        DynamicFields => 0,
2244    );
2245    $TicketNew{Service} = $TicketNew{Service} || 'NULL';
2246    $Param{ServiceID}   = $Param{ServiceID}   || '';
2247    $Ticket{Service}    = $Ticket{Service}    || 'NULL';
2248    $Ticket{ServiceID}  = $Ticket{ServiceID}  || '';
2249
2250    # history insert
2251    $Self->HistoryAdd(
2252        TicketID    => $Param{TicketID},
2253        HistoryType => 'ServiceUpdate',
2254        Name =>
2255            "\%\%$TicketNew{Service}\%\%$Param{ServiceID}\%\%$Ticket{Service}\%\%$Ticket{ServiceID}",
2256        CreateUserID => $Param{UserID},
2257    );
2258
2259    # trigger notification event
2260    $Self->EventHandler(
2261        Event => 'NotificationServiceUpdate',
2262        Data  => {
2263            TicketID              => $Param{TicketID},
2264            CustomerMessageParams => {},
2265        },
2266        UserID => $Param{UserID},
2267    );
2268
2269    # trigger event
2270    $Self->EventHandler(
2271        Event => 'TicketServiceUpdate',
2272        Data  => {
2273            TicketID => $Param{TicketID},
2274        },
2275        UserID => $Param{UserID},
2276    );
2277
2278    return 1;
2279}
2280
2281=head2 TicketEscalationPreferences()
2282
2283get escalation preferences of a ticket (e. g. from SLA or from Queue based settings)
2284
2285    my %Escalation = $TicketObject->TicketEscalationPreferences(
2286        Ticket => $Param{Ticket},
2287        UserID => $Param{UserID},
2288    );
2289
2290=cut
2291
2292sub TicketEscalationPreferences {
2293    my ( $Self, %Param ) = @_;
2294
2295    # check needed stuff
2296    for my $Needed (qw(Ticket UserID)) {
2297        if ( !defined $Param{$Needed} ) {
2298            $Kernel::OM->Get('Kernel::System::Log')->Log(
2299                Priority => 'error',
2300                Message  => "Need $Needed!"
2301            );
2302            return;
2303        }
2304    }
2305
2306    # get ticket attributes
2307    my %Ticket = %{ $Param{Ticket} };
2308
2309    # get escalation properties
2310    my %Escalation;
2311    if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service') && $Ticket{SLAID} ) {
2312
2313        %Escalation = $Kernel::OM->Get('Kernel::System::SLA')->SLAGet(
2314            SLAID  => $Ticket{SLAID},
2315            UserID => $Param{UserID},
2316            Cache  => 1,
2317        );
2318    }
2319    else {
2320        %Escalation = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet(
2321            ID     => $Ticket{QueueID},
2322            UserID => $Param{UserID},
2323            Cache  => 1,
2324        );
2325    }
2326
2327    return %Escalation;
2328}
2329
2330=head2 TicketEscalationDateCalculation()
2331
2332get escalation properties of a ticket
2333
2334    my %Escalation = $TicketObject->TicketEscalationDateCalculation(
2335        Ticket => $Param{Ticket},
2336        UserID => $Param{UserID},
2337    );
2338
2339returns
2340
2341    (general escalation info)
2342    EscalationDestinationIn          (escalation in e. g. 1h 4m)
2343    EscalationDestinationTime        (date of escalation in unix time, e. g. 72193292)
2344    EscalationDestinationDate        (date of escalation, e. g. "2009-02-14 18:00:00")
2345    EscalationTimeWorkingTime        (seconds of working/service time till escalation, e. g. "1800")
2346    EscalationTime                   (seconds total till escalation, e. g. "3600")
2347
2348    (detail escalation info about first response, update and solution time)
2349    FirstResponseTimeEscalation      (if true, ticket is escalated)
2350    FirstResponseTimeNotification    (if true, notify - x% of escalation has reached)
2351    FirstResponseTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
2352    FirstResponseTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
2353    FirstResponseTimeWorkingTime     (seconds of working/service time till escalation, e. g. "1800")
2354    FirstResponseTime                (seconds total till escalation, e. g. "3600")
2355
2356    UpdateTimeEscalation             (if true, ticket is escalated)
2357    UpdateTimeNotification           (if true, notify - x% of escalation has reached)
2358    UpdateTimeDestinationTime        (date of escalation in unix time, e. g. 72193292)
2359    UpdateTimeDestinationDate        (date of escalation, e. g. "2009-02-14 18:00:00")
2360    UpdateTimeWorkingTime            (seconds of working/service time till escalation, e. g. "1800")
2361    UpdateTime                       (seconds total till escalation, e. g. "3600")
2362
2363    SolutionTimeEscalation           (if true, ticket is escalated)
2364    SolutionTimeNotification         (if true, notify - x% of escalation has reached)
2365    SolutionTimeDestinationTime      (date of escalation in unix time, e. g. 72193292)
2366    SolutionTimeDestinationDate      (date of escalation, e. g. "2009-02-14 18:00:00")
2367    SolutionTimeWorkingTime          (seconds of working/service time till escalation, e. g. "1800")
2368    SolutionTime                     (seconds total till escalation, e. g. "3600")
2369
2370=cut
2371
2372sub TicketEscalationDateCalculation {
2373    my ( $Self, %Param ) = @_;
2374
2375    # check needed stuff
2376    for my $Needed (qw(Ticket UserID)) {
2377        if ( !defined $Param{$Needed} ) {
2378            $Kernel::OM->Get('Kernel::System::Log')->Log(
2379                Priority => 'error',
2380                Message  => "Need $Needed!"
2381            );
2382            return;
2383        }
2384    }
2385
2386    # get ticket attributes
2387    my %Ticket = %{ $Param{Ticket} };
2388
2389    # do no escalations on (merge|close|remove) tickets
2390    return if $Ticket{StateType} eq 'merged';
2391    return if $Ticket{StateType} eq 'closed';
2392    return if $Ticket{StateType} eq 'removed';
2393
2394    # get escalation properties
2395    my %Escalation = $Self->TicketEscalationPreferences(
2396        Ticket => $Param{Ticket},
2397        UserID => $Param{UserID} || 1,
2398    );
2399
2400    # return if we do not have any escalation attributes
2401    my %Map = (
2402        EscalationResponseTime => 'FirstResponse',
2403        EscalationUpdateTime   => 'Update',
2404        EscalationSolutionTime => 'Solution',
2405    );
2406    my $EscalationAttribute;
2407    KEY:
2408    for my $Key ( sort keys %Map ) {
2409        if ( $Escalation{ $Map{$Key} . 'Time' } ) {
2410            $EscalationAttribute = 1;
2411            last KEY;
2412        }
2413    }
2414
2415    return if !$EscalationAttribute;
2416
2417    # create datetime object
2418    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
2419
2420    # calculate escalation times based on escalation properties
2421    my %Data;
2422
2423    TIME:
2424    for my $Key ( sort keys %Map ) {
2425
2426        next TIME if !$Ticket{$Key};
2427
2428        # get time before or over escalation (escalation_destination_unixtime - now)
2429        my $TimeTillEscalation = $Ticket{$Key} - $DateTimeObject->ToEpoch();
2430
2431        # ticket is not escalated till now ($TimeTillEscalation > 0)
2432        my $WorkingTime = 0;
2433        if ( $TimeTillEscalation > 0 ) {
2434
2435            my $StopTimeObj = $Kernel::OM->Create(
2436                'Kernel::System::DateTime',
2437                ObjectParams => {
2438                    Epoch => $Ticket{$Key}
2439                }
2440            );
2441
2442            my $DeltaObj = $DateTimeObject->Delta(
2443                DateTimeObject => $StopTimeObj,
2444                ForWorkingTime => 1,
2445                Calendar       => $Escalation{Calendar},
2446            );
2447
2448            $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0;
2449
2450            # extract needed data
2451            my $Notify = $Escalation{ $Map{$Key} . 'Notify' };
2452            my $Time   = $Escalation{ $Map{$Key} . 'Time' };
2453
2454            # set notification if notify % is reached
2455            if ( $Notify && $Time ) {
2456
2457                my $Reached = 100 - ( $WorkingTime / ( $Time * 60 / 100 ) );
2458
2459                if ( $Reached >= $Notify ) {
2460                    $Data{ $Map{$Key} . 'TimeNotification' } = 1;
2461                }
2462            }
2463        }
2464
2465        # ticket is overtime ($TimeTillEscalation < 0)
2466        else {
2467            my $StartTimeObj = $Kernel::OM->Create(
2468                'Kernel::System::DateTime',
2469                ObjectParams => {
2470                    Epoch => $Ticket{$Key}
2471                }
2472            );
2473
2474            my $DeltaObj = $StartTimeObj->Delta(
2475                DateTimeObject => $DateTimeObject,
2476                ForWorkingTime => 1,
2477                Calendar       => $Escalation{Calendar},
2478            );
2479
2480            $WorkingTime = 0;
2481            if ( $DeltaObj && $DeltaObj->{AbsoluteSeconds} ) {
2482                $WorkingTime = '-' . $DeltaObj->{AbsoluteSeconds};
2483            }
2484
2485            # set escalation
2486            $Data{ $Map{$Key} . 'TimeEscalation' } = 1;
2487        }
2488
2489        my $DestinationDate = $Kernel::OM->Create(
2490            'Kernel::System::DateTime',
2491            ObjectParams => {
2492                Epoch => $Ticket{$Key}
2493            }
2494        );
2495
2496        $Data{ $Map{$Key} . 'TimeDestinationTime' } = $Ticket{$Key};
2497        $Data{ $Map{$Key} . 'TimeDestinationDate' } = $DestinationDate->ToString();
2498        $Data{ $Map{$Key} . 'TimeWorkingTime' }     = $WorkingTime;
2499        $Data{ $Map{$Key} . 'Time' }                = $TimeTillEscalation;
2500
2501        # set global escalation attributes (set the escalation which is the first in time)
2502        if (
2503            !$Data{EscalationDestinationTime}
2504            || $Data{EscalationDestinationTime} > $Ticket{$Key}
2505            )
2506        {
2507            $Data{EscalationDestinationTime} = $Ticket{$Key};
2508            $Data{EscalationDestinationDate} = $DestinationDate->ToString();
2509            $Data{EscalationTimeWorkingTime} = $WorkingTime;
2510            $Data{EscalationTime}            = $TimeTillEscalation;
2511
2512            # escalation time in readable way
2513            $Data{EscalationDestinationIn} = '';
2514            $WorkingTime = abs($WorkingTime);
2515            if ( $WorkingTime >= 3600 ) {
2516                $Data{EscalationDestinationIn} .= int( $WorkingTime / 3600 ) . 'h ';
2517                $WorkingTime = $WorkingTime
2518                    - ( int( $WorkingTime / 3600 ) * 3600 );    # remove already shown hours
2519            }
2520            if ( $WorkingTime <= 3600 || int( $WorkingTime / 60 ) ) {
2521                $Data{EscalationDestinationIn} .= int( $WorkingTime / 60 ) . 'm';
2522            }
2523        }
2524    }
2525
2526    return %Data;
2527}
2528
2529=head2 TicketEscalationIndexBuild()
2530
2531build escalation index of one ticket with current settings (SLA, Queue, Calendar...)
2532
2533    my $Success = $TicketObject->TicketEscalationIndexBuild(
2534        TicketID => $Param{TicketID},
2535        UserID   => $Param{UserID},
2536    );
2537
2538=cut
2539
2540sub TicketEscalationIndexBuild {
2541    my ( $Self, %Param ) = @_;
2542
2543    # check needed stuff
2544    for my $Needed (qw(TicketID UserID)) {
2545        if ( !defined $Param{$Needed} ) {
2546            $Kernel::OM->Get('Kernel::System::Log')->Log(
2547                Priority => 'error',
2548                Message  => "Need $Needed!",
2549            );
2550            return;
2551        }
2552    }
2553
2554    my %Ticket = $Self->TicketGet(
2555        TicketID      => $Param{TicketID},
2556        UserID        => $Param{UserID},
2557        DynamicFields => 0,
2558        Silent        => 1,                  # Suppress warning if the ticket was deleted in the meantime.
2559    );
2560
2561    return if !%Ticket;
2562
2563    # get database object
2564    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
2565
2566    # do no escalations on (merge|close|remove) tickets
2567    if ( $Ticket{StateType} && $Ticket{StateType} =~ /^(merge|close|remove)/i ) {
2568
2569        # update escalation times with 0
2570        my %EscalationTimes = (
2571            EscalationTime         => 'escalation_time',
2572            EscalationResponseTime => 'escalation_response_time',
2573            EscalationUpdateTime   => 'escalation_update_time',
2574            EscalationSolutionTime => 'escalation_solution_time',
2575        );
2576
2577        TIME:
2578        for my $Key ( sort keys %EscalationTimes ) {
2579
2580            # check if table update is needed
2581            next TIME if !$Ticket{$Key};
2582
2583            # update ticket table
2584            $DBObject->Do(
2585                SQL =>
2586                    "UPDATE ticket SET $EscalationTimes{$Key} = 0, change_time = current_timestamp, "
2587                    . " change_by = ? WHERE id = ?",
2588                Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ],
2589            );
2590        }
2591
2592        # clear ticket cache
2593        $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
2594
2595        return 1;
2596    }
2597
2598    # get escalation properties
2599    my %Escalation;
2600    if (%Ticket) {
2601        %Escalation = $Self->TicketEscalationPreferences(
2602            Ticket => \%Ticket,
2603            UserID => $Param{UserID},
2604        );
2605    }
2606
2607    # find escalation times
2608    my $EscalationTime = 0;
2609
2610    # update first response (if not responded till now)
2611    if ( !$Escalation{FirstResponseTime} ) {
2612        $DBObject->Do(
2613            SQL =>
2614                'UPDATE ticket SET escalation_response_time = 0, change_time = current_timestamp, '
2615                . ' change_by = ? WHERE id = ?',
2616            Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
2617        );
2618    }
2619    else {
2620
2621        # check if first response is already done
2622        my %FirstResponseDone = $Self->_TicketGetFirstResponse(
2623            TicketID => $Ticket{TicketID},
2624            Ticket   => \%Ticket,
2625        );
2626
2627        # update first response time to 0
2628        if (%FirstResponseDone) {
2629            $DBObject->Do(
2630                SQL =>
2631                    'UPDATE ticket SET escalation_response_time = 0, change_time = current_timestamp, '
2632                    . ' change_by = ? WHERE id = ?',
2633                Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
2634            );
2635        }
2636
2637        # update first response time to expected escalation destination time
2638        else {
2639
2640            my $DateTimeObject = $Kernel::OM->Create(
2641                'Kernel::System::DateTime',
2642                ObjectParams => {
2643                    String => $Ticket{Created},
2644                }
2645            );
2646
2647            $DateTimeObject->Add(
2648                AsWorkingTime => 1,
2649                Calendar      => $Escalation{Calendar},
2650                Seconds       => $Escalation{FirstResponseTime} * 60,
2651            );
2652
2653            my $DestinationTime = $DateTimeObject->ToEpoch();
2654
2655            # update first response time to $DestinationTime
2656            $DBObject->Do(
2657                SQL =>
2658                    'UPDATE ticket SET escalation_response_time = ?, change_time = current_timestamp, '
2659                    . ' change_by = ? WHERE id = ?',
2660                Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ]
2661            );
2662
2663            # remember escalation time
2664            $EscalationTime = $DestinationTime;
2665        }
2666    }
2667
2668    # update update && do not escalate in "pending auto" for escalation update time
2669    if ( !$Escalation{UpdateTime} || $Ticket{StateType} =~ /^(pending)/i ) {
2670        $DBObject->Do(
2671            SQL => 'UPDATE ticket SET escalation_update_time = 0, change_time = current_timestamp, '
2672                . ' change_by = ? WHERE id = ?',
2673            Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
2674        );
2675    }
2676    else {
2677
2678        # check if update escalation should be set
2679        my @SenderHistory;
2680        return if !$DBObject->Prepare(
2681            SQL => 'SELECT article_sender_type_id, is_visible_for_customer, create_time FROM '
2682                . 'article WHERE ticket_id = ? ORDER BY create_time ASC',
2683            Bind => [ \$Param{TicketID} ],
2684        );
2685        while ( my @Row = $DBObject->FetchrowArray() ) {
2686            push @SenderHistory, {
2687                SenderTypeID         => $Row[0],
2688                IsVisibleForCustomer => $Row[1],
2689                Created              => $Row[2],
2690            };
2691        }
2692
2693        my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
2694
2695        # fill up lookups
2696        for my $Row (@SenderHistory) {
2697            $Row->{SenderType} = $ArticleObject->ArticleSenderTypeLookup(
2698                SenderTypeID => $Row->{SenderTypeID},
2699            );
2700        }
2701
2702        # get latest customer contact time
2703        my $LastSenderTime;
2704        my $LastSenderType = '';
2705        ROW:
2706        for my $Row ( reverse @SenderHistory ) {
2707
2708            # fill up latest sender time (as initial value)
2709            if ( !$LastSenderTime ) {
2710                $LastSenderTime = $Row->{Created};
2711            }
2712
2713            # do not use locked tickets for calculation
2714            #last ROW if $Ticket{Lock} eq 'lock';
2715
2716            # do not use internal articles for calculation
2717            next ROW if !$Row->{IsVisibleForCustomer};
2718
2719            # only use 'agent' and 'customer' sender types for calculation
2720            next ROW if $Row->{SenderType} !~ /^(agent|customer)$/;
2721
2722            # last ROW if latest was customer and the next was not customer
2723            # otherwise use also next, older customer article as latest
2724            # customer followup for starting escalation
2725            if ( $Row->{SenderType} eq 'agent' && $LastSenderType eq 'customer' ) {
2726                last ROW;
2727            }
2728
2729            # start escalation on latest customer article
2730            if ( $Row->{SenderType} eq 'customer' ) {
2731                $LastSenderType = 'customer';
2732                $LastSenderTime = $Row->{Created};
2733            }
2734
2735            # start escalation on latest agent article
2736            if ( $Row->{SenderType} eq 'agent' ) {
2737                $LastSenderTime = $Row->{Created};
2738                last ROW;
2739            }
2740        }
2741        if ($LastSenderTime) {
2742
2743            # create datetime object
2744            my $DateTimeObject = $Kernel::OM->Create(
2745                'Kernel::System::DateTime',
2746                ObjectParams => {
2747                    String => $LastSenderTime,
2748                }
2749            );
2750
2751            $DateTimeObject->Add(
2752                Seconds       => $Escalation{UpdateTime} * 60,
2753                AsWorkingTime => 1,
2754                Calendar      => $Escalation{Calendar},
2755            );
2756
2757            my $DestinationTime = $DateTimeObject->ToEpoch();
2758
2759            # update update time to $DestinationTime
2760            $DBObject->Do(
2761                SQL =>
2762                    'UPDATE ticket SET escalation_update_time = ?, change_time = current_timestamp, '
2763                    . ' change_by = ? WHERE id = ?',
2764                Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ]
2765            );
2766
2767            # remember escalation time
2768            if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) {
2769                $EscalationTime = $DestinationTime;
2770            }
2771        }
2772
2773        # else, no not escalate, because latest sender was agent
2774        else {
2775            $DBObject->Do(
2776                SQL =>
2777                    'UPDATE ticket SET escalation_update_time = 0, change_time = current_timestamp, '
2778                    . ' change_by = ? WHERE id = ?',
2779                Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
2780            );
2781        }
2782    }
2783
2784    # update solution
2785    if ( !$Escalation{SolutionTime} ) {
2786        $DBObject->Do(
2787            SQL =>
2788                'UPDATE ticket SET escalation_solution_time = 0, change_time = current_timestamp, '
2789                . ' change_by = ? WHERE id = ?',
2790            Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ],
2791        );
2792    }
2793    else {
2794
2795        # find solution time / first close time
2796        my %SolutionDone = $Self->_TicketGetClosed(
2797            TicketID => $Ticket{TicketID},
2798            Ticket   => \%Ticket,
2799        );
2800
2801        # update solution time to 0
2802        if (%SolutionDone) {
2803            $DBObject->Do(
2804                SQL =>
2805                    'UPDATE ticket SET escalation_solution_time = 0, change_time = current_timestamp, '
2806                    . ' change_by = ? WHERE id = ?',
2807                Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ],
2808            );
2809        }
2810        else {
2811
2812            # get datetime object
2813            my $DateTimeObject = $Kernel::OM->Create(
2814                'Kernel::System::DateTime',
2815                ObjectParams => {
2816                    String => $Ticket{Created},
2817                }
2818            );
2819
2820            $DateTimeObject->Add(
2821                Seconds       => $Escalation{SolutionTime} * 60,
2822                AsWorkingTime => 1,
2823                Calendar      => $Escalation{Calendar},
2824            );
2825
2826            my $DestinationTime = $DateTimeObject->ToEpoch();
2827
2828            # update solution time to $DestinationTime
2829            $DBObject->Do(
2830                SQL =>
2831                    'UPDATE ticket SET escalation_solution_time = ?, change_time = current_timestamp, '
2832                    . ' change_by = ? WHERE id = ?',
2833                Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ],
2834            );
2835
2836            # remember escalation time
2837            if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) {
2838                $EscalationTime = $DestinationTime;
2839            }
2840        }
2841    }
2842
2843    # update escalation time (< escalation time)
2844    if ( defined $EscalationTime ) {
2845        $DBObject->Do(
2846            SQL => 'UPDATE ticket SET escalation_time = ?, change_time = current_timestamp, '
2847                . ' change_by = ? WHERE id = ?',
2848            Bind => [ \$EscalationTime, \$Param{UserID}, \$Ticket{TicketID}, ],
2849        );
2850    }
2851
2852    # clear ticket cache
2853    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
2854
2855    return 1;
2856}
2857
2858=head2 TicketSLAList()
2859
2860to get all possible SLAs for a ticket (depends on workflow, if configured)
2861
2862    my %SLAs = $TicketObject->TicketSLAList(
2863        ServiceID => 1,
2864        UserID    => 123,
2865    );
2866
2867    my %SLAs = $TicketObject->TicketSLAList(
2868        ServiceID      => 1,
2869        CustomerUserID => 'customer_user_id_123',
2870    );
2871
2872
2873    my %SLAs = $TicketObject->TicketSLAList(
2874        QueueID   => 123,
2875        ServiceID => 1,
2876        UserID    => 123,
2877    );
2878
2879    my %SLAs = $TicketObject->TicketSLAList(
2880        TicketID  => 123,
2881        ServiceID => 1,
2882        UserID    => 123,
2883    );
2884
2885Returns:
2886
2887    %SLAs = (
2888        1 => 'SLA A',
2889        2 => 'SLA B',
2890        3 => 'SLA C',
2891    );
2892
2893=cut
2894
2895sub TicketSLAList {
2896    my ( $Self, %Param ) = @_;
2897
2898    # check needed stuff
2899    if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
2900        $Kernel::OM->Get('Kernel::System::Log')->Log(
2901            Priority => 'error',
2902            Message  => 'Need UserID or CustomerUserID!'
2903        );
2904        return;
2905    }
2906
2907    # check needed stuff
2908    if ( !$Param{QueueID} && !$Param{TicketID} ) {
2909        $Kernel::OM->Get('Kernel::System::Log')->Log(
2910            Priority => 'error',
2911            Message  => 'Need QueueID or TicketID!'
2912        );
2913        return;
2914    }
2915
2916    # return emty hash, if no service id is given
2917    if ( !$Param{ServiceID} ) {
2918        return ();
2919    }
2920
2921    # get sla list
2922    my %SLAs = $Kernel::OM->Get('Kernel::System::SLA')->SLAList(
2923        ServiceID => $Param{ServiceID},
2924        UserID    => 1,
2925    );
2926
2927    # workflow
2928    my $ACL = $Self->TicketAcl(
2929        %Param,
2930        ReturnType    => 'Ticket',
2931        ReturnSubType => 'SLA',
2932        Data          => \%SLAs,
2933    );
2934
2935    return $Self->TicketAclData() if $ACL;
2936    return %SLAs;
2937}
2938
2939=head2 TicketSLASet()
2940
2941to set a ticket service level agreement
2942
2943    my $Success = $TicketObject->TicketSLASet(
2944        SLAID    => 123,
2945        TicketID => 123,
2946        UserID   => 123,
2947    );
2948
2949    my $Success = $TicketObject->TicketSLASet(
2950        SLA      => 'SLA A',
2951        TicketID => 123,
2952        UserID   => 123,
2953    );
2954
2955Events:
2956    TicketSLAUpdate
2957
2958=cut
2959
2960sub TicketSLASet {
2961    my ( $Self, %Param ) = @_;
2962
2963    # sla lookup
2964    if ( $Param{SLA} && !$Param{SLAID} ) {
2965        $Param{SLAID} = $Kernel::OM->Get('Kernel::System::SLA')->SLALookup( Name => $Param{SLA} );
2966    }
2967
2968    # check needed stuff
2969    for my $Needed (qw(TicketID SLAID UserID)) {
2970        if ( !defined $Param{$Needed} ) {
2971            $Kernel::OM->Get('Kernel::System::Log')->Log(
2972                Priority => 'error',
2973                Message  => "Need $Needed!"
2974            );
2975            return;
2976        }
2977    }
2978
2979    # get current ticket
2980    my %Ticket = $Self->TicketGet(
2981        %Param,
2982        DynamicFields => 0,
2983    );
2984
2985    # update needed?
2986    return 1 if ( $Param{SLAID} eq $Ticket{SLAID} );
2987
2988    # permission check
2989    my %SLAList = $Self->TicketSLAList(
2990        %Param,
2991        ServiceID => $Ticket{ServiceID},
2992    );
2993
2994    if ( $Param{UserID} != 1 && $Param{SLAID} ne '' && !$SLAList{ $Param{SLAID} } ) {
2995        $Kernel::OM->Get('Kernel::System::Log')->Log(
2996            Priority => 'notice',
2997            Message  => "Permission denied on TicketID: $Param{TicketID}!",
2998        );
2999        return;
3000    }
3001
3002    # check database undef/NULL (set value to undef/NULL to prevent database errors)
3003    for my $Parameter (qw(ServiceID SLAID)) {
3004        if ( !$Param{$Parameter} ) {
3005            $Param{$Parameter} = undef;
3006        }
3007    }
3008
3009    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
3010        SQL => 'UPDATE ticket SET sla_id = ?, change_time = current_timestamp, '
3011            . ' change_by = ? WHERE id = ?',
3012        Bind => [ \$Param{SLAID}, \$Param{UserID}, \$Param{TicketID} ],
3013    );
3014
3015    # clear ticket cache
3016    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
3017
3018    # get new ticket data
3019    my %TicketNew = $Self->TicketGet(
3020        %Param,
3021        DynamicFields => 0,
3022    );
3023    $TicketNew{SLA} = $TicketNew{SLA} || 'NULL';
3024    $Param{SLAID}   = $Param{SLAID}   || '';
3025    $Ticket{SLA}    = $Ticket{SLA}    || 'NULL';
3026    $Ticket{SLAID}  = $Ticket{SLAID}  || '';
3027
3028    # history insert
3029    $Self->HistoryAdd(
3030        TicketID     => $Param{TicketID},
3031        HistoryType  => 'SLAUpdate',
3032        Name         => "\%\%$TicketNew{SLA}\%\%$Param{SLAID}\%\%$Ticket{SLA}\%\%$Ticket{SLAID}",
3033        CreateUserID => $Param{UserID},
3034    );
3035
3036    # trigger event, OldTicketData is needed for escalation events
3037    $Self->EventHandler(
3038        Event => 'TicketSLAUpdate',
3039        Data  => {
3040            TicketID      => $Param{TicketID},
3041            OldTicketData => \%Ticket,
3042        },
3043        UserID => $Param{UserID},
3044    );
3045
3046    return 1;
3047}
3048
3049=head2 TicketCustomerSet()
3050
3051Set customer data of ticket. Can set 'No' (CustomerID),
3052'User' (CustomerUserID), or both.
3053
3054    my $Success = $TicketObject->TicketCustomerSet(
3055        No       => 'client123',
3056        User     => 'client-user-123',
3057        TicketID => 123,
3058        UserID   => 23,
3059    );
3060
3061Events:
3062    TicketCustomerUpdate
3063
3064=cut
3065
3066sub TicketCustomerSet {
3067    my ( $Self, %Param ) = @_;
3068
3069    # check needed stuff
3070    for my $Needed (qw(TicketID UserID)) {
3071        if ( !$Param{$Needed} ) {
3072            $Kernel::OM->Get('Kernel::System::Log')->Log(
3073                Priority => 'error',
3074                Message  => "Need $Needed!"
3075            );
3076            return;
3077        }
3078    }
3079    if ( !defined $Param{No} && !defined $Param{User} ) {
3080        $Kernel::OM->Get('Kernel::System::Log')->Log(
3081            Priority => 'error',
3082            Message  => 'Need User or No for update!'
3083        );
3084        return;
3085    }
3086
3087    # get database object
3088    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
3089
3090    # db customer id update
3091    if ( defined $Param{No} ) {
3092
3093        my $Ok = $DBObject->Do(
3094            SQL => 'UPDATE ticket SET customer_id = ?, '
3095                . ' change_time = current_timestamp, change_by = ? WHERE id = ?',
3096            Bind => [ \$Param{No}, \$Param{UserID}, \$Param{TicketID} ]
3097        );
3098
3099        if ($Ok) {
3100            $Param{History} = "CustomerID=$Param{No};";
3101        }
3102    }
3103
3104    # db customer user update
3105    if ( defined $Param{User} ) {
3106
3107        my $Ok = $DBObject->Do(
3108            SQL => 'UPDATE ticket SET customer_user_id = ?, '
3109                . 'change_time = current_timestamp, change_by = ? WHERE id = ?',
3110            Bind => [ \$Param{User}, \$Param{UserID}, \$Param{TicketID} ],
3111        );
3112
3113        if ($Ok) {
3114            $Param{History} .= "CustomerUser=$Param{User};";
3115        }
3116    }
3117
3118    # clear ticket cache
3119    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
3120
3121    # if no change
3122    if ( !$Param{History} ) {
3123        return;
3124    }
3125
3126    # history insert
3127    $Self->HistoryAdd(
3128        TicketID     => $Param{TicketID},
3129        HistoryType  => 'CustomerUpdate',
3130        Name         => "\%\%" . $Param{History},
3131        CreateUserID => $Param{UserID},
3132    );
3133
3134    # trigger event
3135    $Self->EventHandler(
3136        Event => 'TicketCustomerUpdate',
3137        Data  => {
3138            TicketID => $Param{TicketID},
3139        },
3140        UserID => $Param{UserID},
3141    );
3142
3143    return 1;
3144}
3145
3146=head2 TicketPermission()
3147
3148returns whether or not the agent has permission on a ticket
3149
3150    my $Access = $TicketObject->TicketPermission(
3151        Type     => 'ro',
3152        TicketID => 123,
3153        UserID   => 123,
3154    );
3155
3156or without logging, for example for to check if a link/action should be shown
3157
3158    my $Access = $TicketObject->TicketPermission(
3159        Type     => 'ro',
3160        TicketID => 123,
3161        LogNo    => 1,
3162        UserID   => 123,
3163    );
3164
3165=cut
3166
3167sub TicketPermission {
3168    my ( $Self, %Param ) = @_;
3169
3170    # check needed stuff
3171    for my $Needed (qw(Type TicketID UserID)) {
3172        if ( !$Param{$Needed} ) {
3173            $Kernel::OM->Get('Kernel::System::Log')->Log(
3174                Priority => 'error',
3175                Message  => "Need $Needed!"
3176            );
3177            return;
3178        }
3179    }
3180
3181    # get needed objects
3182    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
3183    my $MainObject   = $Kernel::OM->Get('Kernel::System::Main');
3184
3185    # run all TicketPermission modules
3186    if ( ref $ConfigObject->Get('Ticket::Permission') eq 'HASH' ) {
3187        my %Modules = %{ $ConfigObject->Get('Ticket::Permission') };
3188
3189        MODULE:
3190        for my $Module ( sort keys %Modules ) {
3191
3192            # log try of load module
3193            if ( $Self->{Debug} > 1 ) {
3194                $Kernel::OM->Get('Kernel::System::Log')->Log(
3195                    Priority => 'debug',
3196                    Message  => "Try to load module: $Modules{$Module}->{Module}!",
3197                );
3198            }
3199
3200            # load module
3201            next MODULE if !$MainObject->Require( $Modules{$Module}->{Module} );
3202
3203            # create object
3204            my $ModuleObject = $Modules{$Module}->{Module}->new();
3205
3206            # execute Run()
3207            my $AccessOk = $ModuleObject->Run(%Param);
3208
3209            # check granted option (should I say ok)
3210            if ( $AccessOk && $Modules{$Module}->{Granted} ) {
3211                if ( $Self->{Debug} > 0 ) {
3212                    $Kernel::OM->Get('Kernel::System::Log')->Log(
3213                        Priority => 'debug',
3214                        Message  => "Granted access '$Param{Type}' true for "
3215                            . "TicketID '$Param{TicketID}' "
3216                            . "through $Modules{$Module}->{Module} (no more checks)!",
3217                    );
3218                }
3219
3220                # access ok
3221                return 1;
3222            }
3223
3224            # return because access is false but it's required
3225            if ( !$AccessOk && $Modules{$Module}->{Required} ) {
3226                if ( !$Param{LogNo} ) {
3227                    $Kernel::OM->Get('Kernel::System::Log')->Log(
3228                        Priority => 'notice',
3229                        Message  => "Permission denied because module "
3230                            . "($Modules{$Module}->{Module}) is required "
3231                            . "(UserID: $Param{UserID} '$Param{Type}' on "
3232                            . "TicketID: $Param{TicketID})!",
3233                    );
3234                }
3235
3236                # access not ok
3237                return;
3238            }
3239        }
3240    }
3241
3242    # don't grant access to the ticket
3243    if ( !$Param{LogNo} ) {
3244        $Kernel::OM->Get('Kernel::System::Log')->Log(
3245            Priority => 'notice',
3246            Message  => "Permission denied (UserID: $Param{UserID} '$Param{Type}' "
3247                . "on TicketID: $Param{TicketID})!",
3248        );
3249    }
3250
3251    return;
3252}
3253
3254=head2 TicketCustomerPermission()
3255
3256returns whether or not a customer has permission to a ticket
3257
3258    my $Access = $TicketObject->TicketCustomerPermission(
3259        Type     => 'ro',
3260        TicketID => 123,
3261        UserID   => 123,
3262    );
3263
3264or without logging, for example for to check if a link/action should be displayed
3265
3266    my $Access = $TicketObject->TicketCustomerPermission(
3267        Type     => 'ro',
3268        TicketID => 123,
3269        LogNo    => 1,
3270        UserID   => 123,
3271    );
3272
3273=cut
3274
3275sub TicketCustomerPermission {
3276    my ( $Self, %Param ) = @_;
3277
3278    # check needed stuff
3279    for my $Needed (qw(Type TicketID UserID)) {
3280        if ( !$Param{$Needed} ) {
3281            $Kernel::OM->Get('Kernel::System::Log')->Log(
3282                Priority => 'error',
3283                Message  => "Need $Needed!"
3284            );
3285            return;
3286        }
3287    }
3288
3289    # get main object
3290    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
3291    my $MainObject   = $Kernel::OM->Get('Kernel::System::Main');
3292
3293    # run all CustomerTicketPermission modules
3294    if ( ref $ConfigObject->Get('CustomerTicket::Permission') eq 'HASH' ) {
3295        my %Modules = %{ $ConfigObject->Get('CustomerTicket::Permission') };
3296
3297        MODULE:
3298        for my $Module ( sort keys %Modules ) {
3299
3300            # log try of load module
3301            if ( $Self->{Debug} > 1 ) {
3302                $Kernel::OM->Get('Kernel::System::Log')->Log(
3303                    Priority => 'debug',
3304                    Message  => "Try to load module: $Modules{$Module}->{Module}!",
3305                );
3306            }
3307
3308            # load module
3309            next MODULE if !$MainObject->Require( $Modules{$Module}->{Module} );
3310
3311            # create object
3312            my $ModuleObject = $Modules{$Module}->{Module}->new();
3313
3314            # execute Run()
3315            my $AccessOk = $ModuleObject->Run(%Param);
3316
3317            # check granted option (should I say ok)
3318            if ( $AccessOk && $Modules{$Module}->{Granted} ) {
3319                if ( $Self->{Debug} > 0 ) {
3320                    $Kernel::OM->Get('Kernel::System::Log')->Log(
3321                        Priority => 'debug',
3322                        Message  => "Granted access '$Param{Type}' true for "
3323                            . "TicketID '$Param{TicketID}' "
3324                            . "through $Modules{$Module}->{Module} (no more checks)!",
3325                    );
3326                }
3327
3328                # access ok
3329                return 1;
3330            }
3331
3332            # return because access is false but it's required
3333            if ( !$AccessOk && $Modules{$Module}->{Required} ) {
3334                if ( !$Param{LogNo} ) {
3335                    $Kernel::OM->Get('Kernel::System::Log')->Log(
3336                        Priority => 'notice',
3337                        Message  => "Permission denied because module "
3338                            . "($Modules{$Module}->{Module}) is required "
3339                            . "(UserID: $Param{UserID} '$Param{Type}' on "
3340                            . "TicketID: $Param{TicketID})!",
3341                    );
3342                }
3343
3344                # access not ok
3345                return;
3346            }
3347        }
3348    }
3349
3350    # don't grant access to the ticket
3351    if ( !$Param{LogNo} ) {
3352        $Kernel::OM->Get('Kernel::System::Log')->Log(
3353            Priority => 'notice',
3354            Message  => "Permission denied (UserID: $Param{UserID} '$Param{Type}' on "
3355                . "TicketID: $Param{TicketID})!",
3356        );
3357    }
3358    return;
3359}
3360
3361=head2 GetSubscribedUserIDsByQueueID()
3362
3363returns an array of user ids which selected the given queue id as
3364custom queue.
3365
3366    my @UserIDs = $TicketObject->GetSubscribedUserIDsByQueueID(
3367        QueueID => 123,
3368    );
3369
3370Returns:
3371
3372    @UserIDs = ( 1, 2, 3 );
3373
3374=cut
3375
3376sub GetSubscribedUserIDsByQueueID {
3377    my ( $Self, %Param ) = @_;
3378
3379    # check needed stuff
3380    if ( !$Param{QueueID} ) {
3381        $Kernel::OM->Get('Kernel::System::Log')->Log(
3382            Priority => 'error',
3383            Message  => 'Need QueueID!'
3384        );
3385        return;
3386    }
3387
3388    # get group of queue
3389    my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( ID => $Param{QueueID} );
3390
3391    # get database object
3392    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
3393
3394    # fetch all queues
3395    my @UserIDs;
3396    return if !$DBObject->Prepare(
3397        SQL  => 'SELECT user_id FROM personal_queues WHERE queue_id = ?',
3398        Bind => [ \$Param{QueueID} ],
3399    );
3400    while ( my @Row = $DBObject->FetchrowArray() ) {
3401        push @UserIDs, $Row[0];
3402    }
3403
3404    # get needed objects
3405    my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
3406    my $UserObject  = $Kernel::OM->Get('Kernel::System::User');
3407
3408    # check if user is valid and check permissions
3409    my @CleanUserIDs;
3410
3411    USER:
3412    for my $UserID (@UserIDs) {
3413
3414        my %User = $UserObject->GetUserData(
3415            UserID => $UserID,
3416            Valid  => 1
3417        );
3418
3419        next USER if !%User;
3420
3421        # just send emails to permitted agents
3422        my %GroupMember = $GroupObject->PermissionUserGet(
3423            UserID => $UserID,
3424            Type   => 'ro',
3425        );
3426
3427        if ( $GroupMember{ $Queue{GroupID} } ) {
3428            push @CleanUserIDs, $UserID;
3429        }
3430    }
3431
3432    return @CleanUserIDs;
3433}
3434
3435=head2 GetSubscribedUserIDsByServiceID()
3436
3437returns an array of user ids which selected the given service id as
3438custom service.
3439
3440    my @UserIDs = $TicketObject->GetSubscribedUserIDsByServiceID(
3441        ServiceID => 123,
3442    );
3443
3444Returns:
3445
3446    @UserIDs = ( 1, 2, 3 );
3447
3448=cut
3449
3450sub GetSubscribedUserIDsByServiceID {
3451    my ( $Self, %Param ) = @_;
3452
3453    # check needed stuff
3454    if ( !$Param{ServiceID} ) {
3455        $Kernel::OM->Get('Kernel::System::Log')->Log(
3456            Priority => 'error',
3457            Message  => 'Need ServiceID!'
3458        );
3459        return;
3460    }
3461
3462    # get database object
3463    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
3464
3465    # fetch all users
3466    my @UserIDs;
3467    return if !$DBObject->Prepare(
3468        SQL => '
3469            SELECT user_id
3470            FROM personal_services
3471            WHERE service_id = ?',
3472        Bind => [ \$Param{ServiceID} ],
3473    );
3474
3475    while ( my @Row = $DBObject->FetchrowArray() ) {
3476        push @UserIDs, $Row[0];
3477    }
3478
3479    # get user object
3480    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
3481
3482    # check if user is valid
3483    my @CleanUserIDs;
3484    USER:
3485    for my $UserID (@UserIDs) {
3486
3487        my %User = $UserObject->GetUserData(
3488            UserID => $UserID,
3489            Valid  => 1,
3490        );
3491
3492        next USER if !%User;
3493
3494        push @CleanUserIDs, $UserID;
3495    }
3496
3497    return @CleanUserIDs;
3498}
3499
3500=head2 TicketPendingTimeSet()
3501
3502set ticket pending time:
3503
3504    my $Success = $TicketObject->TicketPendingTimeSet(
3505        Year     => 2003,
3506        Month    => 08,
3507        Day      => 14,
3508        Hour     => 22,
3509        Minute   => 05,
3510        TicketID => 123,
3511        UserID   => 23,
3512    );
3513
3514or use a time stamp:
3515
3516    my $Success = $TicketObject->TicketPendingTimeSet(
3517        String   => '2003-08-14 22:05:00',
3518        TicketID => 123,
3519        UserID   => 23,
3520    );
3521
3522or use a diff (set pending time to "now" + diff minutes)
3523
3524    my $Success = $TicketObject->TicketPendingTimeSet(
3525        Diff     => ( 7 * 24 * 60 ),  # minutes (here: 10080 minutes - 7 days)
3526        TicketID => 123,
3527        UserID   => 23,
3528    );
3529
3530If you want to set the pending time to null, just supply zeros:
3531
3532    my $Success = $TicketObject->TicketPendingTimeSet(
3533        Year     => 0000,
3534        Month    => 00,
3535        Day      => 00,
3536        Hour     => 00,
3537        Minute   => 00,
3538        TicketID => 123,
3539        UserID   => 23,
3540    );
3541
3542or use a time stamp:
3543
3544    my $Success = $TicketObject->TicketPendingTimeSet(
3545        String   => '0000-00-00 00:00:00',
3546        TicketID => 123,
3547        UserID   => 23,
3548    );
3549
3550Events:
3551    TicketPendingTimeUpdate
3552
3553=cut
3554
3555sub TicketPendingTimeSet {
3556    my ( $Self, %Param ) = @_;
3557
3558    # check needed stuff
3559    if ( !$Param{String} && !$Param{Diff} ) {
3560        for my $Needed (qw(Year Month Day Hour Minute TicketID UserID)) {
3561            if ( !defined $Param{$Needed} ) {
3562                $Kernel::OM->Get('Kernel::System::Log')->Log(
3563                    Priority => 'error',
3564                    Message  => "Need $Needed!"
3565                );
3566                return;
3567            }
3568        }
3569    }
3570    elsif (
3571        !$Param{String} &&
3572        !( $Param{Year} && $Param{Month} && $Param{Day} && $Param{Hour} && $Param{Minute} )
3573        )
3574    {
3575        for my $Needed (qw(Diff TicketID UserID)) {
3576            if ( !defined $Param{$Needed} ) {
3577                $Kernel::OM->Get('Kernel::System::Log')->Log(
3578                    Priority => 'error',
3579                    Message  => "Need $Needed!"
3580                );
3581                return;
3582            }
3583        }
3584    }
3585    else {
3586        for my $Needed (qw(String TicketID UserID)) {
3587            if ( !defined $Param{$Needed} ) {
3588                $Kernel::OM->Get('Kernel::System::Log')->Log(
3589                    Priority => 'error',
3590                    Message  => "Need $Needed!"
3591                );
3592                return;
3593            }
3594        }
3595    }
3596
3597    # check if we need to null the PendingTime
3598    my $PendingTimeNull;
3599    if ( $Param{String} && $Param{String} eq '0000-00-00 00:00:00' ) {
3600        $PendingTimeNull = 1;
3601        $Param{Sec}      = 0;
3602        $Param{Minute}   = 0;
3603        $Param{Hour}     = 0;
3604        $Param{Day}      = 0;
3605        $Param{Month}    = 0;
3606        $Param{Year}     = 0;
3607    }
3608    elsif (
3609        !$Param{String}
3610        && !$Param{Diff}
3611        && $Param{Minute} == 0
3612        && $Param{Hour} == 0 && $Param{Day} == 0
3613        && $Param{Month} == 0
3614        && $Param{Year} == 0
3615        )
3616    {
3617        $PendingTimeNull = 1;
3618    }
3619
3620    # get system time from string/params
3621    my $Time = 0;
3622    if ( !$PendingTimeNull ) {
3623
3624        if ( $Param{String} ) {
3625
3626            my $DateTimeObject = $Kernel::OM->Create(
3627                'Kernel::System::DateTime',
3628                ObjectParams => {
3629                    String => $Param{String}
3630                }
3631            );
3632            return if ( !$DateTimeObject );
3633
3634            $Time = $DateTimeObject->ToEpoch();
3635
3636            my $DateTimeValues = $DateTimeObject->Get();
3637            $Param{Sec}    = $DateTimeValues->{Second};
3638            $Param{Minute} = $DateTimeValues->{Minute};
3639            $Param{Hour}   = $DateTimeValues->{Hour};
3640            $Param{Day}    = $DateTimeValues->{Day};
3641            $Param{Month}  = $DateTimeValues->{Month};
3642            $Param{Year}   = $DateTimeValues->{Year};
3643        }
3644        elsif ( $Param{Diff} ) {
3645
3646            my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
3647            $DateTimeObject->Add( Minutes => $Param{Diff} );
3648
3649            $Time = $DateTimeObject->ToEpoch();
3650
3651            my $DateTimeValues = $DateTimeObject->Get();
3652            $Param{Sec}    = $DateTimeValues->{Second};
3653            $Param{Minute} = $DateTimeValues->{Minute};
3654            $Param{Hour}   = $DateTimeValues->{Hour};
3655            $Param{Day}    = $DateTimeValues->{Day};
3656            $Param{Month}  = $DateTimeValues->{Month};
3657            $Param{Year}   = $DateTimeValues->{Year};
3658        }
3659        else {
3660            # create datetime object
3661            my $DateTimeObject = $Kernel::OM->Create(
3662                'Kernel::System::DateTime',
3663                ObjectParams => {
3664                    String => "$Param{Year}-$Param{Month}-$Param{Day} $Param{Hour}:$Param{Minute}:00",
3665                }
3666            );
3667            return if !$DateTimeObject;
3668            $Time = $DateTimeObject->ToEpoch();
3669        }
3670
3671        # return if no convert is possible
3672        return if !$Time;
3673    }
3674
3675    # db update
3676    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
3677        SQL => 'UPDATE ticket SET until_time = ?, change_time = current_timestamp, change_by = ?'
3678            . ' WHERE id = ?',
3679        Bind => [ \$Time, \$Param{UserID}, \$Param{TicketID} ],
3680    );
3681
3682    # clear ticket cache
3683    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
3684
3685    # history insert
3686    $Self->HistoryAdd(
3687        TicketID    => $Param{TicketID},
3688        HistoryType => 'SetPendingTime',
3689        Name        => '%%'
3690            . sprintf( "%02d", $Param{Year} ) . '-'
3691            . sprintf( "%02d", $Param{Month} ) . '-'
3692            . sprintf( "%02d", $Param{Day} ) . ' '
3693            . sprintf( "%02d", $Param{Hour} ) . ':'
3694            . sprintf( "%02d", $Param{Minute} ) . '',
3695        CreateUserID => $Param{UserID},
3696    );
3697
3698    # trigger event
3699    $Self->EventHandler(
3700        Event => 'TicketPendingTimeUpdate',
3701        Data  => {
3702            TicketID => $Param{TicketID},
3703        },
3704        UserID => $Param{UserID},
3705    );
3706
3707    return 1;
3708}
3709
3710=head2 TicketLockGet()
3711
3712check if a ticket is locked or not
3713
3714    if ($TicketObject->TicketLockGet(TicketID => 123)) {
3715        print "Ticket is locked!\n";
3716    }
3717    else {
3718        print "Ticket is not locked!\n";
3719    }
3720
3721=cut
3722
3723sub TicketLockGet {
3724    my ( $Self, %Param ) = @_;
3725
3726    # check needed stuff
3727    if ( !$Param{TicketID} ) {
3728        $Kernel::OM->Get('Kernel::System::Log')->Log(
3729            Priority => 'error',
3730            Message  => 'Need TicketID!'
3731        );
3732        return;
3733    }
3734
3735    my %Ticket = $Self->TicketGet(
3736        %Param,
3737        DynamicFields => 0,
3738    );
3739
3740    # check lock state
3741    return 1 if lc $Ticket{Lock} eq 'lock';
3742
3743    return;
3744}
3745
3746=head2 TicketLockSet()
3747
3748to lock or unlock a ticket
3749
3750    my $Success = $TicketObject->TicketLockSet(
3751        Lock     => 'lock',
3752        TicketID => 123,
3753        UserID   => 123,
3754    );
3755
3756    my $Success = $TicketObject->TicketLockSet(
3757        LockID   => 1,
3758        TicketID => 123,
3759        UserID   => 123,
3760    );
3761
3762Optional attribute:
3763SendNoNotification, disable or enable agent and customer notification for this
3764action. Otherwise a notification will be sent to agent and customer.
3765
3766For example:
3767
3768        SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
3769
3770Events:
3771    TicketLockUpdate
3772
3773=cut
3774
3775sub TicketLockSet {
3776    my ( $Self, %Param ) = @_;
3777
3778    # lookup!
3779    if ( !$Param{LockID} && $Param{Lock} ) {
3780
3781        $Param{LockID} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
3782            Lock => $Param{Lock},
3783        );
3784    }
3785    if ( $Param{LockID} && !$Param{Lock} ) {
3786
3787        $Param{Lock} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
3788            LockID => $Param{LockID},
3789        );
3790    }
3791
3792    # check needed stuff
3793    for my $Needed (qw(TicketID UserID LockID Lock)) {
3794        if ( !$Param{$Needed} ) {
3795            $Kernel::OM->Get('Kernel::System::Log')->Log(
3796                Priority => 'error',
3797                Message  => "Need $Needed!"
3798            );
3799            return;
3800        }
3801    }
3802    if ( !$Param{Lock} && !$Param{LockID} ) {
3803        $Kernel::OM->Get('Kernel::System::Log')->Log(
3804            Priority => 'error',
3805            Message  => 'Need LockID or Lock!'
3806        );
3807        return;
3808    }
3809
3810    # check if update is needed
3811    my %Ticket = $Self->TicketGet(
3812        %Param,
3813        DynamicFields => 0,
3814    );
3815    return 1 if $Ticket{Lock} eq $Param{Lock};
3816
3817    # db update
3818    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
3819        SQL => 'UPDATE ticket SET ticket_lock_id = ?, '
3820            . ' change_time = current_timestamp, change_by = ? WHERE id = ?',
3821        Bind => [ \$Param{LockID}, \$Param{UserID}, \$Param{TicketID} ],
3822    );
3823
3824    # clear ticket cache
3825    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
3826
3827    # add history
3828    my $HistoryType = '';
3829    if ( lc $Param{Lock} eq 'unlock' ) {
3830        $HistoryType = 'Unlock';
3831    }
3832    elsif ( lc $Param{Lock} eq 'lock' ) {
3833        $HistoryType = 'Lock';
3834    }
3835    else {
3836        $HistoryType = 'Misc';
3837    }
3838    if ($HistoryType) {
3839        $Self->HistoryAdd(
3840            TicketID     => $Param{TicketID},
3841            CreateUserID => $Param{UserID},
3842            HistoryType  => $HistoryType,
3843            Name         => "\%\%$Param{Lock}",
3844        );
3845    }
3846
3847    # set unlock time it event is 'lock'
3848    if ( $Param{Lock} eq 'lock' ) {
3849
3850        # create datetime object
3851        my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
3852
3853        $Self->TicketUnlockTimeoutUpdate(
3854            UnlockTimeout => $DateTimeObject->ToEpoch(),
3855            TicketID      => $Param{TicketID},
3856            UserID        => $Param{UserID},
3857        );
3858    }
3859
3860    # send unlock notify
3861    if ( lc $Param{Lock} eq 'unlock' ) {
3862
3863        my $Notification = defined $Param{Notification} ? $Param{Notification} : 1;
3864        if ( !$Param{SendNoNotification} && $Notification )
3865        {
3866            my @SkipRecipients;
3867            if ( $Ticket{OwnerID} eq $Param{UserID} ) {
3868                @SkipRecipients = [ $Param{UserID} ];
3869            }
3870
3871            # trigger notification event
3872            $Self->EventHandler(
3873                Event          => 'NotificationLockTimeout',
3874                SkipRecipients => \@SkipRecipients,
3875                Data           => {
3876                    TicketID              => $Param{TicketID},
3877                    CustomerMessageParams => {},
3878                },
3879                UserID => $Param{UserID},
3880            );
3881        }
3882    }
3883
3884    # trigger event
3885    $Self->EventHandler(
3886        Event => 'TicketLockUpdate',
3887        Data  => {
3888            TicketID => $Param{TicketID},
3889        },
3890        UserID => $Param{UserID},
3891    );
3892
3893    return 1;
3894}
3895
3896=head2 TicketArchiveFlagSet()
3897
3898to set the ticket archive flag
3899
3900    my $Success = $TicketObject->TicketArchiveFlagSet(
3901        ArchiveFlag => 'y',  # (y|n)
3902        TicketID    => 123,
3903        UserID      => 123,
3904    );
3905
3906Events:
3907    TicketArchiveFlagUpdate
3908
3909=cut
3910
3911sub TicketArchiveFlagSet {
3912    my ( $Self, %Param ) = @_;
3913
3914    # check needed stuff
3915    for my $Needed (qw(TicketID UserID ArchiveFlag)) {
3916        if ( !$Param{$Needed} ) {
3917            $Kernel::OM->Get('Kernel::System::Log')->Log(
3918                Priority => 'error',
3919                Message  => "Need $Needed!",
3920            );
3921            return;
3922        }
3923    }
3924
3925    # get config object
3926    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
3927
3928    # return if feature is not enabled
3929    return if !$ConfigObject->Get('Ticket::ArchiveSystem');
3930
3931    # check given archive flag
3932    if ( $Param{ArchiveFlag} ne 'y' && $Param{ArchiveFlag} ne 'n' ) {
3933        $Kernel::OM->Get('Kernel::System::Log')->Log(
3934            Priority => 'error',
3935            Message  => "ArchiveFlag is invalid '$Param{ArchiveFlag}'!",
3936        );
3937        return;
3938    }
3939
3940    # check if update is needed
3941    my %Ticket = $Self->TicketGet(
3942        %Param,
3943        DynamicFields => 0,
3944    );
3945
3946    # return if no update is needed
3947    return 1 if $Ticket{ArchiveFlag} && $Ticket{ArchiveFlag} eq $Param{ArchiveFlag};
3948
3949    # translate archive flag
3950    my $ArchiveFlag = $Param{ArchiveFlag} eq 'y' ? 1 : 0;
3951
3952    # set new archive flag
3953    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
3954        SQL => '
3955            UPDATE ticket
3956            SET archive_flag = ?, change_time = current_timestamp, change_by = ?
3957            WHERE id = ?',
3958        Bind => [ \$ArchiveFlag, \$Param{UserID}, \$Param{TicketID} ],
3959    );
3960
3961    # clear ticket cache
3962    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
3963
3964    # Remove seen flags from ticket and article and ticket watcher data if configured
3965    #   and if the ticket flag was just set.
3966    if ($ArchiveFlag) {
3967
3968        if ( $ConfigObject->Get('Ticket::ArchiveSystem::RemoveSeenFlags') ) {
3969            $Self->TicketFlagDelete(
3970                TicketID => $Param{TicketID},
3971                Key      => 'Seen',
3972                AllUsers => 1,
3973            );
3974
3975            my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
3976
3977            my @Articles = $ArticleObject->ArticleList( TicketID => $Param{TicketID} );
3978            for my $Article (@Articles) {
3979                $ArticleObject->ArticleFlagDelete(
3980                    TicketID  => $Param{TicketID},
3981                    ArticleID => $Article->{ArticleID},
3982                    Key       => 'Seen',
3983                    AllUsers  => 1,
3984                );
3985            }
3986        }
3987
3988        if (
3989            $ConfigObject->Get('Ticket::ArchiveSystem::RemoveTicketWatchers')
3990            && $ConfigObject->Get('Ticket::Watcher')
3991            )
3992        {
3993            $Self->TicketWatchUnsubscribe(
3994                TicketID => $Param{TicketID},
3995                AllUsers => 1,
3996                UserID   => $Param{UserID},
3997            );
3998        }
3999    }
4000
4001    # add history
4002    $Self->HistoryAdd(
4003        TicketID     => $Param{TicketID},
4004        CreateUserID => $Param{UserID},
4005        HistoryType  => 'ArchiveFlagUpdate',
4006        Name         => "\%\%$Param{ArchiveFlag}",
4007    );
4008
4009    # trigger event
4010    $Self->EventHandler(
4011        Event => 'TicketArchiveFlagUpdate',
4012        Data  => {
4013            TicketID => $Param{TicketID},
4014        },
4015        UserID => $Param{UserID},
4016    );
4017
4018    return 1;
4019}
4020
4021=head2 TicketArchiveFlagGet()
4022
4023check if a ticket is archived or not
4024
4025    if ( $TicketObject->TicketArchiveFlagGet( TicketID => 123 ) ) {
4026        print "Ticket is archived!\n";
4027    }
4028    else {
4029        print "Ticket is not archived!\n";
4030    }
4031
4032=cut
4033
4034sub TicketArchiveFlagGet {
4035    my ( $Self, %Param ) = @_;
4036
4037    # check needed stuff
4038    if ( !$Param{TicketID} ) {
4039        $Kernel::OM->Get('Kernel::System::Log')->Log(
4040            Priority => 'error',
4041            Message  => 'Need TicketID!'
4042        );
4043        return;
4044    }
4045
4046    my %Ticket = $Self->TicketGet(
4047        %Param,
4048        DynamicFields => 0,
4049    );
4050
4051    # check archive state
4052    return 1 if lc $Ticket{ArchiveFlag} eq 'y';
4053
4054    return;
4055}
4056
4057=head2 TicketStateSet()
4058
4059to set a ticket state
4060
4061    my $Success = $TicketObject->TicketStateSet(
4062        State     => 'open',
4063        TicketID  => 123,
4064        ArticleID => 123, #optional, for history
4065        UserID    => 123,
4066    );
4067
4068    my $Success = $TicketObject->TicketStateSet(
4069        StateID  => 3,
4070        TicketID => 123,
4071        UserID   => 123,
4072    );
4073
4074Optional attribute:
4075SendNoNotification, disable or enable agent and customer notification for this
4076action. Otherwise a notification will be sent to agent and customer.
4077
4078For example:
4079
4080        SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
4081
4082Events:
4083    TicketStateUpdate
4084
4085=cut
4086
4087sub TicketStateSet {
4088    my ( $Self, %Param ) = @_;
4089
4090    my %State;
4091    my $ArticleID = $Param{ArticleID} || '';
4092
4093    # check needed stuff
4094    for my $Needed (qw(TicketID UserID)) {
4095        if ( !$Param{$Needed} ) {
4096            $Kernel::OM->Get('Kernel::System::Log')->Log(
4097                Priority => 'error',
4098                Message  => "Need $Needed!"
4099            );
4100            return;
4101        }
4102    }
4103    if ( !$Param{State} && !$Param{StateID} ) {
4104        $Kernel::OM->Get('Kernel::System::Log')->Log(
4105            Priority => 'error',
4106            Message  => 'Need StateID or State!'
4107        );
4108        return;
4109    }
4110
4111    # get state object
4112    my $StateObject = $Kernel::OM->Get('Kernel::System::State');
4113
4114    # state id lookup
4115    if ( !$Param{StateID} ) {
4116        %State = $StateObject->StateGet( Name => $Param{State} );
4117    }
4118
4119    # state lookup
4120    if ( !$Param{State} ) {
4121        %State = $StateObject->StateGet( ID => $Param{StateID} );
4122    }
4123    if ( !%State ) {
4124        $Kernel::OM->Get('Kernel::System::Log')->Log(
4125            Priority => 'error',
4126            Message  => 'Need StateID or State!'
4127        );
4128        return;
4129    }
4130
4131    # check if update is needed
4132    my %Ticket = $Self->TicketGet(
4133        TicketID      => $Param{TicketID},
4134        DynamicFields => 0,
4135    );
4136    if ( $State{Name} eq $Ticket{State} ) {
4137
4138        # update is not needed
4139        return 1;
4140    }
4141
4142    # permission check
4143    my %StateList = $Self->StateList(%Param);
4144    if ( !$StateList{ $State{ID} } ) {
4145        $Kernel::OM->Get('Kernel::System::Log')->Log(
4146            Priority => 'notice',
4147            Message  => "Permission denied on TicketID: $Param{TicketID} / StateID: $State{ID}!",
4148        );
4149        return;
4150    }
4151
4152    # db update
4153    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
4154        SQL => 'UPDATE ticket SET ticket_state_id = ?, '
4155            . ' change_time = current_timestamp, change_by = ? WHERE id = ?',
4156        Bind => [ \$State{ID}, \$Param{UserID}, \$Param{TicketID} ],
4157    );
4158
4159    # clear ticket cache
4160    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
4161
4162    # add history
4163    $Self->HistoryAdd(
4164        TicketID     => $Param{TicketID},
4165        StateID      => $State{ID},
4166        ArticleID    => $ArticleID,
4167        QueueID      => $Ticket{QueueID},
4168        Name         => "\%\%$Ticket{State}\%\%$State{Name}\%\%",
4169        HistoryType  => 'StateUpdate',
4170        CreateUserID => $Param{UserID},
4171    );
4172
4173    # trigger event, OldTicketData is needed for escalation events
4174    $Self->EventHandler(
4175        Event => 'TicketStateUpdate',
4176        Data  => {
4177            TicketID      => $Param{TicketID},
4178            OldTicketData => \%Ticket,
4179        },
4180        UserID => $Param{UserID},
4181    );
4182
4183    return 1;
4184}
4185
4186=head2 TicketStateList()
4187
4188to get the state list for a ticket (depends on workflow, if configured)
4189
4190    my %States = $TicketObject->TicketStateList(
4191        TicketID => 123,
4192        UserID   => 123,
4193    );
4194
4195    my %States = $TicketObject->TicketStateList(
4196        TicketID       => 123,
4197        CustomerUserID => 'customer_user_id_123',
4198    );
4199
4200    my %States = $TicketObject->TicketStateList(
4201        QueueID => 123,
4202        UserID  => 123,
4203    );
4204
4205    my %States = $TicketObject->TicketStateList(
4206        TicketID => 123,
4207        Type     => 'open',
4208        UserID   => 123,
4209    );
4210
4211Returns:
4212
4213    %States = (
4214        1 => 'State A',
4215        2 => 'State B',
4216        3 => 'State C',
4217    );
4218
4219=cut
4220
4221sub TicketStateList {
4222    my ( $Self, %Param ) = @_;
4223
4224    my %States;
4225
4226    # check needed stuff
4227    if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
4228        $Kernel::OM->Get('Kernel::System::Log')->Log(
4229            Priority => 'error',
4230            Message  => 'Need UserID or CustomerUserID!'
4231        );
4232        return;
4233    }
4234
4235    # check needed stuff
4236    if ( !$Param{QueueID} && !$Param{TicketID} ) {
4237        $Kernel::OM->Get('Kernel::System::Log')->Log(
4238            Priority => 'error',
4239            Message  => 'Need QueueID, TicketID!'
4240        );
4241        return;
4242    }
4243
4244    # get config object
4245    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4246
4247    # get state object
4248    my $StateObject = $Kernel::OM->Get('Kernel::System::State');
4249
4250    # get states by type
4251    if ( $Param{Type} ) {
4252        %States = $StateObject->StateGetStatesByType(
4253            Type   => $Param{Type},
4254            Result => 'HASH',
4255        );
4256    }
4257    elsif ( $Param{Action} ) {
4258
4259        if (
4260            ref $ConfigObject->Get("Ticket::Frontend::$Param{Action}")->{StateType} ne
4261            'ARRAY'
4262            )
4263        {
4264            $Kernel::OM->Get('Kernel::System::Log')->Log(
4265                Priority => 'error',
4266                Message  => "Need config for Ticket::Frontend::$Param{Action}->StateType!"
4267            );
4268            return;
4269        }
4270
4271        my @StateType = @{ $ConfigObject->Get("Ticket::Frontend::$Param{Action}")->{StateType} };
4272        %States = $StateObject->StateGetStatesByType(
4273            StateType => \@StateType,
4274            Result    => 'HASH',
4275        );
4276    }
4277
4278    # get whole states list
4279    else {
4280        %States = $StateObject->StateList(
4281            UserID => $Param{UserID},
4282        );
4283    }
4284
4285    # workflow
4286    my $ACL = $Self->TicketAcl(
4287        %Param,
4288        ReturnType    => 'Ticket',
4289        ReturnSubType => 'State',
4290        Data          => \%States,
4291    );
4292
4293    return $Self->TicketAclData() if $ACL;
4294    return %States;
4295}
4296
4297=head2 OwnerCheck()
4298
4299to get the ticket owner
4300
4301    my ($OwnerID, $Owner) = $TicketObject->OwnerCheck(
4302        TicketID => 123,
4303    );
4304
4305or for access control
4306
4307    my $AccessOk = $TicketObject->OwnerCheck(
4308        TicketID => 123,
4309        OwnerID  => 321,
4310    );
4311
4312=cut
4313
4314sub OwnerCheck {
4315    my ( $Self, %Param ) = @_;
4316
4317    my $SQL = '';
4318
4319    # check needed stuff
4320    if ( !$Param{TicketID} ) {
4321        $Kernel::OM->Get('Kernel::System::Log')->Log(
4322            Priority => 'error',
4323            Message  => 'Need TicketID!'
4324        );
4325        return;
4326    }
4327
4328    # get database object
4329    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
4330
4331    # db query
4332    if ( $Param{OwnerID} ) {
4333
4334        # create cache key
4335        my $CacheKey = $Param{TicketID} . '::' . $Param{OwnerID};
4336
4337        # check cache
4338        if ( defined $Self->{OwnerCheck}->{$CacheKey} ) {
4339            return   if !$Self->{OwnerCheck}->{$CacheKey};
4340            return 1 if $Self->{OwnerCheck}->{$CacheKey};
4341        }
4342
4343        # check if user has access
4344        return if !$DBObject->Prepare(
4345            SQL => 'SELECT user_id FROM ticket WHERE '
4346                . ' id = ? AND (user_id = ? OR responsible_user_id = ?)',
4347            Bind => [ \$Param{TicketID}, \$Param{OwnerID}, \$Param{OwnerID}, ],
4348        );
4349        my $Access = 0;
4350        while ( my @Row = $DBObject->FetchrowArray() ) {
4351            $Access = 1;
4352        }
4353
4354        # fill cache
4355        $Self->{OwnerCheck}->{$CacheKey} = $Access;
4356        return   if !$Access;
4357        return 1 if $Access;
4358    }
4359
4360    # get config object
4361    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4362
4363    # search for owner_id and owner
4364    return if !$DBObject->Prepare(
4365        SQL => "SELECT st.user_id, su.$ConfigObject->{DatabaseUserTableUser} "
4366            . " FROM ticket st, $ConfigObject->{DatabaseUserTable} su "
4367            . " WHERE st.id = ? AND "
4368            . " st.user_id = su.$ConfigObject->{DatabaseUserTableUserID}",
4369        Bind => [ \$Param{TicketID}, ],
4370    );
4371
4372    while ( my @Row = $DBObject->FetchrowArray() ) {
4373        $Param{SearchUserID} = $Row[0];
4374        $Param{SearchUser}   = $Row[1];
4375    }
4376
4377    # return if no owner as been found
4378    return if !$Param{SearchUserID};
4379
4380    # return owner id and owner
4381    return $Param{SearchUserID}, $Param{SearchUser};
4382}
4383
4384=head2 TicketOwnerSet()
4385
4386to set the ticket owner (notification to the new owner will be sent)
4387
4388by using user id
4389
4390    my $Success = $TicketObject->TicketOwnerSet(
4391        TicketID  => 123,
4392        NewUserID => 555,
4393        UserID    => 123,
4394    );
4395
4396by using user login
4397
4398    my $Success = $TicketObject->TicketOwnerSet(
4399        TicketID => 123,
4400        NewUser  => 'some-user-login',
4401        UserID   => 123,
4402    );
4403
4404Return:
4405    1 = owner has been set
4406    2 = this owner is already set, no update needed
4407
4408Optional attribute:
4409SendNoNotification, disable or enable agent and customer notification for this
4410action. Otherwise a notification will be sent to agent and customer.
4411
4412For example:
4413
4414        SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
4415
4416Events:
4417    TicketOwnerUpdate
4418
4419=cut
4420
4421sub TicketOwnerSet {
4422    my ( $Self, %Param ) = @_;
4423
4424    # check needed stuff
4425    for my $Needed (qw(TicketID UserID)) {
4426        if ( !$Param{$Needed} ) {
4427            $Kernel::OM->Get('Kernel::System::Log')->Log(
4428                Priority => 'error',
4429                Message  => "Need $Needed!"
4430            );
4431            return;
4432        }
4433    }
4434    if ( !$Param{NewUserID} && !$Param{NewUser} ) {
4435        $Kernel::OM->Get('Kernel::System::Log')->Log(
4436            Priority => 'error',
4437            Message  => 'Need NewUserID or NewUser!'
4438        );
4439        return;
4440    }
4441
4442    # get user object
4443    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
4444
4445    # lookup if no NewUserID is given
4446    if ( !$Param{NewUserID} ) {
4447        $Param{NewUserID} = $UserObject->UserLookup(
4448            UserLogin => $Param{NewUser},
4449        );
4450    }
4451
4452    # lookup if no NewUser is given
4453    if ( !$Param{NewUser} ) {
4454        $Param{NewUser} = $UserObject->UserLookup(
4455            UserID => $Param{NewUserID},
4456        );
4457    }
4458
4459    # make sure the user exists
4460    if ( !$UserObject->UserLookup( UserID => $Param{NewUserID} ) ) {
4461        $Kernel::OM->Get('Kernel::System::Log')->Log(
4462            Priority => 'error',
4463            Message  => "User does not exist.",
4464        );
4465        return;
4466    }
4467
4468    # check if update is needed!
4469    my ( $OwnerID, $Owner ) = $Self->OwnerCheck( TicketID => $Param{TicketID} );
4470    if ( $OwnerID eq $Param{NewUserID} ) {
4471
4472        # update is "not" needed!
4473        return 2;
4474    }
4475
4476    # db update
4477    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
4478        SQL => 'UPDATE ticket SET '
4479            . ' user_id = ?, change_time = current_timestamp, change_by = ? WHERE id = ?',
4480        Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ],
4481    );
4482
4483    # clear ticket cache
4484    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
4485
4486    # add history
4487    $Self->HistoryAdd(
4488        TicketID     => $Param{TicketID},
4489        CreateUserID => $Param{UserID},
4490        HistoryType  => 'OwnerUpdate',
4491        Name         => "\%\%$Param{NewUser}\%\%$Param{NewUserID}",
4492    );
4493
4494    # send agent notify
4495    if ( !$Param{SendNoNotification} ) {
4496
4497        my @SkipRecipients;
4498        if ( $Param{UserID} eq $Param{NewUserID} ) {
4499            @SkipRecipients = [ $Param{UserID} ];
4500        }
4501
4502        # trigger notification event
4503        $Self->EventHandler(
4504            Event => 'NotificationOwnerUpdate',
4505            Data  => {
4506                TicketID              => $Param{TicketID},
4507                SkipRecipients        => \@SkipRecipients,
4508                CustomerMessageParams => {
4509                    %Param,
4510                    Body => $Param{Comment} || '',
4511                },
4512            },
4513            UserID => $Param{UserID},
4514        );
4515    }
4516
4517    # trigger event
4518    $Self->EventHandler(
4519        Event => 'TicketOwnerUpdate',
4520        Data  => {
4521            TicketID => $Param{TicketID},
4522        },
4523        UserID => $Param{UserID},
4524    );
4525
4526    return 1;
4527}
4528
4529=head2 TicketOwnerList()
4530
4531returns the owner in the past as array with hash ref of the owner data
4532(name, email, ...)
4533
4534    my @Owner = $TicketObject->TicketOwnerList(
4535        TicketID => 123,
4536    );
4537
4538Returns:
4539
4540    @Owner = (
4541        {
4542            UserFirstname => 'SomeName',
4543            UserLastname  => 'SomeName',
4544            UserEmail     => 'some@example.com',
4545            # custom attributes
4546        },
4547        {
4548            UserFirstname => 'SomeName',
4549            UserLastname  => 'SomeName',
4550            UserEmail     => 'some@example.com',
4551            # custom attributes
4552        },
4553    );
4554
4555=cut
4556
4557sub TicketOwnerList {
4558    my ( $Self, %Param ) = @_;
4559
4560    # check needed stuff
4561    if ( !$Param{TicketID} ) {
4562        $Kernel::OM->Get('Kernel::System::Log')->Log(
4563            Priority => 'error',
4564            Message  => "Need TicketID!"
4565        );
4566        return;
4567    }
4568
4569    # get database object
4570    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
4571
4572    # db query
4573    return if !$DBObject->Prepare(
4574        SQL => 'SELECT sh.owner_id FROM ticket_history sh, ticket_history_type ht WHERE '
4575            . ' sh.ticket_id = ? AND ht.name IN (\'OwnerUpdate\', \'NewTicket\') AND '
4576            . ' ht.id = sh.history_type_id ORDER BY sh.id',
4577        Bind => [ \$Param{TicketID} ],
4578    );
4579    my @UserID;
4580
4581    USER:
4582    while ( my @Row = $DBObject->FetchrowArray() ) {
4583        next USER if !$Row[0];
4584        next USER if $Row[0] eq 1;
4585        push @UserID, $Row[0];
4586    }
4587
4588    # get user object
4589    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
4590
4591    my @UserInfo;
4592    USER:
4593    for my $UserID (@UserID) {
4594
4595        my %User = $UserObject->GetUserData(
4596            UserID => $UserID,
4597            Cache  => 1,
4598            Valid  => 1,
4599        );
4600
4601        next USER if !%User;
4602
4603        push @UserInfo, \%User;
4604    }
4605
4606    return @UserInfo;
4607}
4608
4609=head2 TicketResponsibleSet()
4610
4611to set the ticket responsible (notification to the new responsible will be sent)
4612
4613by using user id
4614
4615    my $Success = $TicketObject->TicketResponsibleSet(
4616        TicketID  => 123,
4617        NewUserID => 555,
4618        UserID    => 213,
4619    );
4620
4621by using user login
4622
4623    my $Success = $TicketObject->TicketResponsibleSet(
4624        TicketID  => 123,
4625        NewUser   => 'some-user-login',
4626        UserID    => 213,
4627    );
4628
4629Return:
4630    1 = responsible has been set
4631    2 = this responsible is already set, no update needed
4632
4633Optional attribute:
4634SendNoNotification, disable or enable agent and customer notification for this
4635action. Otherwise a notification will be sent to agent and customer.
4636
4637For example:
4638
4639        SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
4640
4641Events:
4642    TicketResponsibleUpdate
4643
4644=cut
4645
4646sub TicketResponsibleSet {
4647    my ( $Self, %Param ) = @_;
4648
4649    # check needed stuff
4650    for my $Needed (qw(TicketID UserID)) {
4651        if ( !$Param{$Needed} ) {
4652            $Kernel::OM->Get('Kernel::System::Log')->Log(
4653                Priority => 'error',
4654                Message  => "Need $Needed!"
4655            );
4656            return;
4657        }
4658    }
4659    if ( !$Param{NewUserID} && !$Param{NewUser} ) {
4660        $Kernel::OM->Get('Kernel::System::Log')->Log(
4661            Priority => 'error',
4662            Message  => 'Need NewUserID or NewUser!'
4663        );
4664        return;
4665    }
4666
4667    # get user object
4668    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
4669
4670    # lookup if no NewUserID is given
4671    if ( !$Param{NewUserID} ) {
4672        $Param{NewUserID} = $UserObject->UserLookup( UserLogin => $Param{NewUser} );
4673    }
4674
4675    # lookup if no NewUser is given
4676    if ( !$Param{NewUser} ) {
4677        $Param{NewUser} = $UserObject->UserLookup( UserID => $Param{NewUserID} );
4678    }
4679
4680    # make sure the user exists
4681    if ( !$UserObject->UserLookup( UserID => $Param{NewUserID} ) ) {
4682        $Kernel::OM->Get('Kernel::System::Log')->Log(
4683            Priority => 'error',
4684            Message  => "User does not exist.",
4685        );
4686        return;
4687    }
4688
4689    # check if update is needed!
4690    my %Ticket = $Self->TicketGet(
4691        TicketID      => $Param{TicketID},
4692        UserID        => $Param{NewUserID},
4693        DynamicFields => 0,
4694    );
4695    if ( $Ticket{ResponsibleID} eq $Param{NewUserID} ) {
4696
4697        # update is "not" needed!
4698        return 2;
4699    }
4700
4701    # db update
4702    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
4703        SQL => 'UPDATE ticket SET responsible_user_id = ?, '
4704            . ' change_time = current_timestamp, change_by = ? '
4705            . ' WHERE id = ?',
4706        Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ],
4707    );
4708
4709    # clear ticket cache
4710    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
4711
4712    # add history
4713    $Self->HistoryAdd(
4714        TicketID     => $Param{TicketID},
4715        CreateUserID => $Param{UserID},
4716        HistoryType  => 'ResponsibleUpdate',
4717        Name         => "\%\%$Param{NewUser}\%\%$Param{NewUserID}",
4718    );
4719
4720    # send agent notify
4721    if ( !$Param{SendNoNotification} ) {
4722
4723        my @SkipRecipients;
4724        if ( $Param{UserID} eq $Param{NewUserID} ) {
4725            @SkipRecipients = [ $Param{UserID} ];
4726        }
4727
4728        # trigger notification event
4729        $Self->EventHandler(
4730            Event => 'NotificationResponsibleUpdate',
4731            Data  => {
4732                TicketID              => $Param{TicketID},
4733                SkipRecipients        => \@SkipRecipients,
4734                CustomerMessageParams => \%Param,
4735            },
4736            UserID => $Param{UserID},
4737        );
4738    }
4739
4740    # trigger event
4741    $Self->EventHandler(
4742        Event => 'TicketResponsibleUpdate',
4743        Data  => {
4744            TicketID => $Param{TicketID},
4745        },
4746        UserID => $Param{UserID},
4747    );
4748
4749    return 1;
4750}
4751
4752=head2 TicketResponsibleList()
4753
4754returns the responsible in the past as array with hash ref of the owner data
4755(name, email, ...)
4756
4757    my @Responsible = $TicketObject->TicketResponsibleList(
4758        TicketID => 123,
4759    );
4760
4761Returns:
4762
4763    @Responsible = (
4764        {
4765            UserFirstname => 'SomeName',
4766            UserLastname  => 'SomeName',
4767            UserEmail     => 'some@example.com',
4768            # custom attributes
4769        },
4770        {
4771            UserFirstname => 'SomeName',
4772            UserLastname  => 'SomeName',
4773            UserEmail     => 'some@example.com',
4774            # custom attributes
4775        },
4776    );
4777
4778=cut
4779
4780sub TicketResponsibleList {
4781    my ( $Self, %Param ) = @_;
4782
4783    # check needed stuff
4784    if ( !$Param{TicketID} ) {
4785        $Kernel::OM->Get('Kernel::System::Log')->Log(
4786            Priority => 'error',
4787            Message  => "Need TicketID!"
4788        );
4789        return;
4790    }
4791
4792    # get database object
4793    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
4794
4795    # db query
4796    my @User;
4797    my $LastResponsible = 1;
4798    return if !$DBObject->Prepare(
4799        SQL => 'SELECT sh.name, ht.name, sh.create_by FROM '
4800            . ' ticket_history sh, ticket_history_type ht WHERE '
4801            . ' sh.ticket_id = ? AND '
4802            . ' ht.name IN (\'ResponsibleUpdate\', \'NewTicket\') AND '
4803            . ' ht.id = sh.history_type_id ORDER BY sh.id',
4804        Bind => [ \$Param{TicketID} ],
4805    );
4806
4807    while ( my @Row = $DBObject->FetchrowArray() ) {
4808
4809        # store result
4810        if ( $Row[1] eq 'NewTicket' && $Row[2] ne '1' && $LastResponsible ne $Row[2] ) {
4811            $LastResponsible = $Row[2];
4812            push @User, $Row[2];
4813        }
4814        elsif ( $Row[1] eq 'ResponsibleUpdate' ) {
4815            if (
4816                $Row[0] =~ /^New Responsible is '(.+?)' \(ID=(.+?)\)/
4817                || $Row[0] =~ /^\%\%(.+?)\%\%(.+?)$/
4818                )
4819            {
4820                $LastResponsible = $2;
4821                push @User, $2;
4822            }
4823        }
4824    }
4825
4826    # get user object
4827    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
4828
4829    my @UserInfo;
4830    for my $SingleUser (@User) {
4831
4832        my %User = $UserObject->GetUserData(
4833            UserID => $SingleUser,
4834            Cache  => 1
4835        );
4836        push @UserInfo, \%User;
4837    }
4838
4839    return @UserInfo;
4840}
4841
4842=head2 TicketInvolvedAgentsList()
4843
4844returns an array with hash ref of agents which have been involved with a ticket.
4845It is guaranteed that no agent is returned twice.
4846
4847    my @InvolvedAgents = $TicketObject->TicketInvolvedAgentsList(
4848        TicketID => 123,
4849    );
4850
4851Returns:
4852
4853    @InvolvedAgents = (
4854        {
4855            UserFirstname => 'SomeName',
4856            UserLastname  => 'SomeName',
4857            UserEmail     => 'some@example.com',
4858            # custom attributes
4859        },
4860        {
4861            UserFirstname => 'AnotherName',
4862            UserLastname  => 'AnotherName',
4863            UserEmail     => 'another@example.com',
4864            # custom attributes
4865        },
4866    );
4867
4868=cut
4869
4870sub TicketInvolvedAgentsList {
4871    my ( $Self, %Param ) = @_;
4872
4873    # check needed stuff
4874    if ( !$Param{TicketID} ) {
4875        $Kernel::OM->Get('Kernel::System::Log')->Log(
4876            Priority => 'error',
4877            Message  => 'Need TicketID!'
4878        );
4879        return;
4880    }
4881
4882    # get database object
4883    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
4884
4885    # db query, only entries with a known history_id are retrieved
4886    my @User;
4887    my %UsedOwner;
4888    return if !$DBObject->Prepare(
4889        SQL => ''
4890            . 'SELECT sh.create_by'
4891            . ' FROM ticket_history sh, ticket_history_type ht'
4892            . ' WHERE sh.ticket_id = ?'
4893            . ' AND ht.id = sh.history_type_id'
4894            . ' ORDER BY sh.id',
4895        Bind => [ \$Param{TicketID} ],
4896    );
4897
4898    while ( my @Row = $DBObject->FetchrowArray() ) {
4899
4900        # store result, skip the
4901        if ( $Row[0] ne 1 && !$UsedOwner{ $Row[0] } ) {
4902            $UsedOwner{ $Row[0] } = $Row[0];
4903            push @User, $Row[0];
4904        }
4905    }
4906
4907    # get user object
4908    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
4909
4910    # collect agent information
4911    my @UserInfo;
4912    USER:
4913    for my $SingleUser (@User) {
4914
4915        my %User = $UserObject->GetUserData(
4916            UserID => $SingleUser,
4917            Valid  => 1,
4918            Cache  => 1,
4919        );
4920
4921        next USER if !%User;
4922
4923        push @UserInfo, \%User;
4924    }
4925
4926    return @UserInfo;
4927}
4928
4929=head2 TicketPrioritySet()
4930
4931to set the ticket priority
4932
4933    my $Success = $TicketObject->TicketPrioritySet(
4934        TicketID => 123,
4935        Priority => 'low',
4936        UserID   => 213,
4937    );
4938
4939    my $Success = $TicketObject->TicketPrioritySet(
4940        TicketID   => 123,
4941        PriorityID => 2,
4942        UserID     => 213,
4943    );
4944
4945Events:
4946    TicketPriorityUpdate
4947
4948=cut
4949
4950sub TicketPrioritySet {
4951    my ( $Self, %Param ) = @_;
4952
4953    # get priority object
4954    my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority');
4955
4956    # lookup!
4957    if ( !$Param{PriorityID} && $Param{Priority} ) {
4958        $Param{PriorityID} = $PriorityObject->PriorityLookup(
4959            Priority => $Param{Priority},
4960        );
4961    }
4962    if ( $Param{PriorityID} && !$Param{Priority} ) {
4963        $Param{Priority} = $PriorityObject->PriorityLookup(
4964            PriorityID => $Param{PriorityID},
4965        );
4966    }
4967
4968    # check needed stuff
4969    for my $Needed (qw(TicketID UserID PriorityID Priority)) {
4970        if ( !$Param{$Needed} ) {
4971            $Kernel::OM->Get('Kernel::System::Log')->Log(
4972                Priority => 'error',
4973                Message  => "Need $Needed!"
4974            );
4975            return;
4976        }
4977    }
4978    my %Ticket = $Self->TicketGet(
4979        %Param,
4980        DynamicFields => 0,
4981    );
4982
4983    # check if update is needed
4984    if ( $Ticket{Priority} eq $Param{Priority} ) {
4985
4986        # update not needed
4987        return 1;
4988    }
4989
4990    # permission check
4991    my %PriorityList = $Self->PriorityList(%Param);
4992    if ( !$PriorityList{ $Param{PriorityID} } ) {
4993        $Kernel::OM->Get('Kernel::System::Log')->Log(
4994            Priority => 'notice',
4995            Message  => "Permission denied on TicketID: $Param{TicketID}!",
4996        );
4997        return;
4998    }
4999
5000    # db update
5001    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
5002        SQL => 'UPDATE ticket SET ticket_priority_id = ?, '
5003            . ' change_time = current_timestamp, change_by = ?'
5004            . ' WHERE id = ?',
5005        Bind => [ \$Param{PriorityID}, \$Param{UserID}, \$Param{TicketID} ],
5006    );
5007
5008    # clear ticket cache
5009    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
5010
5011    # add history
5012    $Self->HistoryAdd(
5013        TicketID     => $Param{TicketID},
5014        QueueID      => $Ticket{QueueID},
5015        CreateUserID => $Param{UserID},
5016        HistoryType  => 'PriorityUpdate',
5017        Name         => "\%\%$Ticket{Priority}\%\%$Ticket{PriorityID}"
5018            . "\%\%$Param{Priority}\%\%$Param{PriorityID}",
5019    );
5020
5021    # trigger event
5022    $Self->EventHandler(
5023        Event => 'TicketPriorityUpdate',
5024        Data  => {
5025            TicketID => $Param{TicketID},
5026        },
5027        UserID => $Param{UserID},
5028    );
5029
5030    return 1;
5031}
5032
5033=head2 TicketPriorityList()
5034
5035to get the priority list for a ticket (depends on workflow, if configured)
5036
5037    my %Priorities = $TicketObject->TicketPriorityList(
5038        TicketID => 123,
5039        UserID   => 123,
5040    );
5041
5042    my %Priorities = $TicketObject->TicketPriorityList(
5043        TicketID       => 123,
5044        CustomerUserID => 'customer_user_id_123',
5045    );
5046
5047    my %Priorities = $TicketObject->TicketPriorityList(
5048        QueueID => 123,
5049        UserID  => 123,
5050    );
5051
5052Returns:
5053
5054    %Priorities = (
5055        1 => 'Priority A',
5056        2 => 'Priority B',
5057        3 => 'Priority C',
5058    );
5059
5060=cut
5061
5062sub TicketPriorityList {
5063    my ( $Self, %Param ) = @_;
5064
5065    # check needed stuff
5066    if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
5067        $Kernel::OM->Get('Kernel::System::Log')->Log(
5068            Priority => 'error',
5069            Message  => 'Need UserID or CustomerUserID!'
5070        );
5071        return;
5072    }
5073
5074    my %Data = $Kernel::OM->Get('Kernel::System::Priority')->PriorityList(%Param);
5075
5076    # workflow
5077    my $ACL = $Self->TicketAcl(
5078        %Param,
5079        ReturnType    => 'Ticket',
5080        ReturnSubType => 'Priority',
5081        Data          => \%Data,
5082    );
5083
5084    return $Self->TicketAclData() if $ACL;
5085    return %Data;
5086}
5087
5088=head2 HistoryTicketStatusGet()
5089
5090get a hash with ticket id as key and a hash ref (result of HistoryTicketGet)
5091of all affected tickets in this time area.
5092
5093    my %Tickets = $TicketObject->HistoryTicketStatusGet(
5094        StartDay   => 12,
5095        StartMonth => 1,
5096        StartYear  => 2006,
5097        StopDay    => 18,
5098        StopMonth  => 1,
5099        StopYear   => 2006,
5100        Force      => 0,
5101    );
5102
5103=cut
5104
5105sub HistoryTicketStatusGet {
5106    my ( $Self, %Param ) = @_;
5107
5108    # check needed stuff
5109    for my $Needed (qw(StopYear StopMonth StopDay StartYear StartMonth StartDay)) {
5110        if ( !$Param{$Needed} ) {
5111            $Kernel::OM->Get('Kernel::System::Log')->Log(
5112                Priority => 'error',
5113                Message  => "Need $Needed!"
5114            );
5115            return;
5116        }
5117    }
5118
5119    # format month and day params
5120    for my $DateParameter (qw(StopMonth StopDay StartMonth StartDay)) {
5121        $Param{$DateParameter} = sprintf( "%02d", $Param{$DateParameter} );
5122    }
5123
5124    my $SQLExt = '';
5125    for my $HistoryTypeData (
5126        qw(NewTicket FollowUp OwnerUpdate PriorityUpdate CustomerUpdate StateUpdate
5127        PhoneCallCustomer Forward Bounce SendAnswer EmailCustomer
5128        PhoneCallAgent WebRequestCustomer TicketDynamicFieldUpdate)
5129        )
5130    {
5131        my $ID = $Self->HistoryTypeLookup( Type => $HistoryTypeData );
5132        if ( !$SQLExt ) {
5133            $SQLExt = "AND history_type_id IN ($ID";
5134        }
5135        else {
5136            $SQLExt .= ",$ID";
5137        }
5138    }
5139
5140    if ($SQLExt) {
5141        $SQLExt .= ')';
5142    }
5143
5144    # assemble stop date/time string for database comparison
5145    my $StopDateTimeObject = $Kernel::OM->Create(
5146        'Kernel::System::DateTime',
5147        ObjectParams => {
5148            String => "$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} 00:00:00",
5149        }
5150    );
5151    $StopDateTimeObject->Add( Hours => 24 );
5152    my $StopDateTimeString = $StopDateTimeObject->Format( Format => '%Y-%m-%d 00:00:00' );
5153
5154    # get database object
5155    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
5156
5157    return if !$DBObject->Prepare(
5158        SQL => "
5159            SELECT DISTINCT(th.ticket_id), th.create_time
5160            FROM ticket_history th
5161            WHERE th.create_time <= '$StopDateTimeString'
5162                AND th.create_time >= '$Param{StartYear}-$Param{StartMonth}-$Param{StartDay} 00:00:01'
5163                $SQLExt
5164            ORDER BY th.create_time DESC",
5165        Limit => 150000,
5166    );
5167
5168    my %Ticket;
5169    while ( my @Row = $DBObject->FetchrowArray() ) {
5170        $Ticket{ $Row[0] } = 1;
5171    }
5172
5173    for my $TicketID ( sort keys %Ticket ) {
5174
5175        my %TicketData = $Self->HistoryTicketGet(
5176            TicketID  => $TicketID,
5177            StopYear  => $Param{StopYear},
5178            StopMonth => $Param{StopMonth},
5179            StopDay   => $Param{StopDay},
5180            Force     => $Param{Force} || 0,
5181        );
5182
5183        if (%TicketData) {
5184            $Ticket{$TicketID} = \%TicketData;
5185        }
5186        else {
5187            $Ticket{$TicketID} = {};
5188        }
5189    }
5190
5191    return %Ticket;
5192}
5193
5194=head2 HistoryTicketGet()
5195
5196returns a hash of some of the ticket data
5197calculated based on ticket history info at the given date.
5198
5199    my %HistoryData = $TicketObject->HistoryTicketGet(
5200        StopYear   => 2003,
5201        StopMonth  => 12,
5202        StopDay    => 24,
5203        StopHour   => 10, (optional, default 23)
5204        StopMinute => 0,  (optional, default 59)
5205        StopSecond => 0,  (optional, default 59)
5206        TicketID   => 123,
5207        Force      => 0,     # 1: don't use cache
5208    );
5209
5210returns
5211
5212    TicketNumber
5213    TicketID
5214    Type
5215    TypeID
5216    Queue
5217    QueueID
5218    Priority
5219    PriorityID
5220    State
5221    StateID
5222    Owner
5223    OwnerID
5224    CreateUserID
5225    CreateTime (timestamp)
5226    CreateOwnerID
5227    CreatePriority
5228    CreatePriorityID
5229    CreateState
5230    CreateStateID
5231    CreateQueue
5232    CreateQueueID
5233    LockFirst (timestamp)
5234    LockLast (timestamp)
5235    UnlockFirst (timestamp)
5236    UnlockLast (timestamp)
5237
5238=cut
5239
5240sub HistoryTicketGet {
5241    my ( $Self, %Param ) = @_;
5242
5243    # check needed stuff
5244    for my $Needed (qw(TicketID StopYear StopMonth StopDay)) {
5245        if ( !$Param{$Needed} ) {
5246            $Kernel::OM->Get('Kernel::System::Log')->Log(
5247                Priority => 'error',
5248                Message  => "Need $Needed!"
5249            );
5250            return;
5251        }
5252    }
5253    $Param{StopHour}   = defined $Param{StopHour}   ? $Param{StopHour}   : '23';
5254    $Param{StopMinute} = defined $Param{StopMinute} ? $Param{StopMinute} : '59';
5255    $Param{StopSecond} = defined $Param{StopSecond} ? $Param{StopSecond} : '59';
5256
5257    # format month and day params
5258    for my $DateParameter (qw(StopMonth StopDay)) {
5259        $Param{$DateParameter} = sprintf( "%02d", $Param{$DateParameter} );
5260    }
5261
5262    my $CacheKey = 'HistoryTicketGet::'
5263        . join( '::', map { ( $_ || 0 ) . "::$Param{$_}" } sort keys %Param );
5264
5265    my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get(
5266        Type => $Self->{CacheType},
5267        Key  => $CacheKey,
5268    );
5269    if ( ref $Cached eq 'HASH' && !$Param{Force} ) {
5270        return %{$Cached};
5271    }
5272
5273    my $Time
5274        = "$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} $Param{StopHour}:$Param{StopMinute}:$Param{StopSecond}";
5275
5276    # get database object
5277    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
5278
5279    return if !$DBObject->Prepare(
5280        SQL => '
5281            SELECT th.name, tht.name, th.create_time, th.create_by, th.ticket_id,
5282                th.article_id, th.queue_id, th.state_id, th.priority_id, th.owner_id, th.type_id
5283            FROM ticket_history th, ticket_history_type tht
5284            WHERE th.history_type_id = tht.id
5285                AND th.ticket_id = ?
5286                AND th.create_time <= ?
5287            ORDER BY th.create_time, th.id ASC',
5288        Bind  => [ \$Param{TicketID}, \$Time ],
5289        Limit => 3000,
5290    );
5291
5292    my %Ticket;
5293    while ( my @Row = $DBObject->FetchrowArray() ) {
5294
5295        if ( $Row[1] eq 'NewTicket' ) {
5296            if (
5297                $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/
5298                || $Row[0] =~ /Ticket=\[(.+?)\],.+?Q\=(.+?);P\=(.+?);S\=(.+?)/
5299                )
5300            {
5301                $Ticket{TicketNumber}   = $1;
5302                $Ticket{Queue}          = $2;
5303                $Ticket{CreateQueue}    = $2;
5304                $Ticket{Priority}       = $3;
5305                $Ticket{CreatePriority} = $3;
5306                $Ticket{State}          = $4;
5307                $Ticket{CreateState}    = $4;
5308                $Ticket{TicketID}       = $Row[4];
5309                $Ticket{Owner}          = 'root';
5310                $Ticket{CreateUserID}   = $Row[3];
5311                $Ticket{CreateTime}     = $Row[2];
5312            }
5313            else {
5314
5315                # COMPAT: compat to 1.1
5316                # NewTicket
5317                $Ticket{TicketVersion} = '1.1';
5318                $Ticket{TicketID}      = $Row[4];
5319                $Ticket{CreateUserID}  = $Row[3];
5320                $Ticket{CreateTime}    = $Row[2];
5321            }
5322            $Ticket{CreateOwnerID}    = $Row[9] || '';
5323            $Ticket{CreatePriorityID} = $Row[8] || '';
5324            $Ticket{CreateStateID}    = $Row[7] || '';
5325            $Ticket{CreateQueueID}    = $Row[6] || '';
5326        }
5327
5328        # COMPAT: compat to 1.1
5329        elsif ( $Row[1] eq 'PhoneCallCustomer' ) {
5330            $Ticket{TicketVersion} = '1.1';
5331            $Ticket{TicketID}      = $Row[4];
5332            $Ticket{CreateUserID}  = $Row[3];
5333            $Ticket{CreateTime}    = $Row[2];
5334        }
5335        elsif ( $Row[1] eq 'Move' ) {
5336            if (
5337                $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/
5338                || $Row[0] =~ /^Ticket moved to Queue '(.+?)'/
5339                )
5340            {
5341                $Ticket{Queue} = $1;
5342            }
5343        }
5344        elsif (
5345            $Row[1] eq 'StateUpdate'
5346            || $Row[1] eq 'Close successful'
5347            || $Row[1] eq 'Close unsuccessful'
5348            || $Row[1] eq 'Open'
5349            || $Row[1] eq 'Misc'
5350            )
5351        {
5352            if (
5353                $Row[0]    =~ /^\%\%(.+?)\%\%(.+?)(\%\%|)$/
5354                || $Row[0] =~ /^Old: '(.+?)' New: '(.+?)'/
5355                || $Row[0] =~ /^Changed Ticket State from '(.+?)' to '(.+?)'/
5356                )
5357            {
5358                $Ticket{State}     = $2;
5359                $Ticket{StateTime} = $Row[2];
5360            }
5361        }
5362        elsif ( $Row[1] eq 'TicketFreeTextUpdate' ) {
5363            if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/ ) {
5364                $Ticket{ 'Ticket' . $1 } = $2;
5365                $Ticket{ 'Ticket' . $3 } = $4;
5366                $Ticket{$1}              = $2;
5367                $Ticket{$3}              = $4;
5368            }
5369        }
5370        elsif ( $Row[1] eq 'TicketDynamicFieldUpdate' ) {
5371
5372            # take care about different values between 3.3 and 4
5373            # 3.x: %%FieldName%%test%%Value%%TestValue1
5374            # 4.x: %%FieldName%%test%%Value%%TestValue1%%OldValue%%OldTestValue1
5375            if ( $Row[0] =~ /^\%\%FieldName\%\%(.+?)\%\%Value\%\%(.*?)(?:\%\%|$)/ ) {
5376
5377                my $FieldName = $1;
5378                my $Value     = $2 || '';
5379                $Ticket{$FieldName} = $Value;
5380
5381                # Backward compatibility for TicketFreeText and TicketFreeTime
5382                if ( $FieldName =~ /^Ticket(Free(?:Text|Key)(?:[?:1[0-6]|[1-9]))$/ ) {
5383
5384                    # Remove the leading Ticket on field name
5385                    my $FreeFieldName = $1;
5386                    $Ticket{$FreeFieldName} = $Value;
5387                }
5388            }
5389        }
5390        elsif ( $Row[1] eq 'PriorityUpdate' ) {
5391            if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) {
5392                $Ticket{Priority} = $3;
5393            }
5394        }
5395        elsif ( $Row[1] eq 'OwnerUpdate' ) {
5396            if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)/ || $Row[0] =~ /^New Owner is '(.+?)'/ ) {
5397                $Ticket{Owner} = $1;
5398            }
5399        }
5400        elsif ( $Row[1] eq 'Lock' ) {
5401            if ( !$Ticket{LockFirst} ) {
5402                $Ticket{LockFirst} = $Row[2];
5403            }
5404            $Ticket{LockLast} = $Row[2];
5405        }
5406        elsif ( $Row[1] eq 'Unlock' ) {
5407            if ( !$Ticket{UnlockFirst} ) {
5408                $Ticket{UnlockFirst} = $Row[2];
5409            }
5410            $Ticket{UnlockLast} = $Row[2];
5411        }
5412
5413        # get default options
5414        $Ticket{TypeID}     = $Row[10] || '';
5415        $Ticket{OwnerID}    = $Row[9]  || '';
5416        $Ticket{PriorityID} = $Row[8]  || '';
5417        $Ticket{StateID}    = $Row[7]  || '';
5418        $Ticket{QueueID}    = $Row[6]  || '';
5419    }
5420    if ( !%Ticket ) {
5421        $Kernel::OM->Get('Kernel::System::Log')->Log(
5422            Priority => 'notice',
5423            Message  => "No such TicketID in ticket history till "
5424                . "'$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} $Param{StopHour}:$Param{StopMinute}:$Param{StopSecond}' ($Param{TicketID})!",
5425        );
5426        return;
5427    }
5428
5429    # update old ticket info
5430    my %CurrentTicketData = $Self->TicketGet(
5431        TicketID      => $Ticket{TicketID},
5432        DynamicFields => 0,
5433    );
5434    for my $TicketAttribute (qw(State Priority Queue TicketNumber)) {
5435        if ( !$Ticket{$TicketAttribute} ) {
5436            $Ticket{$TicketAttribute} = $CurrentTicketData{$TicketAttribute};
5437        }
5438        if ( !$Ticket{"Create$TicketAttribute"} ) {
5439            $Ticket{"Create$TicketAttribute"} = $CurrentTicketData{$TicketAttribute};
5440        }
5441    }
5442
5443    # create datetime object
5444    my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
5445
5446    # check if we should cache this ticket data
5447    my $DateTimeValues = $DateTimeObject->Get();
5448
5449    # if the request is for the last month or older, cache it
5450    if ( int $DateTimeValues->{Year} . int $DateTimeValues->{Month} > int $Param{StopYear} . int $Param{StopMonth} ) {
5451        $Kernel::OM->Get('Kernel::System::Cache')->Set(
5452            Type  => $Self->{CacheType},
5453            TTL   => $Self->{CacheTTL},
5454            Key   => $CacheKey,
5455            Value => \%Ticket,
5456        );
5457    }
5458
5459    return %Ticket;
5460}
5461
5462=head2 HistoryTypeLookup()
5463
5464returns the id of the requested history type.
5465
5466    my $ID = $TicketObject->HistoryTypeLookup( Type => 'Move' );
5467
5468=cut
5469
5470sub HistoryTypeLookup {
5471    my ( $Self, %Param ) = @_;
5472
5473    # check needed stuff
5474    if ( !$Param{Type} ) {
5475        $Kernel::OM->Get('Kernel::System::Log')->Log(
5476            Priority => 'error',
5477            Message  => 'Need Type!'
5478        );
5479        return;
5480    }
5481
5482    # check if we ask the same request?
5483    my $CacheKey = 'Ticket::History::HistoryTypeLookup::' . $Param{Type};
5484    my $Cached   = $Kernel::OM->Get('Kernel::System::Cache')->Get(
5485        Type => $Self->{CacheType},
5486        Key  => $CacheKey,
5487    );
5488
5489    if ($Cached) {
5490        return $Cached;
5491    }
5492
5493    # get database object
5494    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
5495
5496    # db query
5497    return if !$DBObject->Prepare(
5498        SQL  => 'SELECT id FROM ticket_history_type WHERE name = ?',
5499        Bind => [ \$Param{Type} ],
5500    );
5501
5502    my $HistoryTypeID;
5503    while ( my @Row = $DBObject->FetchrowArray() ) {
5504        $HistoryTypeID = $Row[0];
5505    }
5506
5507    # check if data exists
5508    if ( !$HistoryTypeID ) {
5509        $Kernel::OM->Get('Kernel::System::Log')->Log(
5510            Priority => 'error',
5511            Message  => "No TypeID for $Param{Type} found!",
5512        );
5513        return;
5514    }
5515
5516    # set cache
5517    $Kernel::OM->Get('Kernel::System::Cache')->Set(
5518        Type           => $Self->{CacheType},
5519        TTL            => $Self->{CacheTTL},
5520        Key            => $CacheKey,
5521        Value          => $HistoryTypeID,
5522        CacheInMemory  => 1,
5523        CacheInBackend => 0,
5524    );
5525
5526    return $HistoryTypeID;
5527}
5528
5529=head2 HistoryAdd()
5530
5531add a history entry to an ticket
5532
5533    my $Success = $TicketObject->HistoryAdd(
5534        Name         => 'Some Comment',
5535        HistoryType  => 'Move', # see system tables
5536        TicketID     => 123,
5537        ArticleID    => 1234, # not required!
5538        QueueID      => 123, # not required!
5539        TypeID       => 123, # not required!
5540        CreateUserID => 123,
5541    );
5542
5543Events:
5544    HistoryAdd
5545
5546=cut
5547
5548sub HistoryAdd {
5549    my ( $Self, %Param ) = @_;
5550
5551    # check needed stuff
5552    if ( !$Param{Name} ) {
5553        $Kernel::OM->Get('Kernel::System::Log')->Log(
5554            Priority => 'error',
5555            Message  => 'Need Name!'
5556        );
5557        return;
5558    }
5559
5560    # lookup!
5561    if ( !$Param{HistoryTypeID} && $Param{HistoryType} ) {
5562        $Param{HistoryTypeID} = $Self->HistoryTypeLookup( Type => $Param{HistoryType} );
5563    }
5564
5565    # check needed stuff
5566    for my $Needed (qw(TicketID CreateUserID HistoryTypeID)) {
5567        if ( !$Param{$Needed} ) {
5568            $Kernel::OM->Get('Kernel::System::Log')->Log(
5569                Priority => 'error',
5570                Message  => "Need $Needed!"
5571            );
5572            return;
5573        }
5574    }
5575
5576    my %Ticket;
5577    if ( !$Param{QueueID} || !$Param{TypeID} || !$Param{OwnerID} || !$Param{PriorityID} || !$Param{StateID} ) {
5578        %Ticket = $Self->TicketGet(
5579            %Param,
5580            DynamicFields => 0,
5581        );
5582    }
5583
5584    if ( !$Param{QueueID} ) {
5585        $Param{QueueID} = $Ticket{QueueID};
5586    }
5587    if ( !$Param{TypeID} ) {
5588        $Param{TypeID} = $Ticket{TypeID};
5589    }
5590    if ( !$Param{OwnerID} ) {
5591        $Param{OwnerID} = $Ticket{OwnerID};
5592    }
5593    if ( !$Param{PriorityID} ) {
5594        $Param{PriorityID} = $Ticket{PriorityID};
5595    }
5596    if ( !$Param{StateID} ) {
5597        $Param{StateID} = $Ticket{StateID};
5598    }
5599
5600    # limit name to 200 chars
5601    if ( $Param{Name} ) {
5602        $Param{Name} = substr( $Param{Name}, 0, 200 );
5603    }
5604
5605    # db quote
5606    if ( !$Param{ArticleID} ) {
5607        $Param{ArticleID} = undef;
5608    }
5609
5610    # db insert
5611    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
5612        SQL => 'INSERT INTO ticket_history '
5613            . ' (name, history_type_id, ticket_id, article_id, queue_id, owner_id, '
5614            . ' priority_id, state_id, type_id, '
5615            . ' create_time, create_by, change_time, change_by) '
5616            . 'VALUES '
5617            . '(?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)',
5618        Bind => [
5619            \$Param{Name},    \$Param{HistoryTypeID}, \$Param{TicketID},   \$Param{ArticleID},
5620            \$Param{QueueID}, \$Param{OwnerID},       \$Param{PriorityID}, \$Param{StateID},
5621            \$Param{TypeID},  \$Param{CreateUserID},  \$Param{CreateUserID},
5622        ],
5623    );
5624
5625    # Prevent infinite loops for notifications base on 'HistoryAdd' event
5626    # see bug#13002
5627    if ( $Param{HistoryType} ne 'SendAgentNotification' ) {
5628
5629        # trigger event
5630        $Self->EventHandler(
5631            Event => 'HistoryAdd',
5632            Data  => {
5633                TicketID => $Param{TicketID},
5634            },
5635            UserID => $Param{CreateUserID},
5636        );
5637    }
5638
5639    return 1;
5640}
5641
5642=head2 HistoryGet()
5643
5644get ticket history as array with hashes
5645(TicketID, ArticleID, Name, CreateBy, CreateTime, HistoryType, QueueID,
5646OwnerID, PriorityID, StateID, HistoryTypeID and TypeID)
5647
5648    my @HistoryLines = $TicketObject->HistoryGet(
5649        TicketID => 123,
5650        UserID   => 123,
5651    );
5652
5653=cut
5654
5655sub HistoryGet {
5656    my ( $Self, %Param ) = @_;
5657
5658    my @Lines;
5659
5660    # check needed stuff
5661    for my $Needed (qw(TicketID UserID)) {
5662        if ( !$Param{$Needed} ) {
5663            $Kernel::OM->Get('Kernel::System::Log')->Log(
5664                Priority => 'error',
5665                Message  => "Need $Needed!"
5666            );
5667            return;
5668        }
5669    }
5670
5671    # get database object
5672    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
5673
5674    return if !$DBObject->Prepare(
5675        SQL => 'SELECT sh.name, sh.article_id, sh.create_time, sh.create_by, ht.name, '
5676            . ' sh.queue_id, sh.owner_id, sh.priority_id, sh.state_id, sh.history_type_id, sh.type_id '
5677            . ' FROM ticket_history sh, ticket_history_type ht WHERE '
5678            . ' sh.ticket_id = ? AND ht.id = sh.history_type_id'
5679            . ' ORDER BY sh.create_time, sh.id',
5680        Bind => [ \$Param{TicketID} ],
5681    );
5682
5683    while ( my @Row = $DBObject->FetchrowArray() ) {
5684        my %Data;
5685        $Data{TicketID}      = $Param{TicketID};
5686        $Data{ArticleID}     = $Row[1] || 0;
5687        $Data{Name}          = $Row[0];
5688        $Data{CreateBy}      = $Row[3];
5689        $Data{CreateTime}    = $Row[2];
5690        $Data{HistoryType}   = $Row[4];
5691        $Data{QueueID}       = $Row[5];
5692        $Data{OwnerID}       = $Row[6];
5693        $Data{PriorityID}    = $Row[7];
5694        $Data{StateID}       = $Row[8];
5695        $Data{HistoryTypeID} = $Row[9];
5696        $Data{TypeID}        = $Row[10];
5697        push @Lines, \%Data;
5698    }
5699
5700    # get user object
5701    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
5702
5703    # get user data
5704    for my $Data (@Lines) {
5705
5706        my %UserInfo = $UserObject->GetUserData(
5707            UserID => $Data->{CreateBy},
5708        );
5709
5710        # merge result, put %Data last so that it "wins"
5711        %{$Data} = ( %UserInfo, %{$Data} );
5712    }
5713
5714    return @Lines;
5715}
5716
5717=head2 HistoryDelete()
5718
5719delete a ticket history (from storage)
5720
5721    my $Success = $TicketObject->HistoryDelete(
5722        TicketID => 123,
5723        UserID   => 123,
5724    );
5725
5726Events:
5727    HistoryDelete
5728
5729=cut
5730
5731sub HistoryDelete {
5732    my ( $Self, %Param ) = @_;
5733
5734    # check needed stuff
5735    for my $Needed (qw(TicketID UserID)) {
5736        if ( !$Param{$Needed} ) {
5737            $Kernel::OM->Get('Kernel::System::Log')->Log(
5738                Priority => 'error',
5739                Message  => "Need $Needed!"
5740            );
5741            return;
5742        }
5743    }
5744
5745    # delete ticket history entries from db
5746    return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
5747        SQL =>
5748            'DELETE FROM ticket_history WHERE ticket_id = ? AND (article_id IS NULL OR article_id = 0)',
5749        Bind => [ \$Param{TicketID} ],
5750    );
5751
5752    # trigger event
5753    $Self->EventHandler(
5754        Event => 'HistoryDelete',
5755        Data  => {
5756            TicketID => $Param{TicketID},
5757        },
5758        UserID => $Param{UserID},
5759    );
5760
5761    return 1;
5762}
5763
5764=head2 TicketAccountedTimeGet()
5765
5766returns the accounted time of a ticket.
5767
5768    my $AccountedTime = $TicketObject->TicketAccountedTimeGet(TicketID => 1234);
5769
5770=cut
5771
5772sub TicketAccountedTimeGet {
5773    my ( $Self, %Param ) = @_;
5774
5775    # check needed stuff
5776    if ( !$Param{TicketID} ) {
5777        $Kernel::OM->Get('Kernel::System::Log')->Log(
5778            Priority => 'error',
5779            Message  => 'Need TicketID!'
5780        );
5781        return;
5782    }
5783
5784    # get database object
5785    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
5786
5787    # db query
5788    return if !$DBObject->Prepare(
5789        SQL  => 'SELECT time_unit FROM time_accounting WHERE ticket_id = ?',
5790        Bind => [ \$Param{TicketID} ],
5791    );
5792
5793    my $AccountedTime = 0;
5794    while ( my @Row = $DBObject->FetchrowArray() ) {
5795        $Row[0] =~ s/,/./g;
5796        $AccountedTime = $AccountedTime + $Row[0];
5797    }
5798
5799    return $AccountedTime;
5800}
5801
5802=head2 TicketAccountTime()
5803
5804account time to a ticket.
5805
5806    my $Success = $TicketObject->TicketAccountTime(
5807        TicketID  => 1234,
5808        ArticleID => 23542,
5809        TimeUnit  => '4.5',
5810        UserID    => 1,
5811    );
5812
5813Events:
5814    TicketAccountTime
5815
5816=cut
5817
5818sub TicketAccountTime {
5819    my ( $Self, %Param ) = @_;
5820
5821    # check needed stuff
5822    for my $Needed (qw(TicketID ArticleID TimeUnit UserID)) {
5823        if ( !$Param{$Needed} ) {
5824            $Kernel::OM->Get('Kernel::System::Log')->Log(
5825                Priority => 'error',
5826                Message  => "Need $Needed!"
5827            );
5828            return;
5829        }
5830    }
5831
5832    # check some wrong formats
5833    $Param{TimeUnit} =~ s/,/\./g;
5834    $Param{TimeUnit} =~ s/ //g;
5835    $Param{TimeUnit} =~ s/^(\d{1,10}\.\d\d).+?$/$1/g;
5836    chomp $Param{TimeUnit};
5837
5838    # get database object
5839    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
5840
5841    # update change time
5842    return if !$DBObject->Do(
5843        SQL => 'UPDATE ticket SET change_time = current_timestamp, '
5844            . ' change_by = ? WHERE id = ?',
5845        Bind => [ \$Param{UserID}, \$Param{TicketID} ],
5846    );
5847
5848    # db quote
5849    $Param{TimeUnit} = $DBObject->Quote( $Param{TimeUnit}, 'Number' );
5850
5851    # db update
5852    return if !$DBObject->Do(
5853        SQL => "INSERT INTO time_accounting "
5854            . " (ticket_id, article_id, time_unit, create_time, create_by, change_time, change_by) "
5855            . " VALUES (?, ?, $Param{TimeUnit}, current_timestamp, ?, current_timestamp, ?)",
5856        Bind => [
5857            \$Param{TicketID}, \$Param{ArticleID}, \$Param{UserID}, \$Param{UserID},
5858        ],
5859    );
5860
5861    # clear ticket cache
5862    $Self->_TicketCacheClear( TicketID => $Param{TicketID} );
5863
5864    # add history
5865    my $AccountedTime = $Self->TicketAccountedTimeGet( TicketID => $Param{TicketID} );
5866    $Self->HistoryAdd(
5867        TicketID     => $Param{TicketID},
5868        ArticleID    => $Param{ArticleID},
5869        CreateUserID => $Param{UserID},
5870        HistoryType  => 'TimeAccounting',
5871        Name         => "\%\%$Param{TimeUnit}\%\%$AccountedTime",
5872    );
5873
5874    # trigger event
5875    $Self->EventHandler(
5876        Event => 'TicketAccountTime',
5877        Data  => {
5878            TicketID  => $Param{TicketID},
5879            ArticleID => $Param{ArticleID},
5880        },
5881        UserID => $Param{UserID},
5882    );
5883
5884    return 1;
5885}
5886
5887=head2 TicketMerge()
5888
5889merge two tickets
5890
5891    my $Success = $TicketObject->TicketMerge(
5892        MainTicketID  => 412,
5893        MergeTicketID => 123,
5894        UserID        => 123,
5895    );
5896
5897Events:
5898    TicketMerge
5899
5900=cut
5901
5902sub TicketMerge {
5903    my ( $Self, %Param ) = @_;
5904
5905    # check needed stuff
5906    for my $Needed (qw(MainTicketID MergeTicketID UserID)) {
5907        if ( !$Param{$Needed} ) {
5908            $Kernel::OM->Get('Kernel::System::Log')->Log(
5909                Priority => 'error',
5910                Message  => "Need $Needed!"
5911            );
5912            return;
5913        }
5914    }
5915
5916    # Get the list of all merged states.
5917    my @MergeStateList = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
5918        StateType => ['merged'],
5919        Result    => 'Name',
5920    );
5921
5922    # Error handling.
5923    if ( !@MergeStateList ) {
5924        $Kernel::OM->Get('Kernel::System::Log')->Log(
5925            Priority => 'error',
5926            Message  => "No merge state found! Please add a valid merge state.",
5927        );
5928        return 'NoValidMergeStates';
5929    }
5930
5931    # get database object
5932    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
5933
5934    # change ticket id of merge ticket to main ticket
5935    return if !$DBObject->Do(
5936        SQL => 'UPDATE article SET ticket_id = ?, change_time = current_timestamp, '
5937            . ' change_by = ? WHERE ticket_id = ?',
5938        Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ],
5939    );
5940
5941    # former bug 9635 (with table article_index)
5942    # do the same with article_search_index (harmless if not used)
5943    return if !$DBObject->Do(
5944        SQL  => 'UPDATE article_search_index SET ticket_id = ? WHERE ticket_id = ?',
5945        Bind => [ \$Param{MainTicketID}, \$Param{MergeTicketID} ],
5946    );
5947
5948    # reassign article history
5949    return if !$DBObject->Do(
5950        SQL => 'UPDATE ticket_history SET ticket_id = ?, change_time = current_timestamp, '
5951            . ' change_by = ? WHERE ticket_id = ?
5952            AND (article_id IS NOT NULL OR article_id != 0)',
5953        Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ],
5954    );
5955
5956    # update the accounted time of the main ticket
5957    return if !$DBObject->Do(
5958        SQL => 'UPDATE time_accounting SET ticket_id = ?, change_time = current_timestamp, '
5959            . ' change_by = ? WHERE ticket_id = ?',
5960        Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ],
5961    );
5962
5963    my %MainTicket = $Self->TicketGet(
5964        TicketID      => $Param{MainTicketID},
5965        DynamicFields => 0,
5966    );
5967    my %MergeTicket = $Self->TicketGet(
5968        TicketID      => $Param{MergeTicketID},
5969        DynamicFields => 0,
5970    );
5971
5972    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
5973
5974    # Set language for AutomaticMergeText, see more in bug #13967
5975    my %UserInfo = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
5976        UserID => $Param{UserID},
5977    );
5978    my $Language = $UserInfo{UserLanguage} || $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en';
5979
5980    $Kernel::OM->ObjectsDiscard(
5981        Objects => ['Kernel::Language'],
5982    );
5983    $Kernel::OM->ObjectParamAdd(
5984        'Kernel::Language' => {
5985            UserLanguage => $Language,
5986        },
5987    );
5988    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
5989
5990    my $Body = $ConfigObject->Get('Ticket::Frontend::AutomaticMergeText');
5991    $Body = $LanguageObject->Translate($Body);
5992    $Body =~ s{<OTRS_TICKET>}{$MergeTicket{TicketNumber}}xms;
5993    $Body =~ s{<OTRS_MERGE_TO_TICKET>}{$MainTicket{TicketNumber}}xms;
5994
5995    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
5996
5997    # Add merge article to merge ticket using internal channel.
5998    $ArticleObject->BackendForChannel( ChannelName => 'Internal' )->ArticleCreate(
5999        TicketID             => $Param{MergeTicketID},
6000        SenderType           => 'agent',
6001        IsVisibleForCustomer => 1,
6002        ContentType          => "text/plain; charset=ascii",
6003        UserID               => $Param{UserID},
6004        HistoryType          => 'AddNote',
6005        HistoryComment       => '%%Note',
6006        Subject              => $ConfigObject->Get('Ticket::Frontend::AutomaticMergeSubject') || 'Ticket Merged',
6007        Body                 => $Body,
6008        NoAgentNotify        => 1,
6009    );
6010
6011    # add merge history to merge ticket
6012    $Self->HistoryAdd(
6013        TicketID    => $Param{MergeTicketID},
6014        HistoryType => 'Merged',
6015        Name        => "\%\%$MergeTicket{TicketNumber}\%\%$Param{MergeTicketID}"
6016            . "\%\%$MainTicket{TicketNumber}\%\%$Param{MainTicketID}",
6017        CreateUserID => $Param{UserID},
6018    );
6019
6020    # add merge history to main ticket
6021    $Self->HistoryAdd(
6022        TicketID    => $Param{MainTicketID},
6023        HistoryType => 'Merged',
6024        Name        => "\%\%$MergeTicket{TicketNumber}\%\%$Param{MergeTicketID}"
6025            . "\%\%$MainTicket{TicketNumber}\%\%$Param{MainTicketID}",
6026        CreateUserID => $Param{UserID},
6027    );
6028
6029    # transfer watchers - only those that were not already watching the main ticket
6030    # delete all watchers from the merge ticket that are already watching the main ticket
6031    my %MainWatchers = $Self->TicketWatchGet(
6032        TicketID => $Param{MainTicketID},
6033    );
6034
6035    my %MergeWatchers = $Self->TicketWatchGet(
6036        TicketID => $Param{MergeTicketID},
6037    );
6038
6039    WATCHER:
6040    for my $WatcherID ( sort keys %MergeWatchers ) {
6041
6042        next WATCHER if !$MainWatchers{$WatcherID};
6043        return if !$DBObject->Do(
6044            SQL => '
6045                DELETE FROM ticket_watcher
6046                    WHERE user_id = ?
6047                    AND ticket_id = ?
6048                ',
6049            Bind => [ \$WatcherID, \$Param{MergeTicketID} ],
6050        );
6051    }
6052
6053    # transfer remaining watchers to new ticket
6054    return if !$DBObject->Do(
6055        SQL => '
6056            UPDATE ticket_watcher
6057                SET ticket_id = ?
6058                WHERE ticket_id = ?
6059            ',
6060        Bind => [ \$Param{MainTicketID}, \$Param{MergeTicketID} ],
6061    );
6062
6063    # transfer all linked objects to new ticket
6064    $Self->TicketMergeLinkedObjects(
6065        MergeTicketID => $Param{MergeTicketID},
6066        MainTicketID  => $Param{MainTicketID},
6067        UserID        => $Param{UserID},
6068    );
6069
6070    # link tickets
6071    $Kernel::OM->Get('Kernel::System::LinkObject')->LinkAdd(
6072        SourceObject => 'Ticket',
6073        SourceKey    => $Param{MainTicketID},
6074        TargetObject => 'Ticket',
6075        TargetKey    => $Param{MergeTicketID},
6076        Type         => 'ParentChild',
6077        State        => 'Valid',
6078        UserID       => $Param{UserID},
6079    );
6080
6081    # Update change time and user ID for main ticket.
6082    #   See bug#13092 for more information.
6083    return if !$DBObject->Do(
6084        SQL  => 'UPDATE ticket SET change_time = current_timestamp, change_by = ? WHERE id = ?',
6085        Bind => [ \$Param{UserID}, \$Param{MainTicketID} ],
6086    );
6087
6088    # set new state of merge ticket
6089    $Self->TicketStateSet(
6090        State    => $MergeStateList[0],
6091        TicketID => $Param{MergeTicketID},
6092        UserID   => $Param{UserID},
6093    );
6094
6095    # unlock ticket
6096    $Self->LockSet(
6097        Lock     => 'unlock',
6098        TicketID => $Param{MergeTicketID},
6099        UserID   => $Param{UserID},
6100    );
6101
6102    # remove seen flag for all users on the main ticket
6103    $Self->TicketFlagDelete(
6104        TicketID => $Param{MainTicketID},
6105        Key      => 'Seen',
6106        AllUsers => 1,
6107    );
6108
6109    $Self->TicketMergeDynamicFields(
6110        MergeTicketID => $Param{MergeTicketID},
6111        MainTicketID  => $Param{MainTicketID},
6112        UserID        => $Param{UserID},
6113    );
6114
6115    $Self->_TicketCacheClear( TicketID => $Param{MergeTicketID} );
6116    $Self->_TicketCacheClear( TicketID => $Param{MainTicketID} );
6117
6118    # trigger event
6119    $Self->EventHandler(
6120        Event => 'TicketMerge',
6121        Data  => {
6122            TicketID     => $Param{MergeTicketID},
6123            MainTicketID => $Param{MainTicketID},
6124        },
6125        UserID => $Param{UserID},
6126    );
6127
6128    return 1;
6129}
6130
6131=head2 TicketMergeDynamicFields()
6132
6133merge dynamic fields from one ticket into another, that is, copy
6134them from the merge ticket to the main ticket if the value is empty
6135in the main ticket.
6136
6137    my $Success = $TicketObject->TicketMergeDynamicFields(
6138        MainTicketID  => 123,
6139        MergeTicketID => 42,
6140        UserID        => 1,
6141        DynamicFields => ['DynamicField_TicketFreeText1'], # optional
6142    );
6143
6144If DynamicFields is not present, it is taken from the Ticket::MergeDynamicFields
6145configuration.
6146
6147=cut
6148
6149sub TicketMergeDynamicFields {
6150    my ( $Self, %Param ) = @_;
6151
6152    for my $Needed (qw(MainTicketID MergeTicketID UserID)) {
6153        if ( !$Param{$Needed} ) {
6154            $Kernel::OM->Get('Kernel::System::Log')->Log(
6155                Priority => 'error',
6156                Message  => "Need $Needed!"
6157            );
6158            return;
6159        }
6160    }
6161
6162    my $DynamicFields = $Param{DynamicFields};
6163
6164    if ( !$DynamicFields ) {
6165        $DynamicFields = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::MergeDynamicFields');
6166    }
6167
6168    return 1 if !IsArrayRefWithData($DynamicFields);
6169
6170    my %MainTicket = $Self->TicketGet(
6171        TicketID      => $Param{MainTicketID},
6172        UserID        => $Param{UserID},
6173        DynamicFields => 1,
6174    );
6175    my %MergeTicket = $Self->TicketGet(
6176        TicketID      => $Param{MergeTicketID},
6177        UserID        => $Param{UserID},
6178        DynamicFields => 1,
6179    );
6180
6181    # get dynamic field objects
6182    my $DynamicFieldObject        = $Kernel::OM->Get('Kernel::System::DynamicField');
6183    my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
6184
6185    FIELDS:
6186    for my $DynamicFieldName ( @{$DynamicFields} ) {
6187
6188        my $Key = "DynamicField_$DynamicFieldName";
6189
6190        if (
6191            defined $MergeTicket{$Key}
6192            && length $MergeTicket{$Key}
6193            && !( defined $MainTicket{$Key} && length $MainTicket{$Key} )
6194            )
6195        {
6196
6197            my $DynamicFieldConfig = $DynamicFieldObject->DynamicFieldGet(
6198                Name => $DynamicFieldName,
6199            );
6200
6201            if ( !$DynamicFieldConfig ) {
6202                $Kernel::OM->Get('Kernel::System::Log')->Log(
6203                    Priority => 'Error',
6204                    Message  => qq[No such dynamic field "$DynamicFieldName"],
6205                );
6206                return;
6207            }
6208
6209            $DynamicFieldBackendObject->ValueSet(
6210                DynamicFieldConfig => $DynamicFieldConfig,
6211                ObjectID           => $Param{MainTicketID},
6212                UserID             => $Param{UserID},
6213                Value              => $MergeTicket{$Key},
6214            );
6215        }
6216    }
6217
6218    return 1;
6219}
6220
6221=head2 TicketMergeLinkedObjects()
6222
6223merge linked objects from one ticket into another, that is, move
6224them from the merge ticket to the main ticket in the link_relation table.
6225
6226    my $Success = $TicketObject->TicketMergeLinkedObjects(
6227        MainTicketID  => 123,
6228        MergeTicketID => 42,
6229        UserID        => 1,
6230    );
6231
6232=cut
6233
6234sub TicketMergeLinkedObjects {
6235    my ( $Self, %Param ) = @_;
6236
6237    for my $Needed (qw(MainTicketID MergeTicketID UserID)) {
6238        if ( !$Param{$Needed} ) {
6239            $Kernel::OM->Get('Kernel::System::Log')->Log(
6240                Priority => 'error',
6241                Message  => "Need $Needed!",
6242            );
6243            return;
6244        }
6245    }
6246
6247    # Lookup the object id of a ticket.
6248    my $TicketObjectID = $Kernel::OM->Get('Kernel::System::LinkObject')->ObjectLookup(
6249        Name => 'Ticket',
6250    );
6251
6252    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
6253
6254    # Delete all duplicate links relations between merged tickets.
6255    # See bug#12994 (https://bugs.otrs.org/show_bug.cgi?id=12994).
6256    $DBObject->Prepare(
6257        SQL => '
6258            SELECT target_key
6259            FROM link_relation
6260            WHERE target_object_id = ?
6261              AND source_object_id = ?
6262              AND source_key= ?
6263              AND target_key
6264              IN (SELECT target_key FROM link_relation WHERE source_key= ? )',
6265        Bind => [
6266            \$TicketObjectID,
6267            \$TicketObjectID,
6268            \$Param{MainTicketID},
6269            \$Param{MergeTicketID},
6270        ],
6271    );
6272
6273    my @Relations;
6274    while ( my @Row = $DBObject->FetchrowArray() ) {
6275        push @Relations, $Row[0];
6276    }
6277    if (@Relations) {
6278
6279        my $SQL = "DELETE FROM link_relation
6280                 WHERE target_object_id = ?
6281                   AND source_object_id = ?
6282                   AND source_key = ?
6283                   AND target_key IN ( '${\(join '\',\'', @Relations)}' )";
6284
6285        $DBObject->Prepare(
6286            SQL  => $SQL,
6287            Bind => [
6288                \$TicketObjectID,
6289                \$TicketObjectID,
6290                \$Param{MergeTicketID},
6291            ],
6292        );
6293    }
6294
6295    # Update links from old ticket to new ticket where the old ticket is the source  MainTicketID.
6296    $DBObject->Do(
6297        SQL => '
6298            UPDATE link_relation
6299            SET source_key = ?
6300            WHERE source_object_id = ?
6301              AND source_key = ?',
6302        Bind => [
6303
6304            \$Param{MainTicketID},
6305            \$TicketObjectID,
6306            \$Param{MergeTicketID},
6307        ],
6308    );
6309
6310    # Update links from old ticket to new ticket where the old ticket is the target.
6311    $DBObject->Do(
6312        SQL => '
6313            UPDATE link_relation
6314            SET target_key = ?
6315            WHERE target_object_id = ?
6316              AND target_key = ?',
6317        Bind => [
6318            \$Param{MainTicketID},
6319            \$TicketObjectID,
6320            \$Param{MergeTicketID},
6321        ],
6322    );
6323
6324    # Delete all links between tickets where source and target object are the same.
6325    $DBObject->Do(
6326        SQL => '
6327            DELETE FROM link_relation
6328            WHERE source_object_id = ?
6329                AND target_object_id = ?
6330                AND source_key = target_key
6331        ',
6332        Bind => [
6333            \$TicketObjectID,
6334            \$TicketObjectID,
6335        ],
6336    );
6337
6338    return 1;
6339}
6340
6341=head2 TicketWatchGet()
6342
6343to get all user ids and additional attributes of an watched ticket
6344
6345    my %Watch = $TicketObject->TicketWatchGet(
6346        TicketID => 123,
6347    );
6348
6349get list of users to notify
6350
6351    my %Watch = $TicketObject->TicketWatchGet(
6352        TicketID => 123,
6353        Notify   => 1,
6354    );
6355
6356get list of users as array
6357
6358    my @Watch = $TicketObject->TicketWatchGet(
6359        TicketID => 123,
6360        Result   => 'ARRAY',
6361    );
6362
6363=cut
6364
6365sub TicketWatchGet {
6366    my ( $Self, %Param ) = @_;
6367
6368    # check needed stuff
6369    if ( !$Param{TicketID} ) {
6370        $Kernel::OM->Get('Kernel::System::Log')->Log(
6371            Priority => 'error',
6372            Message  => "Need TicketID!"
6373        );
6374        return;
6375    }
6376
6377    # check if feature is enabled
6378    return if !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::Watcher');
6379
6380    # get database object
6381    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
6382
6383    # get all attributes of an watched ticket
6384    return if !$DBObject->Prepare(
6385        SQL => '
6386            SELECT user_id, create_time, create_by, change_time, change_by
6387            FROM ticket_watcher
6388            WHERE ticket_id = ?',
6389        Bind => [ \$Param{TicketID} ],
6390    );
6391
6392    # fetch the result
6393    my %Data;
6394    while ( my @Row = $DBObject->FetchrowArray() ) {
6395        $Data{ $Row[0] } = {
6396            CreateTime => $Row[1],
6397            CreateBy   => $Row[2],
6398            ChangeTime => $Row[3],
6399            ChangeBy   => $Row[4],
6400        };
6401    }
6402
6403    if ( $Param{Notify} ) {
6404
6405        for my $UserID ( sort keys %Data ) {
6406
6407            # get user object
6408            my $UserObject = $Kernel::OM->Get('Kernel::System::User');
6409
6410            my %UserData = $UserObject->GetUserData(
6411                UserID => $UserID,
6412                Valid  => 1,
6413            );
6414
6415            if ( !$UserData{UserSendWatcherNotification} ) {
6416                delete $Data{$UserID};
6417            }
6418        }
6419    }
6420
6421    # check result
6422    if ( $Param{Result} && $Param{Result} eq 'ARRAY' ) {
6423
6424        my @UserIDs;
6425
6426        for my $UserID ( sort keys %Data ) {
6427            push @UserIDs, $UserID;
6428        }
6429
6430        return @UserIDs;
6431    }
6432
6433    return %Data;
6434}
6435
6436=head2 TicketWatchSubscribe()
6437
6438to subscribe a ticket to watch it
6439
6440    my $Success = $TicketObject->TicketWatchSubscribe(
6441        TicketID    => 111,
6442        WatchUserID => 123,
6443        UserID      => 123,
6444    );
6445
6446Events:
6447    TicketSubscribe
6448
6449=cut
6450
6451sub TicketWatchSubscribe {
6452    my ( $Self, %Param ) = @_;
6453
6454    # check needed stuff
6455    for my $Needed (qw(TicketID WatchUserID UserID)) {
6456        if ( !defined $Param{$Needed} ) {
6457            $Kernel::OM->Get('Kernel::System::Log')->Log(
6458                Priority => 'error',
6459                Message  => "Need $Needed!"
6460            );
6461            return;
6462        }
6463    }
6464
6465    # get database object
6466    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
6467
6468    # db access
6469    return if !$DBObject->Do(
6470        SQL => '
6471            DELETE FROM ticket_watcher
6472            WHERE ticket_id = ?
6473                AND user_id = ?',
6474        Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ],
6475    );
6476    return if !$DBObject->Do(
6477        SQL => '
6478            INSERT INTO ticket_watcher (ticket_id, user_id, create_time, create_by, change_time, change_by)
6479            VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)',
6480        Bind => [ \$Param{TicketID}, \$Param{WatchUserID}, \$Param{UserID}, \$Param{UserID} ],
6481    );
6482
6483    # get user data
6484    my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
6485        UserID => $Param{WatchUserID},
6486    );
6487
6488    # add history
6489    $Self->HistoryAdd(
6490        TicketID     => $Param{TicketID},
6491        CreateUserID => $Param{UserID},
6492        HistoryType  => 'Subscribe',
6493        Name         => "\%\%$User{UserFullname}",
6494    );
6495
6496    # trigger event
6497    $Self->EventHandler(
6498        Event => 'TicketSubscribe',
6499        Data  => {
6500            TicketID => $Param{TicketID},
6501        },
6502        UserID => $Param{UserID},
6503    );
6504
6505    return 1;
6506}
6507
6508=head2 TicketWatchUnsubscribe()
6509
6510to remove a subscription of a ticket
6511
6512    my $Success = $TicketObject->TicketWatchUnsubscribe(
6513        TicketID    => 111,
6514        WatchUserID => 123,
6515        UserID      => 123,
6516    );
6517
6518Events:
6519    TicketUnsubscribe
6520
6521=cut
6522
6523sub TicketWatchUnsubscribe {
6524    my ( $Self, %Param ) = @_;
6525
6526    # check needed stuff
6527    for my $Needed (qw(TicketID UserID)) {
6528        if ( !defined $Param{$Needed} ) {
6529            $Kernel::OM->Get('Kernel::System::Log')->Log(
6530                Priority => 'error',
6531                Message  => "Need $Needed!"
6532            );
6533            return;
6534        }
6535    }
6536
6537    # only one of these parameters is needed
6538    if ( !$Param{WatchUserID} && !$Param{AllUsers} ) {
6539        $Kernel::OM->Get('Kernel::System::Log')->Log(
6540            Priority => 'error',
6541            Message  => "Need WatchUserID or AllUsers param!"
6542        );
6543        return;
6544    }
6545
6546    # get user object
6547    my $UserObject = $Kernel::OM->Get('Kernel::System::User');
6548
6549    if ( $Param{AllUsers} ) {
6550        my @WatchUsers = $Self->TicketWatchGet(
6551            TicketID => $Param{TicketID},
6552            Result   => 'ARRAY',
6553        );
6554
6555        return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
6556            SQL  => 'DELETE FROM ticket_watcher WHERE ticket_id = ?',
6557            Bind => [ \$Param{TicketID} ],
6558        );
6559
6560        for my $WatchUser (@WatchUsers) {
6561
6562            my %User = $UserObject->GetUserData(
6563                UserID => $WatchUser,
6564            );
6565
6566            $Self->HistoryAdd(
6567                TicketID     => $Param{TicketID},
6568                CreateUserID => $Param{UserID},
6569                HistoryType  => 'Unsubscribe',
6570                Name         => "\%\%$User{UserFullname}",
6571            );
6572
6573            $Self->EventHandler(
6574                Event => 'TicketUnsubscribe',
6575                Data  => {
6576                    TicketID => $Param{TicketID},
6577                },
6578                UserID => $Param{UserID},
6579            );
6580        }
6581
6582    }
6583    else {
6584        return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
6585            SQL  => 'DELETE FROM ticket_watcher WHERE ticket_id = ? AND user_id = ?',
6586            Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ],
6587        );
6588
6589        my %User = $UserObject->GetUserData(
6590            UserID => $Param{WatchUserID},
6591        );
6592
6593        $Self->HistoryAdd(
6594            TicketID     => $Param{TicketID},
6595            CreateUserID => $Param{UserID},
6596            HistoryType  => 'Unsubscribe',
6597            Name         => "\%\%$User{UserFullname}",
6598        );
6599
6600        $Self->EventHandler(
6601            Event => 'TicketUnsubscribe',
6602            Data  => {
6603                TicketID => $Param{TicketID},
6604            },
6605            UserID => $Param{UserID},
6606        );
6607    }
6608
6609    return 1;
6610}
6611
6612=head2 TicketFlagSet()
6613
6614set ticket flags
6615
6616    my $Success = $TicketObject->TicketFlagSet(
6617        TicketID => 123,
6618        Key      => 'Seen',
6619        Value    => 1,
6620        UserID   => 123, # apply to this user
6621    );
6622
6623Events:
6624    TicketFlagSet
6625
6626=cut
6627
6628sub TicketFlagSet {
6629    my ( $Self, %Param ) = @_;
6630
6631    # check needed stuff
6632    for my $Needed (qw(TicketID Key Value UserID)) {
6633        if ( !defined $Param{$Needed} ) {
6634            $Kernel::OM->Get('Kernel::System::Log')->Log(
6635                Priority => 'error',
6636                Message  => "Need $Needed!",
6637            );
6638            return;
6639        }
6640    }
6641
6642    # get flags
6643    my %Flag = $Self->TicketFlagGet(
6644        TicketID => $Param{TicketID},
6645        UserID   => $Param{UserID},
6646    );
6647
6648    # check if set is needed
6649    return 1 if defined $Flag{ $Param{Key} } && $Flag{ $Param{Key} } eq $Param{Value};
6650
6651    # get database object
6652    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
6653
6654    # set flag
6655    return if !$DBObject->Do(
6656        SQL => '
6657            DELETE FROM ticket_flag
6658            WHERE ticket_id = ?
6659                AND ticket_key = ?
6660                AND create_by = ?',
6661        Bind => [ \$Param{TicketID}, \$Param{Key}, \$Param{UserID} ],
6662    );
6663    return if !$DBObject->Do(
6664        SQL => '
6665            INSERT INTO ticket_flag
6666            (ticket_id, ticket_key, ticket_value, create_time, create_by)
6667            VALUES (?, ?, ?, current_timestamp, ?)',
6668        Bind => [ \$Param{TicketID}, \$Param{Key}, \$Param{Value}, \$Param{UserID} ],
6669    );
6670
6671    # delete cache
6672    $Kernel::OM->Get('Kernel::System::Cache')->Delete(
6673        Type => $Self->{CacheType},
6674        Key  => 'TicketFlag::' . $Param{TicketID},
6675    );
6676
6677    # event
6678    $Self->EventHandler(
6679        Event => 'TicketFlagSet',
6680        Data  => {
6681            TicketID => $Param{TicketID},
6682            Key      => $Param{Key},
6683            Value    => $Param{Value},
6684            UserID   => $Param{UserID},
6685        },
6686        UserID => $Param{UserID},
6687    );
6688
6689    return 1;
6690}
6691
6692=head2 TicketFlagDelete()
6693
6694delete ticket flag
6695
6696    my $Success = $TicketObject->TicketFlagDelete(
6697        TicketID => 123,
6698        Key      => 'Seen',
6699        UserID   => 123,
6700    );
6701
6702    my $Success = $TicketObject->TicketFlagDelete(
6703        TicketID => 123,
6704        Key      => 'Seen',
6705        AllUsers => 1,
6706    );
6707
6708Events:
6709    TicketFlagDelete
6710
6711=cut
6712
6713sub TicketFlagDelete {
6714    my ( $Self, %Param ) = @_;
6715
6716    # check needed stuff
6717    for my $Needed (qw(TicketID Key)) {
6718        if ( !$Param{$Needed} ) {
6719            $Kernel::OM->Get('Kernel::System::Log')->Log(
6720                Priority => 'error',
6721                Message  => "Need $Needed!",
6722            );
6723            return;
6724        }
6725    }
6726
6727    # only one of these parameters is needed
6728    if ( !$Param{UserID} && !$Param{AllUsers} ) {
6729        $Kernel::OM->Get('Kernel::System::Log')->Log(
6730            Priority => 'error',
6731            Message  => "Need UserID or AllUsers param!",
6732        );
6733        return;
6734    }
6735
6736    # if all users parameter was given
6737    if ( $Param{AllUsers} ) {
6738
6739        # get all affected users
6740        my @AllTicketFlags = $Self->TicketFlagGet(
6741            TicketID => $Param{TicketID},
6742            AllUsers => 1,
6743        );
6744
6745        # delete flags from database
6746        return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
6747            SQL => '
6748                DELETE FROM ticket_flag
6749                WHERE ticket_id = ?
6750                    AND ticket_key = ?',
6751            Bind => [ \$Param{TicketID}, \$Param{Key} ],
6752        );
6753
6754        # delete cache
6755        $Kernel::OM->Get('Kernel::System::Cache')->Delete(
6756            Type => $Self->{CacheType},
6757            Key  => 'TicketFlag::' . $Param{TicketID},
6758        );
6759
6760        for my $Record (@AllTicketFlags) {
6761
6762            $Self->EventHandler(
6763                Event => 'TicketFlagDelete',
6764                Data  => {
6765                    TicketID => $Param{TicketID},
6766                    Key      => $Param{Key},
6767                    UserID   => $Record->{UserID},
6768                },
6769                UserID => $Record->{UserID},
6770            );
6771        }
6772    }
6773    else {
6774
6775        # delete flags from database
6776        return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
6777            SQL => '
6778                DELETE FROM ticket_flag
6779                WHERE ticket_id = ?
6780                    AND create_by = ?
6781                    AND ticket_key = ?',
6782            Bind => [ \$Param{TicketID}, \$Param{UserID}, \$Param{Key} ],
6783        );
6784
6785        # delete cache
6786        $Kernel::OM->Get('Kernel::System::Cache')->Delete(
6787            Type => $Self->{CacheType},
6788            Key  => 'TicketFlag::' . $Param{TicketID},
6789        );
6790
6791        $Self->EventHandler(
6792            Event => 'TicketFlagDelete',
6793            Data  => {
6794                TicketID => $Param{TicketID},
6795                Key      => $Param{Key},
6796                UserID   => $Param{UserID},
6797            },
6798            UserID => $Param{UserID},
6799        );
6800    }
6801
6802    return 1;
6803}
6804
6805=head2 TicketFlagGet()
6806
6807get ticket flags
6808
6809    my %Flags = $TicketObject->TicketFlagGet(
6810        TicketID => 123,
6811        UserID   => 123,  # to get flags of one user
6812    );
6813
6814    my @Flags = $TicketObject->TicketFlagGet(
6815        TicketID => 123,
6816        AllUsers => 1,    # to get flags of all users
6817    );
6818
6819=cut
6820
6821sub TicketFlagGet {
6822    my ( $Self, %Param ) = @_;
6823
6824    # check needed stuff
6825    if ( !$Param{TicketID} ) {
6826        $Kernel::OM->Get('Kernel::System::Log')->Log(
6827            Priority => 'error',
6828            Message  => "Need TicketID!",
6829        );
6830        return;
6831    }
6832
6833    # check optional
6834    if ( !$Param{UserID} && !$Param{AllUsers} ) {
6835        $Kernel::OM->Get('Kernel::System::Log')->Log(
6836            Priority => 'error',
6837            Message  => "Need UserID or AllUsers param!",
6838        );
6839        return;
6840    }
6841
6842    # get cache object
6843    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
6844
6845    # check cache
6846    my $Flags = $CacheObject->Get(
6847        Type => $Self->{CacheType},
6848        Key  => 'TicketFlag::' . $Param{TicketID},
6849    );
6850
6851    if ( !$Flags || ref $Flags ne 'HASH' ) {
6852
6853        # get database object
6854        my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
6855
6856        # get all ticket flags of the given ticket
6857        return if !$DBObject->Prepare(
6858            SQL => '
6859                SELECT create_by, ticket_key, ticket_value
6860                FROM ticket_flag
6861                WHERE ticket_id = ?',
6862            Bind => [ \$Param{TicketID} ],
6863        );
6864
6865        # fetch the result
6866        $Flags = {};
6867        while ( my @Row = $DBObject->FetchrowArray() ) {
6868            $Flags->{ $Row[0] }->{ $Row[1] } = $Row[2];
6869        }
6870
6871        # set cache
6872        $CacheObject->Set(
6873            Type  => $Self->{CacheType},
6874            TTL   => $Self->{CacheTTL},
6875            Key   => 'TicketFlag::' . $Param{TicketID},
6876            Value => $Flags,
6877        );
6878    }
6879
6880    if ( $Param{AllUsers} ) {
6881
6882        my @FlagAllUsers;
6883        for my $UserID ( sort keys %{$Flags} ) {
6884
6885            for my $Key ( sort keys %{ $Flags->{$UserID} } ) {
6886
6887                push @FlagAllUsers, {
6888                    Key    => $Key,
6889                    Value  => $Flags->{$UserID}->{$Key},
6890                    UserID => $UserID,
6891                };
6892            }
6893        }
6894
6895        return @FlagAllUsers;
6896    }
6897
6898    # extract user tags
6899    my $UserTags = $Flags->{ $Param{UserID} } || {};
6900
6901    return %{$UserTags};
6902}
6903
6904=head2 TicketArticleStorageSwitch()
6905
6906move article storage from one backend to other backend
6907
6908    my $Success = $TicketObject->TicketArticleStorageSwitch(
6909        TicketID    => 123,
6910        Source      => 'ArticleStorageDB',
6911        Destination => 'ArticleStorageFS',
6912        UserID      => 1,
6913    );
6914
6915=cut
6916
6917sub TicketArticleStorageSwitch {
6918    my ( $Self, %Param ) = @_;
6919
6920    # check needed stuff
6921    for my $Needed (qw(TicketID Source Destination UserID)) {
6922        if ( !$Param{$Needed} ) {
6923            $Kernel::OM->Get('Kernel::System::Log')->Log(
6924                Priority => 'error',
6925                Message  => "Need $Needed!"
6926            );
6927            return;
6928        }
6929    }
6930
6931    # check source vs. destination
6932    return 1 if $Param{Source} eq $Param{Destination};
6933
6934    # get config object
6935    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
6936
6937    # reset events and remember
6938    my $EventConfig = $ConfigObject->Get('Ticket::EventModulePost');
6939    $ConfigObject->{'Ticket::EventModulePost'} = {};
6940
6941    # make sure that CheckAllBackends is set for the duration of this method
6942    $Self->{CheckAllBackends} = 1;
6943
6944    my $MainObject    = $Kernel::OM->Get('Kernel::System::Main');
6945    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
6946
6947    # Get articles.
6948    my @Articles = $ArticleObject->ArticleList(
6949        TicketID => $Param{TicketID},
6950        UserID   => $Param{UserID},
6951    );
6952
6953    ARTICLE:
6954    for my $Article (@Articles) {
6955
6956        # Handle only MIME based articles.
6957        my $BackendName = $ArticleObject->BackendForArticle(
6958            TicketID  => $Param{TicketID},
6959            ArticleID => $Article->{ArticleID}
6960        )->ChannelNameGet();
6961        next ARTICLE if $BackendName !~ /^(Email|Phone|Internal)$/;
6962
6963        my $ArticleObjectSource = Kernel::System::Ticket::Article::Backend::MIMEBase->new(
6964            ArticleStorageModule => 'Kernel::System::Ticket::Article::Backend::MIMEBase::' . $Param{Source},
6965        );
6966        if (
6967            !$ArticleObjectSource
6968            || $ArticleObjectSource->{ArticleStorageModule} ne
6969            "Kernel::System::Ticket::Article::Backend::MIMEBase::$Param{Source}"
6970            )
6971        {
6972            $Kernel::OM->Get('Kernel::System::Log')->Log(
6973                Priority => 'error',
6974                Message  => "Could not create Kernel::System::Ticket::Article::Backend::MIMEBase::" . $Param{Source},
6975            );
6976            die;
6977        }
6978
6979        # read source attachments
6980        my %Index = $ArticleObjectSource->ArticleAttachmentIndex(
6981            ArticleID     => $Article->{ArticleID},
6982            OnlyMyBackend => 1,
6983        );
6984
6985        # read source plain
6986        my $Plain = $ArticleObjectSource->ArticlePlain(
6987            ArticleID     => $Article->{ArticleID},
6988            OnlyMyBackend => 1,
6989        );
6990        my $PlainMD5Sum = '';
6991        if ($Plain) {
6992            my $PlainMD5 = $Plain;
6993            $PlainMD5Sum = $MainObject->MD5sum(
6994                String => \$PlainMD5,
6995            );
6996        }
6997
6998        # read source attachments
6999        my @Attachments;
7000        my %MD5Sums;
7001        for my $FileID ( sort keys %Index ) {
7002            my %Attachment = $ArticleObjectSource->ArticleAttachment(
7003                ArticleID     => $Article->{ArticleID},
7004                FileID        => $FileID,
7005                OnlyMyBackend => 1,
7006                Force         => 1,
7007            );
7008            push @Attachments, \%Attachment;
7009            my $MD5Sum = $MainObject->MD5sum(
7010                String => $Attachment{Content},
7011            );
7012            $MD5Sums{$MD5Sum}++;
7013        }
7014
7015        # nothing to transfer
7016        next ARTICLE if !@Attachments && !$Plain;
7017
7018        my $ArticleObjectDestination = Kernel::System::Ticket::Article::Backend::MIMEBase->new(
7019            ArticleStorageModule => 'Kernel::System::Ticket::Article::Backend::MIMEBase::' . $Param{Destination},
7020        );
7021        if (
7022            !$ArticleObjectDestination
7023            || $ArticleObjectDestination->{ArticleStorageModule} ne
7024            "Kernel::System::Ticket::Article::Backend::MIMEBase::$Param{Destination}"
7025            )
7026        {
7027            $Kernel::OM->Get('Kernel::System::Log')->Log(
7028                Priority => 'error',
7029                Message  => "Could not create Kernel::System::Ticket::" . $Param{Destination},
7030            );
7031            die;
7032        }
7033
7034        # read destination attachments
7035        %Index = $ArticleObjectDestination->ArticleAttachmentIndex(
7036            ArticleID     => $Article->{ArticleID},
7037            OnlyMyBackend => 1,
7038        );
7039
7040        # read source attachments
7041        if (%Index) {
7042            $Kernel::OM->Get('Kernel::System::Log')->Log(
7043                Priority => 'error',
7044                Message =>
7045                    "Attachments of TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID} already in $Param{Destination}!",
7046            );
7047        }
7048        else {
7049
7050            # write attachments to destination
7051            for my $Attachment (@Attachments) {
7052
7053                # Check UTF8 string for validity and replace any wrongly encoded characters with _
7054                if (
7055                    utf8::is_utf8( $Attachment->{Filename} )
7056                    && !eval { Encode::is_utf8( $Attachment->{Filename}, 1 ) }
7057                    )
7058                {
7059
7060                    Encode::_utf8_off( $Attachment->{Filename} );
7061
7062                    # replace invalid characters with � (U+FFFD, Unicode replacement character)
7063                    # If it runs on good UTF-8 input, output should be identical to input
7064                    $Attachment->{Filename} = eval {
7065                        Encode::decode( 'UTF-8', $Attachment->{Filename} );
7066                    };
7067
7068                    # Replace wrong characters with "_".
7069                    $Attachment->{Filename} =~ s{[\x{FFFD}]}{_}xms;
7070                }
7071
7072                $ArticleObjectDestination->ArticleWriteAttachment(
7073                    %{$Attachment},
7074                    ArticleID => $Article->{ArticleID},
7075                    UserID    => $Param{UserID},
7076                );
7077            }
7078
7079            # write destination plain
7080            if ($Plain) {
7081                $ArticleObjectDestination->ArticleWritePlain(
7082                    Email     => $Plain,
7083                    ArticleID => $Article->{ArticleID},
7084                    UserID    => $Param{UserID},
7085                );
7086            }
7087
7088            # verify destination attachments
7089            %Index = $ArticleObjectDestination->ArticleAttachmentIndex(
7090                ArticleID     => $Article->{ArticleID},
7091                OnlyMyBackend => 1,
7092            );
7093        }
7094
7095        for my $FileID ( sort keys %Index ) {
7096            my %Attachment = $ArticleObjectDestination->ArticleAttachment(
7097                ArticleID     => $Article->{ArticleID},
7098                FileID        => $FileID,
7099                OnlyMyBackend => 1,
7100                Force         => 1,
7101            );
7102            my $MD5Sum = $MainObject->MD5sum(
7103                String => \$Attachment{Content},
7104            );
7105            if ( $MD5Sums{$MD5Sum} ) {
7106                $MD5Sums{$MD5Sum}--;
7107                if ( !$MD5Sums{$MD5Sum} ) {
7108                    delete $MD5Sums{$MD5Sum};
7109                }
7110            }
7111            else {
7112                $Kernel::OM->Get('Kernel::System::Log')->Log(
7113                    Priority => 'error',
7114                    Message =>
7115                        "Corrupt file: $Attachment{Filename} (TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID})!",
7116                );
7117
7118                # delete corrupt attachments from destination
7119                $ArticleObjectDestination->ArticleDeleteAttachment(
7120                    ArticleID     => $Article->{ArticleID},
7121                    UserID        => 1,
7122                    OnlyMyBackend => 1,
7123                );
7124
7125                # set events
7126                $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
7127                return;
7128            }
7129        }
7130
7131        # check if all files are moved
7132        if (%MD5Sums) {
7133            $Kernel::OM->Get('Kernel::System::Log')->Log(
7134                Priority => 'error',
7135                Message =>
7136                    "Not all files are moved! (TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID})!",
7137            );
7138
7139            # delete incomplete attachments from destination
7140            $ArticleObjectDestination->ArticleDeleteAttachment(
7141                ArticleID     => $Article->{ArticleID},
7142                UserID        => 1,
7143                OnlyMyBackend => 1,
7144            );
7145
7146            # set events
7147            $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
7148            return;
7149        }
7150
7151        # verify destination plain if exists in source backend
7152        if ($Plain) {
7153            my $PlainVerify = $ArticleObjectDestination->ArticlePlain(
7154                ArticleID     => $Article->{ArticleID},
7155                OnlyMyBackend => 1,
7156            );
7157            my $PlainMD5SumVerify = '';
7158            if ($PlainVerify) {
7159                $PlainMD5SumVerify = $MainObject->MD5sum(
7160                    String => \$PlainVerify,
7161                );
7162            }
7163            if ( $PlainMD5Sum ne $PlainMD5SumVerify ) {
7164                $Kernel::OM->Get('Kernel::System::Log')->Log(
7165                    Priority => 'error',
7166                    Message =>
7167                        "Corrupt plain file: ArticleID: $Article->{ArticleID} ($PlainMD5Sum/$PlainMD5SumVerify)",
7168                );
7169
7170                # delete corrupt plain file from destination
7171                $ArticleObjectDestination->ArticleDeletePlain(
7172                    ArticleID     => $Article->{ArticleID},
7173                    UserID        => 1,
7174                    OnlyMyBackend => 1,
7175                );
7176
7177                # set events
7178                $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
7179                return;
7180            }
7181        }
7182
7183        $ArticleObjectSource->ArticleDeleteAttachment(
7184            ArticleID     => $Article->{ArticleID},
7185            UserID        => 1,
7186            OnlyMyBackend => 1,
7187        );
7188
7189        # remove source plain
7190        $ArticleObjectSource->ArticleDeletePlain(
7191            ArticleID     => $Article->{ArticleID},
7192            UserID        => 1,
7193            OnlyMyBackend => 1,
7194        );
7195
7196        # read source attachments
7197        %Index = $ArticleObjectSource->ArticleAttachmentIndex(
7198            ArticleID     => $Article->{ArticleID},
7199            OnlyMyBackend => 1,
7200        );
7201
7202        # read source attachments
7203        if (%Index) {
7204            $Kernel::OM->Get('Kernel::System::Log')->Log(
7205                Priority => 'error',
7206                Message  => "Attachments still in $Param{Source}!",
7207            );
7208            return;
7209        }
7210    }
7211
7212    # set events
7213    $ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
7214
7215    # Restore previous behavior.
7216    $Self->{CheckAllBackends} =
7217        $ConfigObject->Get('Ticket::Article::Backend::MIMEBase::CheckAllStorageBackends')
7218        // 0;
7219
7220    return 1;
7221}
7222
7223# ProcessManagement functions
7224
7225=head2 TicketCheckForProcessType()
7226
7227    checks whether or not the ticket is of a process type.
7228
7229    $TicketObject->TicketCheckForProcessType(
7230        TicketID => 123,
7231    );
7232
7233=cut
7234
7235sub TicketCheckForProcessType {
7236    my ( $Self, %Param ) = @_;
7237
7238    # check needed stuff
7239    if ( !$Param{TicketID} ) {
7240        $Kernel::OM->Get('Kernel::System::Log')->Log(
7241            Priority => 'error',
7242            Message  => 'Need TicketID!',
7243        );
7244        return;
7245    }
7246
7247    my $DynamicFieldName = $Kernel::OM->Get('Kernel::Config')->Get('Process::DynamicFieldProcessManagementProcessID');
7248
7249    return if !$DynamicFieldName;
7250    $DynamicFieldName = 'DynamicField_' . $DynamicFieldName;
7251
7252    # get ticket attributes
7253    my %Ticket = $Self->TicketGet(
7254        TicketID      => $Param{TicketID},
7255        DynamicFields => 1,
7256    );
7257
7258    # return 1 if we got process ticket
7259    return 1 if $Ticket{$DynamicFieldName};
7260
7261    return;
7262}
7263
7264=head2 TicketCalendarGet()
7265
7266checks calendar to be used for ticket based on sla and queue
7267
7268    my $Calendar = $TicketObject->TicketCalendarGet(
7269        QueueID => 1,
7270        SLAID   => 1,   # optional
7271    );
7272
7273returns calendar number or empty string for default calendar
7274
7275=cut
7276
7277sub TicketCalendarGet {
7278    my ( $Self, %Param ) = @_;
7279
7280    # check needed stuff
7281    if ( !$Param{QueueID} ) {
7282        $Kernel::OM->Get('Kernel::System::Log')->Log(
7283            Priority => 'error',
7284            Message  => 'Need QueueID!'
7285        );
7286        return;
7287    }
7288
7289    # check if SLAID was passed and if sla has a specific calendar
7290    if ( $Param{SLAID} ) {
7291
7292        my %SLAData = $Kernel::OM->Get('Kernel::System::SLA')->SLAGet(
7293            SLAID  => $Param{SLAID},
7294            UserID => 1,
7295        );
7296
7297        # if SLA has a defined calendar, return it
7298        return $SLAData{Calendar} if $SLAData{Calendar};
7299    }
7300
7301    # if no calendar was determined by SLA, check if queue has a specific calendar
7302    my %QueueData = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet(
7303        ID => $Param{QueueID},
7304    );
7305
7306    # if queue has a defined calendar, return it
7307    return $QueueData{Calendar} if $QueueData{Calendar};
7308
7309    # use default calendar
7310    return '';
7311}
7312
7313=head2 SearchUnknownTicketCustomers()
7314
7315search customer users that are not saved in any backend
7316
7317    my $UnknownTicketCustomerList = $TicketObject->SearchUnknownTicketCustomers(
7318        SearchTerm => 'SomeSearchTerm',
7319    );
7320
7321Returns:
7322
7323    %UnknownTicketCustomerList = (
7324        {
7325            CustomerID    => 'SomeCustomerID',
7326            CustomerUser  => 'SomeCustomerUser',
7327        },
7328        {
7329            CustomerID    => 'SomeCustomerID',
7330            CustomerUser  => 'SomeCustomerUser',
7331        },
7332    );
7333
7334=cut
7335
7336sub SearchUnknownTicketCustomers {
7337    my ( $Self, %Param ) = @_;
7338
7339    my $SearchTerm = $Param{SearchTerm} || '';
7340
7341    # get database object
7342    my $DBObject         = $Kernel::OM->Get('Kernel::System::DB');
7343    my $LikeEscapeString = $DBObject->GetDatabaseFunction('LikeEscapeString');
7344    my $QuotedSearch     = '%' . $DBObject->Quote( $SearchTerm, 'Like' ) . '%';
7345
7346    # db query
7347    return if !$DBObject->Prepare(
7348        SQL =>
7349            "SELECT DISTINCT customer_user_id, customer_id FROM ticket WHERE customer_user_id LIKE ? $LikeEscapeString",
7350        Bind => [ \$QuotedSearch ],
7351    );
7352    my $UnknownTicketCustomerList;
7353
7354    CUSTOMERUSER:
7355    while ( my @Row = $DBObject->FetchrowArray() ) {
7356        $UnknownTicketCustomerList->{ $Row[0] } = $Row[1];
7357    }
7358
7359    return $UnknownTicketCustomerList;
7360}
7361
7362sub TicketAcceleratorUpdate {
7363    my ( $Self, %Param ) = @_;
7364
7365    my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
7366        || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
7367
7368    return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorUpdate(%Param);
7369}
7370
7371sub TicketAcceleratorDelete {
7372    my ( $Self, %Param ) = @_;
7373
7374    my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
7375        || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
7376
7377    return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorDelete(%Param);
7378}
7379
7380sub TicketAcceleratorAdd {
7381    my ( $Self, %Param ) = @_;
7382
7383    my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
7384        || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
7385
7386    return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorAdd(%Param);
7387}
7388
7389sub TicketAcceleratorIndex {
7390    my ( $Self, %Param ) = @_;
7391
7392    my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
7393        || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
7394
7395    return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorIndex(%Param);
7396}
7397
7398sub TicketAcceleratorRebuild {
7399    my ( $Self, %Param ) = @_;
7400
7401    my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
7402        || 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
7403
7404    return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorRebuild(%Param);
7405}
7406
7407sub DESTROY {
7408    my $Self = shift;
7409
7410    # execute all transaction events
7411    $Self->EventHandlerTransaction();
7412
7413    return 1;
7414}
7415
7416# COMPAT: to OTRS 1.x and 2.x (can be removed later)
7417
7418sub CustomerPermission {
7419    my ( $Self, %Param ) = @_;
7420
7421    return $Self->TicketCustomerPermission(%Param);
7422}
7423
7424sub InvolvedAgents {
7425    my ( $Self, %Param ) = @_;
7426
7427    return $Self->TicketInvolvedAgentsList(%Param);
7428}
7429
7430sub LockIsTicketLocked {
7431    my ( $Self, %Param ) = @_;
7432
7433    return $Self->TicketLockGet(%Param);
7434}
7435
7436sub LockSet {
7437    my ( $Self, %Param ) = @_;
7438
7439    return $Self->TicketLockSet(%Param);
7440}
7441
7442sub MoveList {
7443    my ( $Self, %Param ) = @_;
7444
7445    return $Self->TicketMoveList(%Param);
7446}
7447
7448sub MoveTicket {
7449    my ( $Self, %Param ) = @_;
7450
7451    return $Self->TicketQueueSet(%Param);
7452}
7453
7454sub MoveQueueList {
7455    my ( $Self, %Param ) = @_;
7456
7457    return $Self->TicketMoveQueueList(%Param);
7458}
7459
7460sub OwnerList {
7461    my ( $Self, %Param ) = @_;
7462
7463    return $Self->TicketOwnerList(%Param);
7464}
7465
7466sub OwnerSet {
7467    my ( $Self, %Param ) = @_;
7468
7469    return $Self->TicketOwnerSet(%Param);
7470}
7471
7472sub Permission {
7473    my ( $Self, %Param ) = @_;
7474
7475    return $Self->TicketPermission(%Param);
7476}
7477
7478sub PriorityList {
7479    my ( $Self, %Param ) = @_;
7480
7481    return $Self->TicketPriorityList(%Param);
7482}
7483
7484sub PrioritySet {
7485    my ( $Self, %Param ) = @_;
7486
7487    return $Self->TicketPrioritySet(%Param);
7488}
7489
7490sub ResponsibleList {
7491    my ( $Self, %Param ) = @_;
7492
7493    return $Self->TicketResponsibleList(%Param);
7494}
7495
7496sub ResponsibleSet {
7497    my ( $Self, %Param ) = @_;
7498
7499    return $Self->TicketResponsibleSet(%Param);
7500}
7501
7502sub SetCustomerData {
7503    my ( $Self, %Param ) = @_;
7504
7505    return $Self->TicketCustomerSet(%Param);
7506}
7507
7508sub StateList {
7509    my ( $Self, %Param ) = @_;
7510
7511    return $Self->TicketStateList(%Param);
7512}
7513
7514sub StateSet {
7515    my ( $Self, %Param ) = @_;
7516
7517    return $Self->TicketStateSet(%Param);
7518}
7519
7520=head1 PRIVATE FUNCTIONS
7521
7522=head2 _TicketCacheClear()
7523
7524Remove all caches related to specified ticket.
7525
7526    my $Success = $TicketObject->_TicketCacheClear(
7527        TicketID => 123,
7528    );
7529
7530=cut
7531
7532sub _TicketCacheClear {
7533    my ( $Self, %Param ) = @_;
7534
7535    for my $Needed (qw(TicketID)) {
7536        if ( !defined $Param{$Needed} ) {
7537            $Kernel::OM->Get('Kernel::System::Log')->Log(
7538                Priority => 'error',
7539                Message  => "Need $Needed!"
7540            );
7541            return;
7542        }
7543    }
7544
7545    # get cache object
7546    my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
7547
7548    # TicketGet()
7549    my $CacheKey = 'Cache::GetTicket' . $Param{TicketID};
7550    $CacheObject->Delete(
7551        Type => $Self->{CacheType},
7552        Key  => $CacheKey,
7553    );
7554
7555    # delete extended cache for TicketGet()
7556    for my $Extended ( 0 .. 1 ) {
7557        for my $FetchDynamicFields ( 0 .. 1 ) {
7558            my $CacheKeyDynamicFields = $CacheKey . '::' . $Extended . '::' . $FetchDynamicFields;
7559
7560            $CacheObject->Delete(
7561                Type => $Self->{CacheType},
7562                Key  => $CacheKeyDynamicFields,
7563            );
7564        }
7565    }
7566
7567    $Kernel::OM->Get('Kernel::System::Ticket::Article')->_ArticleCacheClear(%Param);
7568
7569    return 1;
7570}
7571
7572=head2 _TicketGetExtended()
7573
7574Collect extended attributes for given ticket,
7575namely first response, first lock and close data.
7576
7577    my %TicketExtended = $TicketObject->_TicketGetExtended(
7578        TicketID => $Param{TicketID},
7579        Ticket   => \%Ticket,
7580    );
7581
7582=cut
7583
7584sub _TicketGetExtended {
7585    my ( $Self, %Param ) = @_;
7586
7587    # check needed stuff
7588    for my $Needed (qw(TicketID Ticket)) {
7589        if ( !defined $Param{$Needed} ) {
7590            $Kernel::OM->Get('Kernel::System::Log')->Log(
7591                Priority => 'error',
7592                Message  => "Need $Needed!"
7593            );
7594            return;
7595        }
7596    }
7597
7598    # get extended attributes
7599    my %FirstResponse   = $Self->_TicketGetFirstResponse(%Param);
7600    my %FirstLock       = $Self->_TicketGetFirstLock(%Param);
7601    my %TicketGetClosed = $Self->_TicketGetClosed(%Param);
7602
7603    # return all as hash
7604    return ( %TicketGetClosed, %FirstResponse, %FirstLock );
7605}
7606
7607=head2 _TicketGetFirstResponse()
7608
7609Collect attributes of first response for given ticket.
7610
7611    my %FirstResponse = $TicketObject->_TicketGetFirstResponse(
7612        TicketID => $Param{TicketID},
7613        Ticket   => \%Ticket,
7614    );
7615
7616=cut
7617
7618sub _TicketGetFirstResponse {
7619    my ( $Self, %Param ) = @_;
7620
7621    # check needed stuff
7622    for my $Needed (qw(TicketID Ticket)) {
7623        if ( !defined $Param{$Needed} ) {
7624            $Kernel::OM->Get('Kernel::System::Log')->Log(
7625                Priority => 'error',
7626                Message  => "Need $Needed!"
7627            );
7628            return;
7629        }
7630    }
7631
7632    # get database object
7633    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
7634
7635    # check if first response is already done
7636    return if !$DBObject->Prepare(
7637        SQL => '
7638            SELECT a.create_time,a.id FROM article a, article_sender_type ast
7639            WHERE a.article_sender_type_id = ast.id
7640                AND a.ticket_id = ?
7641                AND ast.name = ?
7642                AND a.is_visible_for_customer = ?
7643            ORDER BY a.create_time',
7644        Bind  => [ \$Param{TicketID}, \'agent', \1 ],
7645        Limit => 1,
7646    );
7647
7648    my %Data;
7649    while ( my @Row = $DBObject->FetchrowArray() ) {
7650        $Data{FirstResponse} = $Row[0];
7651
7652        # cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000
7653        # and 0000-00-00 00:00:00 time stamps)
7654        $Data{FirstResponse} =~ s/^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\..+?$/$1/;
7655    }
7656
7657    return if !$Data{FirstResponse};
7658
7659    # get escalation properties
7660    my %Escalation = $Self->TicketEscalationPreferences(
7661        Ticket => $Param{Ticket},
7662        UserID => $Param{UserID} || 1,
7663    );
7664
7665    if ( $Escalation{FirstResponseTime} ) {
7666
7667        # create datetime object
7668        my $DateTimeObject = $Kernel::OM->Create(
7669            'Kernel::System::DateTime',
7670            ObjectParams => {
7671                String => $Param{Ticket}->{Created},
7672            }
7673        );
7674
7675        my $FirstResponseTimeObj = $DateTimeObject->Clone();
7676        $FirstResponseTimeObj->Set(
7677            String => $Data{FirstResponse}
7678        );
7679
7680        my $DeltaObj = $DateTimeObject->Delta(
7681            DateTimeObject => $FirstResponseTimeObj,
7682            ForWorkingTime => 1,
7683            Calendar       => $Escalation{Calendar},
7684        );
7685
7686        my $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0;
7687
7688        $Data{FirstResponseInMin} = int( $WorkingTime / 60 );
7689        my $EscalationFirstResponseTime = $Escalation{FirstResponseTime} * 60;
7690        $Data{FirstResponseDiffInMin} =
7691            int( ( $EscalationFirstResponseTime - $WorkingTime ) / 60 );
7692    }
7693
7694    return %Data;
7695}
7696
7697=head2 _TicketGetClosed()
7698
7699Collect attributes of (last) closing for given ticket.
7700
7701    my %TicketGetClosed = $TicketObject->_TicketGetClosed(
7702        TicketID => $Param{TicketID},
7703        Ticket   => \%Ticket,
7704    );
7705
7706=cut
7707
7708sub _TicketGetClosed {
7709    my ( $Self, %Param ) = @_;
7710
7711    # check needed stuff
7712    for my $Needed (qw(TicketID Ticket)) {
7713        if ( !defined $Param{$Needed} ) {
7714            $Kernel::OM->Get('Kernel::System::Log')->Log(
7715                Priority => 'error',
7716                Message  => "Need $Needed!"
7717            );
7718            return;
7719        }
7720    }
7721
7722    # get close state types
7723    my @List = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
7724        StateType => ['closed'],
7725        Result    => 'ID',
7726    );
7727    return if !@List;
7728
7729    # Get id for history types
7730    my @HistoryTypeIDs;
7731    for my $HistoryType (qw(StateUpdate NewTicket)) {
7732        push @HistoryTypeIDs, $Self->HistoryTypeLookup( Type => $HistoryType );
7733    }
7734
7735    # get database object
7736    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
7737
7738    return if !$DBObject->Prepare(
7739        SQL => "
7740            SELECT MAX(create_time)
7741            FROM ticket_history
7742            WHERE ticket_id = ?
7743               AND state_id IN (${\(join ', ', sort @List)})
7744               AND history_type_id IN  (${\(join ', ', sort @HistoryTypeIDs)})
7745            ",
7746        Bind => [ \$Param{TicketID} ],
7747    );
7748
7749    my %Data;
7750    ROW:
7751    while ( my @Row = $DBObject->FetchrowArray() ) {
7752        last ROW if !defined $Row[0];
7753        $Data{Closed} = $Row[0];
7754
7755        # cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000
7756        # and 0000-00-00 00:00:00 time stamps)
7757        $Data{Closed} =~ s/^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\..+?$/$1/;
7758    }
7759
7760    return if !$Data{Closed};
7761
7762    # get escalation properties
7763    my %Escalation = $Self->TicketEscalationPreferences(
7764        Ticket => $Param{Ticket},
7765        UserID => $Param{UserID} || 1,
7766    );
7767
7768    # create datetime object
7769    my $DateTimeObject = $Kernel::OM->Create(
7770        'Kernel::System::DateTime',
7771        ObjectParams => {
7772            String => $Param{Ticket}->{Created},
7773        }
7774    );
7775
7776    my $SolutionTimeObj = $Kernel::OM->Create(
7777        'Kernel::System::DateTime',
7778        ObjectParams => {
7779            String => $Data{Closed},
7780        }
7781    );
7782
7783    my $DeltaObj = $DateTimeObject->Delta(
7784        DateTimeObject => $SolutionTimeObj,
7785        ForWorkingTime => 1,
7786        Calendar       => $Escalation{Calendar},
7787    );
7788
7789    my $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0;
7790
7791    $Data{SolutionInMin} = int( $WorkingTime / 60 );
7792
7793    if ( $Escalation{SolutionTime} ) {
7794        my $EscalationSolutionTime = $Escalation{SolutionTime} * 60;
7795        $Data{SolutionDiffInMin} =
7796            int( ( $EscalationSolutionTime - $WorkingTime ) / 60 );
7797    }
7798
7799    return %Data;
7800}
7801
7802=head2 _TicketGetFirstLock()
7803
7804Collect first lock time for given ticket.
7805
7806    my %FirstLock = $TicketObject->_TicketGetFirstLock(
7807        TicketID => $Param{TicketID},
7808        Ticket   => \%Ticket,
7809    );
7810
7811=cut
7812
7813sub _TicketGetFirstLock {
7814    my ( $Self, %Param ) = @_;
7815
7816    # check needed stuff
7817    for my $Needed (qw(TicketID Ticket)) {
7818        if ( !defined $Param{$Needed} ) {
7819            $Kernel::OM->Get('Kernel::System::Log')->Log(
7820                Priority => 'error',
7821                Message  => "Need $Needed!"
7822            );
7823            return;
7824        }
7825    }
7826
7827    my $LockHistoryTypeID = $Self->HistoryTypeLookup( Type => 'Lock' );
7828
7829    my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
7830
7831    # get first lock
7832    return if !$DBObject->Prepare(
7833        SQL => 'SELECT create_time '
7834            . 'FROM ticket_history '
7835            . 'WHERE ticket_id = ? AND history_type_id = ? '
7836            . 'ORDER BY create_time ASC, id ASC',
7837        Bind  => [ \$Param{TicketID}, \$LockHistoryTypeID, ],
7838        Limit => 1,
7839    );
7840
7841    my %Data;
7842    while ( my @Row = $DBObject->FetchrowArray() ) {
7843        $Data{FirstLock} = $Row[0];
7844
7845        # cleanup time stamp (some databases are using e. g. '2008-02-25 22:03:00.000000' time stamps)
7846        $Data{FirstLock} =~ s{ \A ( \d{4} - \d{2} - \d{2} [ ] \d{2} : \d{2} : \d{2} ) .* \z }{$1}xms;
7847    }
7848
7849    return %Data;
7850}
7851
78521;
7853
7854=head1 TERMS AND CONDITIONS
7855
7856This software is part of the OTRS project (L<https://otrs.org/>).
7857
7858This software comes with ABSOLUTELY NO WARRANTY. For details, see
7859the enclosed file COPYING for license information (GPL). If you
7860did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
7861
7862=cut
7863