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::GenericInterface::Operation::Ticket::TicketUpdate;
10
11use strict;
12use warnings;
13
14use Kernel::System::VariableCheck qw( :all );
15
16use parent qw(
17    Kernel::GenericInterface::Operation::Common
18    Kernel::GenericInterface::Operation::Ticket::Common
19);
20
21our $ObjectManagerDisabled = 1;
22
23=head1 NAME
24
25Kernel::GenericInterface::Operation::Ticket::TicketUpdate - GenericInterface Ticket TicketUpdate Operation backend
26
27=head1 PUBLIC INTERFACE
28
29=head2 new()
30
31usually, you want to create an instance of this
32by using Kernel::GenericInterface::Operation->new();
33
34=cut
35
36sub new {
37    my ( $Type, %Param ) = @_;
38
39    my $Self = {};
40    bless( $Self, $Type );
41
42    # check needed objects
43    for my $Needed (qw( DebuggerObject WebserviceID )) {
44        if ( !$Param{$Needed} ) {
45            return {
46                Success      => 0,
47                ErrorMessage => "Got no $Needed!",
48            };
49        }
50
51        $Self->{$Needed} = $Param{$Needed};
52    }
53
54    $Self->{Config}    = $Kernel::OM->Get('Kernel::Config')->Get('GenericInterface::Operation::TicketUpdate');
55    $Self->{Operation} = $Param{Operation};
56
57    return $Self;
58}
59
60=head2 Run()
61
62perform TicketUpdate Operation. This will return the updated TicketID and
63if applicable the created ArticleID.
64
65    my $Result = $OperationObject->Run(
66        Data => {
67            UserLogin         => 'some agent login',                            # UserLogin or CustomerUserLogin or SessionID is
68                                                                                #   required
69            CustomerUserLogin => 'some customer login',
70            SessionID         => 123,
71
72            Password  => 'some password',                                       # if UserLogin or customerUserLogin is sent then
73                                                                                #   Password is required
74
75            TicketID     => 123,                                                # TicketID or TicketNumber is required
76            TicketNumber => '2004040510440485',
77
78            Ticket {                                                            # optional
79                Title      => 'some ticket title',
80
81                QueueID       => 123,                                           # Optional
82                Queue         => 'some queue name',                             # Optional
83                LockID        => 123,                                           # optional
84                Lock          => 'some lock name',                              # optional
85                TypeID        => 123,                                           # optional
86                Type          => 'some type name',                              # optional
87                ServiceID     => 123,                                           # optional
88                Service       => 'some service name',                           # optional
89                SLAID         => 123,                                           # optional
90                SLA           => 'some SLA name',                               # optional
91                StateID       => 123,                                           # optional
92                State         => 'some state name',                             # optional
93                PriorityID    => 123,                                           # optional
94                Priority      => 'some priority name',                          # optional
95                OwnerID       => 123,                                           # optional
96                Owner         => 'some user login',                             # optional
97                ResponsibleID => 123,                                           # optional
98                Responsible   => 'some user login',                             # optional
99                CustomerUser  => 'some customer user login',
100
101                PendingTime {       # optional
102                    Year   => 2011,
103                    Month  => 12
104                    Day    => 03,
105                    Hour   => 23,
106                    Minute => 05,
107                },
108                # or
109                # PendingTime {
110                #     Diff => 10080, # Pending time in minutes
111                #},
112            },
113            Article => {                                                          # optional
114                CommunicationChannel            => 'Email',                    # CommunicationChannel or CommunicationChannelID must be provided.
115                CommunicationChannelID          => 1,
116                IsVisibleForCustomer            => 1,                          # optional
117                SenderTypeID                    => 123,                        # optional
118                SenderType                      => 'some sender type name',    # optional
119                AutoResponseType                => 'some auto response type',  # optional
120                From                            => 'some from string',         # optional
121                Subject                         => 'some subject',
122                Body                            => 'some body',
123
124                ContentType                     => 'some content type',        # ContentType or MimeType and Charset is required
125                MimeType                        => 'some mime type',
126                Charset                         => 'some charset',
127
128                HistoryType                     => 'some history type',        # optional
129                HistoryComment                  => 'Some  history comment',    # optional
130                TimeUnit                        => 123,                        # optional
131                NoAgentNotify                   => 1,                          # optional
132                ForceNotificationToUserID       => [1, 2, 3]                   # optional
133                ExcludeNotificationToUserID     => [1, 2, 3]                   # optional
134                ExcludeMuteNotificationToUserID => [1, 2, 3]                   # optional
135            },
136
137            DynamicField => [                                                  # optional
138                {
139                    Name   => 'some name',
140                    Value  => $Value,                                          # value type depends on the dynamic field
141                },
142                # ...
143            ],
144            # or
145            # DynamicField {
146            #    Name   => 'some name',
147            #    Value  => $Value,
148            #},
149
150            Attachment [
151                {
152                    Content     => 'content'                                 # base64 encoded
153                    ContentType => 'some content type'
154                    Filename    => 'some fine name'
155                },
156                # ...
157            ],
158            #or
159            #Attachment {
160            #    Content     => 'content'
161            #    ContentType => 'some content type'
162            #    Filename    => 'some fine name'
163            #},
164        },
165    );
166
167    $Result = {
168        Success         => 1,                       # 0 or 1
169        ErrorMessage    => '',                      # in case of error
170        Data            => {                        # result data payload after Operation
171            TicketID    => 123,                     # Ticket  ID number in OTRS (help desk system)
172            ArticleID   => 43,                      # Article ID number in OTRS (help desk system)
173            Error => {                              # should not return errors
174                    ErrorCode    => 'TicketUpdate.ErrorCode'
175                    ErrorMessage => 'Error Description'
176            },
177
178            # If IncludeTicketData is enabled
179            Ticket => [
180                {
181                    TicketNumber       => '20101027000001',
182                    Title              => 'some title',
183                    TicketID           => 123,
184                    State              => 'some state',
185                    StateID            => 123,
186                    StateType          => 'some state type',
187                    Priority           => 'some priority',
188                    PriorityID         => 123,
189                    Lock               => 'lock',
190                    LockID             => 123,
191                    Queue              => 'some queue',
192                    QueueID            => 123,
193                    CustomerID         => 'customer_id_123',
194                    CustomerUserID     => 'customer_user_id_123',
195                    Owner              => 'some_owner_login',
196                    OwnerID            => 123,
197                    Type               => 'some ticket type',
198                    TypeID             => 123,
199                    SLA                => 'some sla',
200                    SLAID              => 123,
201                    Service            => 'some service',
202                    ServiceID          => 123,
203                    Responsible        => 'some_responsible_login',
204                    ResponsibleID      => 123,
205                    Age                => 3456,
206                    Created            => '2010-10-27 20:15:00'
207                    CreateBy           => 123,
208                    Changed            => '2010-10-27 20:15:15',
209                    ChangeBy           => 123,
210                    ArchiveFlag        => 'y',
211
212                    DynamicField => [
213                        {
214                            Name  => 'some name',
215                            Value => 'some value',
216                        },
217                    ],
218
219                    # (time stamps of expected escalations)
220                    EscalationResponseTime           (unix time stamp of response time escalation)
221                    EscalationUpdateTime             (unix time stamp of update time escalation)
222                    EscalationSolutionTime           (unix time stamp of solution time escalation)
223
224                    # (general escalation info of nearest escalation type)
225                    EscalationDestinationIn          (escalation in e. g. 1h 4m)
226                    EscalationDestinationTime        (date of escalation in unix time, e. g. 72193292)
227                    EscalationDestinationDate        (date of escalation, e. g. "2009-02-14 18:00:00")
228                    EscalationTimeWorkingTime        (seconds of working/service time till escalation, e. g. "1800")
229                    EscalationTime                   (seconds total till escalation of nearest escalation time type - response, update or solution time, e. g. "3600")
230
231                    # (detailed escalation info about first response, update and solution time)
232                    FirstResponseTimeEscalation      (if true, ticket is escalated)
233                    FirstResponseTimeNotification    (if true, notify - x% of escalation has reached)
234                    FirstResponseTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
235                    FirstResponseTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
236                    FirstResponseTimeWorkingTime     (seconds of working/service time till escalation, e. g. "1800")
237                    FirstResponseTime                (seconds total till escalation, e. g. "3600")
238
239                    UpdateTimeEscalation             (if true, ticket is escalated)
240                    UpdateTimeNotification           (if true, notify - x% of escalation has reached)
241                    UpdateTimeDestinationTime        (date of escalation in unix time, e. g. 72193292)
242                    UpdateTimeDestinationDate        (date of escalation, e. g. "2009-02-14 18:00:00")
243                    UpdateTimeWorkingTime            (seconds of working/service time till escalation, e. g. "1800")
244                    UpdateTime                       (seconds total till escalation, e. g. "3600")
245
246                    SolutionTimeEscalation           (if true, ticket is escalated)
247                    SolutionTimeNotification         (if true, notify - x% of escalation has reached)
248                    SolutionTimeDestinationTime      (date of escalation in unix time, e. g. 72193292)
249                    SolutionTimeDestinationDate      (date of escalation, e. g. "2009-02-14 18:00:00")
250                    SolutionTimeWorkingTime          (seconds of working/service time till escalation, e. g. "1800")
251                    SolutionTime                     (seconds total till escalation, e. g. "3600")
252
253                    Article => [
254                        {
255                            ArticleID
256                            From
257                            To
258                            Cc
259                            Subject
260                            Body
261                            ReplyTo
262                            MessageID
263                            InReplyTo
264                            References
265                            SenderType
266                            SenderTypeID
267                            CommunicationChannelID
268                            IsVisibleForCustomer
269                            ContentType
270                            Charset
271                            MimeType
272                            IncomingTime
273
274                            DynamicField => [
275                                {
276                                    Name  => 'some name',
277                                    Value => 'some value',
278                                },
279                            ],
280
281                            Attachment => [
282                                {
283                                    Content            => "xxxx",     # actual attachment contents, base64 enconded
284                                    ContentAlternative => "",
285                                    ContentID          => "",
286                                    ContentType        => "application/pdf",
287                                    Filename           => "StdAttachment-Test1.pdf",
288                                    Filesize           => "4.6 KBytes",
289                                    FilesizeRaw        => 4722,
290                                },
291                            ],
292                        },
293                    ],
294                },
295            ],
296        },
297    };
298
299=cut
300
301sub Run {
302    my ( $Self, %Param ) = @_;
303
304    my $Result = $Self->Init(
305        WebserviceID => $Self->{WebserviceID},
306    );
307
308    if ( !$Result->{Success} ) {
309        $Self->ReturnError(
310            ErrorCode    => 'Webservice.InvalidConfiguration',
311            ErrorMessage => $Result->{ErrorMessage},
312        );
313    }
314
315    # check needed stuff
316    if ( !IsHashRefWithData( $Param{Data} ) ) {
317        return $Self->ReturnError(
318            ErrorCode    => 'TicketUpdate.EmptyRequest',
319            ErrorMessage => "TicketUpdate: The request data is invalid!",
320        );
321    }
322
323    if ( !$Param{Data}->{TicketID} && !$Param{Data}->{TicketNumber} ) {
324        return $Self->ReturnError(
325            ErrorCode    => 'TicketUpdate.MissingParameter',
326            ErrorMessage => "TicketUpdate: TicketID or TicketNumber is required!",
327        );
328    }
329
330    if (
331        !$Param{Data}->{UserLogin}
332        && !$Param{Data}->{CustomerUserLogin}
333        && !$Param{Data}->{SessionID}
334        )
335    {
336        return $Self->ReturnError(
337            ErrorCode    => 'TicketUpdate.MissingParameter',
338            ErrorMessage => "TicketUpdate: UserLogin, CustomerUserLogin or SessionID is required!",
339        );
340    }
341
342    if ( $Param{Data}->{UserLogin} || $Param{Data}->{CustomerUserLogin} ) {
343
344        if ( !$Param{Data}->{Password} )
345        {
346            return $Self->ReturnError(
347                ErrorCode    => 'TicketUpdate.MissingParameter',
348                ErrorMessage => "TicketUpdate: Password or SessionID is required!",
349            );
350        }
351    }
352
353    # authenticate user
354    my ( $UserID, $UserType ) = $Self->Auth(%Param);
355
356    if ( !$UserID ) {
357        return $Self->ReturnError(
358            ErrorCode    => 'TicketUpdate.AuthFail',
359            ErrorMessage => "TicketUpdate: User could not be authenticated!",
360        );
361    }
362
363    my $PermissionUserID = $UserID;
364
365    if ( $UserType eq 'Customer' ) {
366        $UserID = $Kernel::OM->Get('Kernel::Config')->Get('CustomerPanelUserID');
367    }
368
369    # get ticket object
370    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
371
372    # check TicketID
373    my $TicketID;
374    if ( $Param{Data}->{TicketNumber} ) {
375        $TicketID = $TicketObject->TicketIDLookup(
376            TicketNumber => $Param{Data}->{TicketNumber},
377            UserID       => $UserID,
378        );
379
380    }
381    else {
382        $TicketID = $Param{Data}->{TicketID};
383    }
384
385    if ( !($TicketID) ) {
386        return $Self->ReturnError(
387            ErrorCode    => 'TicketUpdate.AccessDenied',
388            ErrorMessage => "TicketUpdate: User does not have access to the ticket!",
389        );
390    }
391
392    my %TicketData = $TicketObject->TicketGet(
393        TicketID      => $TicketID,
394        DynamicFields => 0,
395        UserID        => $UserID,
396    );
397
398    if ( !IsHashRefWithData( \%TicketData ) ) {
399        return $Self->ReturnError(
400            ErrorCode    => 'TicketUpdate.AccessDenied',
401            ErrorMessage => "TicketUpdate: User does not have access to the ticket!",
402        );
403    }
404
405    # check basic needed permissions
406    my $Access = $Self->CheckAccessPermissions(
407        TicketID => $TicketID,
408        UserID   => $PermissionUserID,
409        UserType => $UserType,
410    );
411
412    if ( !$Access ) {
413        return $Self->ReturnError(
414            ErrorCode    => 'TicketUpdate.AccessDenied',
415            ErrorMessage => "TicketUpdate: User does not have access to the ticket!",
416        );
417    }
418
419    # check optional hashes
420    for my $Optional (qw(Ticket Article)) {
421        if (
422            defined $Param{Data}->{$Optional}
423            && !IsHashRefWithData( $Param{Data}->{$Optional} )
424            )
425        {
426            return $Self->ReturnError(
427                ErrorCode    => 'TicketUpdate.InvalidParameter',
428                ErrorMessage => "TicketUpdate: $Optional parameter is not valid!",
429            );
430        }
431    }
432
433    # check optional array/hashes
434    for my $Optional (qw(DynamicField Attachment)) {
435        if (
436            defined $Param{Data}->{$Optional}
437            && !IsHashRefWithData( $Param{Data}->{$Optional} )
438            && !IsArrayRefWithData( $Param{Data}->{$Optional} )
439            )
440        {
441            return $Self->ReturnError(
442                ErrorCode    => 'TicketUpdate.MissingParameter',
443                ErrorMessage => "TicketUpdate: $Optional parameter is missing or not valid!",
444            );
445        }
446    }
447
448    my $Ticket;
449    if ( defined $Param{Data}->{Ticket} ) {
450
451        # isolate ticket parameter
452        $Ticket = $Param{Data}->{Ticket};
453
454        $Ticket->{UserID} = $UserID;
455
456        # remove leading and trailing spaces
457        for my $Attribute ( sort keys %{$Ticket} ) {
458            if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
459
460                #remove leading spaces
461                $Ticket->{$Attribute} =~ s{\A\s+}{};
462
463                #remove trailing spaces
464                $Ticket->{$Attribute} =~ s{\s+\z}{};
465            }
466        }
467        if ( IsHashRefWithData( $Ticket->{PendingTime} ) ) {
468            for my $Attribute ( sort keys %{ $Ticket->{PendingTime} } ) {
469                if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
470
471                    #remove leading spaces
472                    $Ticket->{PendingTime}->{$Attribute} =~ s{\A\s+}{};
473
474                    #remove trailing spaces
475                    $Ticket->{PendingTime}->{$Attribute} =~ s{\s+\z}{};
476                }
477            }
478        }
479
480        # check Ticket attribute values
481        my $TicketCheck = $Self->_CheckTicket(
482            Ticket    => $Ticket,
483            OldTicket => \%TicketData,
484        );
485
486        if ( !$TicketCheck->{Success} ) {
487            return $Self->ReturnError( %{$TicketCheck} );
488        }
489    }
490
491    my $Article;
492    if ( defined $Param{Data}->{Article} ) {
493
494        $Article = $Param{Data}->{Article};
495        $Article->{UserType} = $UserType;
496
497        # remove leading and trailing spaces
498        for my $Attribute ( sort keys %{$Article} ) {
499            if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
500
501                #remove leading spaces
502                $Article->{$Attribute} =~ s{\A\s+}{};
503
504                #remove trailing spaces
505                $Article->{$Attribute} =~ s{\s+\z}{};
506            }
507        }
508        if ( IsHashRefWithData( $Article->{OrigHeader} ) ) {
509            for my $Attribute ( sort keys %{ $Article->{OrigHeader} } ) {
510                if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
511
512                    #remove leading spaces
513                    $Article->{OrigHeader}->{$Attribute} =~ s{\A\s+}{};
514
515                    #remove trailing spaces
516                    $Article->{OrigHeader}->{$Attribute} =~ s{\s+\z}{};
517                }
518            }
519        }
520
521        # Check attributes that can be set by sysconfig.
522        if ( !$Article->{AutoResponseType} ) {
523            $Article->{AutoResponseType} = $Self->{Config}->{AutoResponseType} || '';
524        }
525
526        # TODO: GenericInterface::Operation::TicketUpdate###CommunicationChannel
527        if ( !$Article->{CommunicationChannelID} && !$Article->{CommunicationChannel} ) {
528            $Article->{CommunicationChannel} = 'Internal';
529        }
530        if ( !defined $Article->{IsVisibleForCustomer} ) {
531            $Article->{IsVisibleForCustomer} = $Self->{Config}->{IsVisibleForCustomer} // 1;
532        }
533        if ( !$Article->{SenderTypeID} && !$Article->{SenderType} ) {
534            $Article->{SenderType} = $UserType eq 'User' ? 'agent' : 'customer';
535        }
536        if ( !$Article->{HistoryType} ) {
537            $Article->{HistoryType} = $Self->{Config}->{HistoryType} || '';
538        }
539        if ( !$Article->{HistoryComment} ) {
540            $Article->{HistoryComment} = $Self->{Config}->{HistoryComment} || '';
541        }
542
543        # check Article attribute values
544        my $ArticleCheck = $Self->_CheckArticle( Article => $Article );
545
546        if ( !$ArticleCheck->{Success} ) {
547            if ( !$ArticleCheck->{ErrorCode} ) {
548                return {
549                    Success => 0,
550                    %{$ArticleCheck},
551                };
552            }
553            return $Self->ReturnError( %{$ArticleCheck} );
554        }
555    }
556
557    my $DynamicField;
558    my @DynamicFieldList;
559    if ( defined $Param{Data}->{DynamicField} ) {
560
561        # isolate DynamicField parameter
562        $DynamicField = $Param{Data}->{DynamicField};
563
564        # homogenate input to array
565        if ( ref $DynamicField eq 'HASH' ) {
566            push @DynamicFieldList, $DynamicField;
567        }
568        else {
569            @DynamicFieldList = @{$DynamicField};
570        }
571
572        # check DynamicField internal structure
573        for my $DynamicFieldItem (@DynamicFieldList) {
574            if ( !IsHashRefWithData($DynamicFieldItem) ) {
575                return {
576                    ErrorCode => 'TicketUpdate.InvalidParameter',
577                    ErrorMessage =>
578                        "TicketUpdate: Ticket->DynamicField parameter is invalid!",
579                };
580            }
581
582            # remove leading and trailing spaces
583            for my $Attribute ( sort keys %{$DynamicFieldItem} ) {
584                if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
585
586                    #remove leading spaces
587                    $DynamicFieldItem->{$Attribute} =~ s{\A\s+}{};
588
589                    #remove trailing spaces
590                    $DynamicFieldItem->{$Attribute} =~ s{\s+\z}{};
591                }
592            }
593
594            # check DynamicField attribute values
595            my $DynamicFieldCheck = $Self->_CheckDynamicField(
596                DynamicField => $DynamicFieldItem,
597                Article      => $Article,
598            );
599
600            if ( !$DynamicFieldCheck->{Success} ) {
601                return $Self->ReturnError( %{$DynamicFieldCheck} );
602            }
603        }
604    }
605
606    my $Attachment;
607    my @AttachmentList;
608    if ( defined $Param{Data}->{Attachment} ) {
609
610        # isolate Attachment parameter
611        $Attachment = $Param{Data}->{Attachment};
612
613        # homogenate input to array
614        if ( ref $Attachment eq 'HASH' ) {
615            push @AttachmentList, $Attachment;
616        }
617        else {
618            @AttachmentList = @{$Attachment};
619        }
620
621        # check Attachment internal structure
622        for my $AttachmentItem (@AttachmentList) {
623            if ( !IsHashRefWithData($AttachmentItem) ) {
624                return {
625                    ErrorCode => 'TicketUpdate.InvalidParameter',
626                    ErrorMessage =>
627                        "TicketUpdate: Ticket->Attachment parameter is invalid!",
628                };
629            }
630
631            # remove leading and trailing spaces
632            for my $Attribute ( sort keys %{$AttachmentItem} ) {
633                if ( ref $Attribute ne 'HASH' && ref $Attribute ne 'ARRAY' ) {
634
635                    #remove leading spaces
636                    $AttachmentItem->{$Attribute} =~ s{\A\s+}{};
637
638                    #remove trailing spaces
639                    $AttachmentItem->{$Attribute} =~ s{\s+\z}{};
640                }
641            }
642
643            # check Attachment attribute values
644            my $AttachmentCheck = $Self->_CheckAttachment(
645                Attachment => $AttachmentItem,
646                Article    => $Article,
647            );
648
649            if ( !$AttachmentCheck->{Success} ) {
650                return $Self->ReturnError( %{$AttachmentCheck} );
651            }
652        }
653    }
654
655    return $Self->_TicketUpdate(
656        TicketID         => $TicketID,
657        Ticket           => $Ticket,
658        Article          => $Article,
659        DynamicFieldList => \@DynamicFieldList,
660        AttachmentList   => \@AttachmentList,
661        UserID           => $UserID,
662        UserType         => $UserType,
663    );
664}
665
666=begin Internal:
667
668=head2 _CheckTicket()
669
670checks if the given ticket parameters are valid.
671
672    my $TicketCheck = $OperationObject->_CheckTicket(
673        Ticket => $Ticket,                          # all ticket parameters
674    );
675
676    returns:
677
678    $TicketCheck = {
679        Success => 1,                               # if everything is OK
680    }
681
682    $TicketCheck = {
683        ErrorCode    => 'Function.Error',           # if error
684        ErrorMessage => 'Error description',
685    }
686
687=cut
688
689sub _CheckTicket {
690    my ( $Self, %Param ) = @_;
691
692    my $Ticket    = $Param{Ticket};
693    my $OldTicket = $Param{OldTicket};
694
695    # check Ticket->CustomerUser
696    if (
697        $Ticket->{CustomerUser}
698        && !$Self->ValidateCustomer( %{$Ticket} )
699        )
700    {
701        return {
702            ErrorCode => 'TicketUpdate.InvalidParameter',
703            ErrorMessage =>
704                "TicketUpdate: Ticket->CustomerUser parameter is invalid!",
705        };
706    }
707
708    # check Ticket->Queue
709    if ( $Ticket->{QueueID} || $Ticket->{Queue} ) {
710        if ( !$Self->ValidateQueue( %{$Ticket} ) ) {
711            return {
712                ErrorCode    => 'TicketUpdate.InvalidParameter',
713                ErrorMessage => "TicketUpdate: Ticket->QueueID or Ticket->Queue parameter is"
714                    . " invalid!",
715            };
716        }
717    }
718
719    # check Ticket->Lock
720    if ( $Ticket->{LockID} || $Ticket->{Lock} ) {
721        if ( !$Self->ValidateLock( %{$Ticket} ) ) {
722            return {
723                ErrorCode    => 'TicketUpdate.InvalidParameter',
724                ErrorMessage => "TicketUpdate: Ticket->LockID or Ticket->Lock parameter is"
725                    . " invalid!",
726            };
727        }
728    }
729
730    # check Ticket->Type
731    if ( $Ticket->{TypeID} || $Ticket->{Type} ) {
732        if ( !$Self->ValidateType( %{$Ticket} ) ) {
733            return {
734                ErrorCode => 'TicketUpdate.InvalidParameter',
735                ErrorMessage =>
736                    "TicketUpdate: Ticket->TypeID or Ticket->Type parameter is invalid!",
737            };
738        }
739    }
740
741    # check Ticket->Service
742    if ( $Ticket->{ServiceID} || $Ticket->{Service} ) {
743
744        # set customer user from old ticket if no new customer user is to be set
745        my $CustomerUser = $Ticket->{CustomerUser} || '';
746        if ( !$CustomerUser ) {
747            $CustomerUser = $OldTicket->{CustomerUserID};
748        }
749        if (
750            !$Self->ValidateService(
751                %{$Ticket},
752                CustomerUser => $CustomerUser,
753            )
754            )
755        {
756            return {
757                ErrorCode => 'TicketUpdate.InvalidParameter',
758                ErrorMessage =>
759                    "TicketUpdate: Ticket->ServiceID or Ticket->Service parameter is invalid!",
760            };
761        }
762    }
763
764    # check Ticket->SLA
765    if ( $Ticket->{SLAID} || $Ticket->{SLA} ) {
766
767        # set ServiceID from old ticket if no new ServiceID or Service is to be set
768        my $Service   = $Ticket->{Service}   || '';
769        my $ServiceID = $Ticket->{ServiceID} || '';
770        if ( !$ServiceID && !$Service ) {
771            $ServiceID = $OldTicket->{ServiceID};
772        }
773
774        if (
775            !$Self->ValidateSLA(
776                %{$Ticket},
777                Service   => $Service,
778                ServiceID => $ServiceID,
779            )
780            )
781        {
782            return {
783                ErrorCode => 'TicketUpdate.InvalidParameter',
784                ErrorMessage =>
785                    "TicketUpdate: Ticket->SLAID or Ticket->SLA parameter is invalid!",
786            };
787        }
788    }
789
790    # check Ticket->State
791    if ( $Ticket->{StateID} || $Ticket->{State} ) {
792        if ( !$Self->ValidateState( %{$Ticket} ) ) {
793            return {
794                ErrorCode    => 'TicketUpdate.InvalidParameter',
795                ErrorMessage => "TicketUpdate: Ticket->StateID or Ticket->State parameter is"
796                    . " invalid!",
797            };
798        }
799    }
800
801    # check Ticket->Priority
802    if ( $Ticket->{PriorityID} || $Ticket->{Priority} ) {
803        if ( !$Self->ValidatePriority( %{$Ticket} ) ) {
804            return {
805                ErrorCode    => 'TicketUpdate.InvalidParameter',
806                ErrorMessage => "TicketUpdate: Ticket->PriorityID or Ticket->Priority parameter is"
807                    . " invalid!",
808            };
809        }
810    }
811
812    # check Ticket->Owner
813    if ( $Ticket->{OwnerID} || $Ticket->{Owner} ) {
814        if ( !$Self->ValidateOwner( %{$Ticket} ) ) {
815            return {
816                ErrorCode => 'TicketUpdate.InvalidParameter',
817                ErrorMessage =>
818                    "TicketUpdate: Ticket->OwnerID or Ticket->Owner parameter is invalid!",
819            };
820        }
821    }
822
823    # check Ticket->Responsible
824    if ( $Ticket->{ResponsibleID} || $Ticket->{Responsible} ) {
825        if ( !$Self->ValidateResponsible( %{$Ticket} ) ) {
826            return {
827                ErrorCode    => 'TicketUpdate.InvalidParameter',
828                ErrorMessage => "TicketUpdate: Ticket->ResponsibleID or Ticket->Responsible"
829                    . " parameter is invalid!",
830            };
831        }
832    }
833
834    # check Ticket->PendingTime
835    if ( $Ticket->{PendingTime} ) {
836        if ( !$Self->ValidatePendingTime( %{$Ticket} ) ) {
837            return {
838                ErrorCode    => 'TicketUpdate.InvalidParameter',
839                ErrorMessage => "TicketUpdate: Ticket->PendingTime parameter is invalid!",
840            };
841        }
842    }
843
844    # if everything is OK then return Success
845    return {
846        Success => 1,
847    };
848}
849
850=head2 _CheckArticle()
851
852checks if the given article parameter is valid.
853
854    my $ArticleCheck = $OperationObject->_CheckArticle(
855        Article => $Article,                        # all article parameters
856    );
857
858    returns:
859
860    $ArticleCheck = {
861        Success => 1,                               # if everything is OK
862    }
863
864    $ArticleCheck = {
865        ErrorCode    => 'Function.Error',           # if error
866        ErrorMessage => 'Error description',
867    }
868
869=cut
870
871sub _CheckArticle {
872    my ( $Self, %Param ) = @_;
873
874    my $Article = $Param{Article};
875
876    # check ticket internally
877    for my $Needed (qw(Subject Body AutoResponseType)) {
878        if ( !$Article->{$Needed} ) {
879            return {
880                ErrorCode    => 'TicketUpdate.MissingParameter',
881                ErrorMessage => "TicketUpdate: Article->$Needed parameter is missing!",
882            };
883        }
884    }
885
886    # check Article->AutoResponseType
887    if ( !$Article->{AutoResponseType} ) {
888
889        # return internal server error
890        return {
891            ErrorMessage => "TicketUpdate: Article->AutoResponseType parameter is required and",
892        };
893    }
894
895    if ( !$Self->ValidateAutoResponseType( %{$Article} ) ) {
896        return {
897            ErrorCode    => 'TicketUpdate.InvalidParameter',
898            ErrorMessage => "TicketUpdate: Article->AutoResponseType parameter is invalid!",
899        };
900    }
901
902    # check Article->CommunicationChannel
903    if ( !$Article->{CommunicationChannel} && !$Article->{CommunicationChannelID} ) {
904
905        # return internal server error
906        return {
907            ErrorMessage => "TicketUpdate: Article->CommunicationChannelID or Article->CommunicationChannel parameter"
908                . " is required and Sysconfig CommunicationChannelID setting could not be read!"
909        };
910    }
911    if ( !$Self->ValidateArticleCommunicationChannel( %{$Article} ) ) {
912        return {
913            ErrorCode    => 'TicketUpdate.InvalidParameter',
914            ErrorMessage => "TicketUpdate: Article->CommunicationChannel or Article->CommunicationChannelID parameter"
915                . " is invalid or not supported!",
916        };
917    }
918
919    # check Article->SenderType
920    if ( !$Article->{SenderTypeID} && !$Article->{SenderType} ) {
921
922        # return internal server error
923        return {
924            ErrorMessage => "TicketUpdate: Article->SenderTypeID or Article->SenderType parameter"
925                . " is required and Sysconfig SenderTypeID setting could not be read!"
926        };
927    }
928    if ( !$Self->ValidateSenderType( %{$Article} ) ) {
929        return {
930            ErrorCode    => 'TicketUpdate.InvalidParameter',
931            ErrorMessage => "TicketUpdate: Article->SenderTypeID or Ticket->SenderType parameter"
932                . " is invalid!",
933        };
934    }
935
936    # check Article->From
937    if ( $Article->{From} ) {
938        if ( !$Self->ValidateFrom( %{$Article} ) ) {
939            return {
940                ErrorCode    => 'TicketUpdate.InvalidParameter',
941                ErrorMessage => "TicketUpdate: Article->From parameter is invalid!",
942            };
943        }
944    }
945
946    # check Article->ContentType vs Article->MimeType and Article->Charset
947    if ( !$Article->{ContentType} && !$Article->{MimeType} && !$Article->{Charset} ) {
948        return {
949            ErrorCode    => 'TicketUpdate.MissingParameter',
950            ErrorMessage => "TicketUpdate: Article->ContentType or Ticket->MimeType and"
951                . " Article->Charset parameters are required!",
952        };
953    }
954
955    if ( $Article->{MimeType} && !$Article->{Charset} ) {
956        return {
957            ErrorCode    => 'TicketUpdate.MissingParameter',
958            ErrorMessage => "TicketUpdate: Article->Charset is required!",
959        };
960    }
961
962    if ( $Article->{Charset} && !$Article->{MimeType} ) {
963        return {
964            ErrorCode    => 'TicketUpdate.MissingParameter',
965            ErrorMessage => "TicketUpdate: Article->MimeType is required!",
966        };
967    }
968
969    # check Article->MimeType
970    if ( $Article->{MimeType} ) {
971
972        $Article->{MimeType} = lc $Article->{MimeType};
973
974        if ( !$Self->ValidateMimeType( %{$Article} ) ) {
975            return {
976                ErrorCode    => 'TicketUpdate.InvalidParameter',
977                ErrorMessage => "TicketUpdate: Article->MimeType is invalid!",
978            };
979        }
980    }
981
982    # check Article->MimeType
983    if ( $Article->{Charset} ) {
984
985        $Article->{Charset} = lc $Article->{Charset};
986
987        if ( !$Self->ValidateCharset( %{$Article} ) ) {
988            return {
989                ErrorCode    => 'TicketUpdate.InvalidParameter',
990                ErrorMessage => "TicketUpdate: Article->Charset is invalid!",
991            };
992        }
993    }
994
995    # check Article->ContentType
996    if ( $Article->{ContentType} ) {
997
998        $Article->{ContentType} = lc $Article->{ContentType};
999
1000        # check Charset part
1001        my $Charset = '';
1002        if ( $Article->{ContentType} =~ /charset=/i ) {
1003            $Charset = $Article->{ContentType};
1004            $Charset =~ s/.+?charset=("|'|)(\w+)/$2/gi;
1005            $Charset =~ s/"|'//g;
1006            $Charset =~ s/(.+?);.*/$1/g;
1007        }
1008
1009        if ( !$Self->ValidateCharset( Charset => $Charset ) ) {
1010            return {
1011                ErrorCode    => 'TicketUpdate.InvalidParameter',
1012                ErrorMessage => "TicketUpdate: Article->ContentType is invalid!",
1013            };
1014        }
1015
1016        # check MimeType part
1017        my $MimeType = '';
1018        if ( $Article->{ContentType} =~ /^(\w+\/\w+)/i ) {
1019            $MimeType = $1;
1020            $MimeType =~ s/"|'//g;
1021        }
1022
1023        if ( !$Self->ValidateMimeType( MimeType => $MimeType ) ) {
1024            return {
1025                ErrorCode    => 'TicketUpdate.InvalidParameter',
1026                ErrorMessage => "TicketUpdate: Article->ContentType is invalid!",
1027            };
1028        }
1029    }
1030
1031    # check Article->HistoryType
1032    if ( !$Article->{HistoryType} ) {
1033
1034        # return internal server error
1035        return {
1036            ErrorMessage => "TicketUpdate: Article-> HistoryType is required and Sysconfig"
1037                . " HistoryType setting could not be read!"
1038        };
1039    }
1040    if ( !$Self->ValidateHistoryType( %{$Article} ) ) {
1041        return {
1042            ErrorCode    => 'TicketUpdate.InvalidParameter',
1043            ErrorMessage => "TicketUpdate: Article->HistoryType parameter is invalid!",
1044        };
1045    }
1046
1047    # check Article->HistoryComment
1048    if ( !$Article->{HistoryComment} ) {
1049
1050        # return internal server error
1051        return {
1052            ErrorMessage => "TicketUpdate: Article->HistoryComment is required and Sysconfig"
1053                . " HistoryComment setting could not be read!"
1054        };
1055    }
1056
1057    # get config object
1058    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1059
1060    # check Article->TimeUnit
1061    # TimeUnit could be required or not depending on sysconfig option
1062    if (
1063        ( !defined $Article->{TimeUnit} || !IsStringWithData( $Article->{TimeUnit} ) )
1064        && $ConfigObject->{'Ticket::Frontend::AccountTime'}
1065        && $ConfigObject->{'Ticket::Frontend::NeedAccountedTime'}
1066        )
1067    {
1068        return {
1069            ErrorCode    => 'TicketUpdate.MissingParameter',
1070            ErrorMessage => "TicketUpdate: Article->TimeUnit is required by sysconfig option!",
1071        };
1072    }
1073    if ( $Article->{TimeUnit} ) {
1074        if ( !$Self->ValidateTimeUnit( %{$Article} ) ) {
1075            return {
1076                ErrorCode    => 'TicketUpdate.InvalidParameter',
1077                ErrorMessage => "TicketUpdate: Article->TimeUnit parameter is invalid!",
1078            };
1079        }
1080    }
1081
1082    # check Article->NoAgentNotify
1083    if ( $Article->{NoAgentNotify} && $Article->{NoAgentNotify} ne '1' ) {
1084        return {
1085            ErrorCode    => 'TicketUpdate.InvalidParameter',
1086            ErrorMessage => "TicketUpdate: Article->NoAgent parameter is invalid!",
1087        };
1088    }
1089
1090    # check Article array parameters
1091    for my $Attribute (
1092        qw( ForceNotificationToUserID ExcludeNotificationToUserID ExcludeMuteNotificationToUserID )
1093        )
1094    {
1095        if ( defined $Article->{$Attribute} ) {
1096
1097            # check structure
1098            if ( IsHashRefWithData( $Article->{$Attribute} ) ) {
1099                return {
1100                    ErrorCode    => 'TicketUpdate.InvalidParameter',
1101                    ErrorMessage => "TicketUpdate: Article->$Attribute parameter is invalid!",
1102                };
1103            }
1104            else {
1105                if ( !IsArrayRefWithData( $Article->{$Attribute} ) ) {
1106                    $Article->{$Attribute} = [ $Article->{$Attribute} ];
1107                }
1108                for my $UserID ( @{ $Article->{$Attribute} } ) {
1109                    if ( !$Self->ValidateUserID( UserID => $UserID ) ) {
1110                        return {
1111                            ErrorCode    => 'TicketUpdate.InvalidParameter',
1112                            ErrorMessage => "TicketUpdate: Article->$Attribute UserID=$UserID"
1113                                . " parameter is invalid!",
1114                        };
1115                    }
1116                }
1117            }
1118        }
1119    }
1120
1121    # if everything is OK then return Success
1122    return {
1123        Success => 1,
1124    };
1125}
1126
1127=head2 _CheckDynamicField()
1128
1129checks if the given dynamic field parameter is valid.
1130
1131    my $DynamicFieldCheck = $OperationObject->_CheckDynamicField(
1132        DynamicField => $DynamicField,              # all dynamic field parameters
1133    );
1134
1135    returns:
1136
1137    $DynamicFieldCheck = {
1138        Success => 1,                               # if everything is OK
1139    }
1140
1141    $DynamicFieldCheck = {
1142        ErrorCode    => 'Function.Error',           # if error
1143        ErrorMessage => 'Error description',
1144    }
1145
1146=cut
1147
1148sub _CheckDynamicField {
1149    my ( $Self, %Param ) = @_;
1150
1151    my $DynamicField = $Param{DynamicField};
1152    my $ArticleData  = $Param{Article};
1153
1154    my $Article;
1155    if ( IsHashRefWithData($ArticleData) ) {
1156        $Article = 1;
1157    }
1158
1159    # check DynamicField item internally
1160    for my $Needed (qw(Name Value)) {
1161        if (
1162            !defined $DynamicField->{$Needed}
1163            || ( !IsString( $DynamicField->{$Needed} ) && ref $DynamicField->{$Needed} ne 'ARRAY' )
1164            )
1165        {
1166            return {
1167                ErrorCode    => 'TicketUpdate.MissingParameter',
1168                ErrorMessage => "TicketUpdate: DynamicField->$Needed parameter is missing!",
1169            };
1170        }
1171    }
1172
1173    # check DynamicField->Name
1174    if ( !$Self->ValidateDynamicFieldName( %{$DynamicField} ) ) {
1175        return {
1176            ErrorCode    => 'TicketUpdate.InvalidParameter',
1177            ErrorMessage => "TicketUpdate: DynamicField->Name parameter is invalid!",
1178        };
1179    }
1180
1181    # check objectType for dynamic field
1182    if (
1183        !$Self->ValidateDynamicFieldObjectType(
1184            %{$DynamicField},
1185            Article => $Article,
1186        )
1187        )
1188    {
1189        return {
1190            ErrorCode => 'TicketUpdate.MissingParameter',
1191            ErrorMessage =>
1192                "TicketUpdate: To create an article DynamicField an article is required!",
1193        };
1194    }
1195
1196    # check DynamicField->Value
1197    if ( !$Self->ValidateDynamicFieldValue( %{$DynamicField} ) ) {
1198        return {
1199            ErrorCode    => 'TicketUpdate.InvalidParameter',
1200            ErrorMessage => "TicketUpdate: DynamicField->Value parameter is invalid!",
1201        };
1202    }
1203
1204    # if everything is OK then return Success
1205    return {
1206        Success => 1,
1207    };
1208}
1209
1210=head2 _CheckAttachment()
1211
1212checks if the given attachment parameter is valid.
1213
1214    my $AttachmentCheck = $OperationObject->_CheckAttachment(
1215        Attachment => $Attachment,                  # all attachment parameters
1216    );
1217
1218    returns:
1219
1220    $AttachmentCheck = {
1221        Success => 1,                               # if everything is OK
1222    }
1223
1224    $AttachmentCheck = {
1225        ErrorCode    => 'Function.Error',           # if error
1226        ErrorMessage => 'Error description',
1227    }
1228
1229=cut
1230
1231sub _CheckAttachment {
1232    my ( $Self, %Param ) = @_;
1233
1234    my $Attachment = $Param{Attachment};
1235    my $Article    = $Param{Article};
1236
1237    # check if article is going to be created
1238    if ( !IsHashRefWithData($Article) ) {
1239        return {
1240            ErrorCode    => 'TicketUpdate.MissingParameter',
1241            ErrorMessage => "TicketUpdate: To create an attachment an article is needed!",
1242        };
1243    }
1244
1245    # check attachment item internally
1246    for my $Needed (qw(Content ContentType Filename)) {
1247        if ( !IsStringWithData( $Attachment->{$Needed} ) ) {
1248            return {
1249                ErrorCode    => 'TicketUpdate.MissingParameter',
1250                ErrorMessage => "TicketUpdate: Attachment->$Needed parameter is missing!",
1251            };
1252        }
1253    }
1254
1255    # check Article->ContentType
1256    if ( $Attachment->{ContentType} ) {
1257
1258        $Attachment->{ContentType} = lc $Attachment->{ContentType};
1259
1260        # check Charset part
1261        my $Charset = '';
1262        if ( $Attachment->{ContentType} =~ /charset=/i ) {
1263            $Charset = $Attachment->{ContentType};
1264            $Charset =~ s/.+?charset=("|'|)(\w+)/$2/gi;
1265            $Charset =~ s/"|'//g;
1266            $Charset =~ s/(.+?);.*/$1/g;
1267        }
1268
1269        if ( $Charset && !$Self->ValidateCharset( Charset => $Charset ) ) {
1270            return {
1271                ErrorCode    => 'TicketUpdate.InvalidParameter',
1272                ErrorMessage => "TicketUpdate: Attachment->ContentType is invalid!",
1273            };
1274        }
1275
1276        # check MimeType part
1277        my $MimeType = '';
1278        if ( $Attachment->{ContentType} =~ /^(\w+\/\w+)/i ) {
1279            $MimeType = $1;
1280            $MimeType =~ s/"|'//g;
1281        }
1282
1283        if ( !$Self->ValidateMimeType( MimeType => $MimeType ) ) {
1284            return {
1285                ErrorCode    => 'TicketUpdate.InvalidParameter',
1286                ErrorMessage => "TicketUpdate: Attachment->ContentType is invalid!",
1287            };
1288        }
1289    }
1290
1291    # if everything is OK then return Success
1292    return {
1293        Success => 1,
1294    };
1295}
1296
1297=head2 _CheckUpdatePermissions()
1298
1299check if user has permissions to update ticket attributes.
1300
1301    my $Response = $OperationObject->_CheckUpdatePermissions(
1302        TicketID     => 123
1303        Ticket       => $Ticket,                  # all ticket parameters
1304        Article      => $Ticket,                  # all attachment parameters
1305        DynamicField => $Ticket,                  # all dynamic field parameters
1306        Attachment   => $Ticket,                  # all attachment parameters
1307        UserID       => 123,
1308    );
1309
1310    returns:
1311
1312    $Response = {
1313        Success => 1,                               # if everything is OK
1314    }
1315
1316    $Response = {
1317        Success      => 0,
1318        ErrorCode    => "function.error",           # if error
1319        ErrorMessage => "Error description"
1320    }
1321
1322=cut
1323
1324sub _CheckUpdatePermissions {
1325    my ( $Self, %Param ) = @_;
1326
1327    my $TicketID         = $Param{TicketID};
1328    my $Ticket           = $Param{Ticket};
1329    my $Article          = $Param{Article};
1330    my $DynamicFieldList = $Param{DynamicFieldList};
1331    my $AttachmentList   = $Param{AttachmentList};
1332
1333    # get ticket object
1334    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
1335
1336    # check Article permissions
1337    if ( IsHashRefWithData($Article) ) {
1338        my $Access = $TicketObject->TicketPermission(
1339            Type     => 'note',
1340            TicketID => $TicketID,
1341            UserID   => $Param{UserID},
1342        );
1343        if ( !$Access ) {
1344            return {
1345                ErrorCode    => 'TicketUpdate.AccessDenied',
1346                ErrorMessage => "TicketUpdate: Does not have permissions to create new articles!",
1347            };
1348        }
1349    }
1350
1351    # check dynamic field permissions
1352    if ( IsArrayRefWithData($DynamicFieldList) ) {
1353        my $Access = $TicketObject->TicketPermission(
1354            Type     => 'rw',
1355            TicketID => $TicketID,
1356            UserID   => $Param{UserID},
1357        );
1358        if ( !$Access ) {
1359            return {
1360                ErrorCode    => 'TicketUpdate.AccessDenied',
1361                ErrorMessage => "TicketUpdate: Does not have permissions to update dynamic fields!",
1362            };
1363        }
1364    }
1365
1366    # check queue permissions
1367    if ( $Ticket->{Queue} || $Ticket->{QueueID} ) {
1368        my $Access = $TicketObject->TicketPermission(
1369            Type     => 'move',
1370            TicketID => $TicketID,
1371            UserID   => $Param{UserID},
1372        );
1373        if ( !$Access ) {
1374            return {
1375                ErrorCode    => 'TicketUpdate.AccessDenied',
1376                ErrorMessage => "TicketUpdate: Does not have permissions to update queue!",
1377            };
1378        }
1379    }
1380
1381    # check owner permissions
1382    if ( $Ticket->{Owner} || $Ticket->{OwnerID} ) {
1383        my $Access = $TicketObject->TicketPermission(
1384            Type     => 'owner',
1385            TicketID => $TicketID,
1386            UserID   => $Param{UserID},
1387        );
1388        if ( !$Access ) {
1389            return {
1390                ErrorCode    => 'TicketUpdate.AccessDenied',
1391                ErrorMessage => "TicketUpdate: Does not have permissions to update owner!",
1392            };
1393        }
1394    }
1395
1396    # check responsible permissions
1397    if ( $Ticket->{Responsible} || $Ticket->{ResponsibleID} ) {
1398        my $Access = $TicketObject->TicketPermission(
1399            Type     => 'responsible',
1400            TicketID => $TicketID,
1401            UserID   => $Param{UserID},
1402        );
1403        if ( !$Access ) {
1404            return {
1405                ErrorCode    => 'TicketUpdate.AccessDenied',
1406                ErrorMessage => "TicketUpdate: Does not have permissions to update responsibe!",
1407            };
1408        }
1409    }
1410
1411    # check priority permissions
1412    if ( $Ticket->{Priority} || $Ticket->{PriorityID} ) {
1413        my $Access = $TicketObject->TicketPermission(
1414            Type     => 'priority',
1415            TicketID => $TicketID,
1416            UserID   => $Param{UserID},
1417        );
1418        if ( !$Access ) {
1419            return {
1420                ErrorCode    => 'TicketUpdate.AccessDenied',
1421                ErrorMessage => "TicketUpdate: Does not have permissions to update priority!",
1422            };
1423        }
1424    }
1425
1426    # check state permissions
1427    if ( $Ticket->{State} || $Ticket->{StateID} ) {
1428
1429        # get State Data
1430        my %StateData;
1431        my $StateID;
1432
1433        # get state object
1434        my $StateObject = $Kernel::OM->Get('Kernel::System::State');
1435
1436        if ( $Ticket->{StateID} ) {
1437            $StateID = $Ticket->{StateID};
1438        }
1439        else {
1440            $StateID = $StateObject->StateLookup(
1441                State => $Ticket->{State},
1442            );
1443        }
1444
1445        %StateData = $StateObject->StateGet(
1446            ID => $StateID,
1447        );
1448
1449        my $Access = 1;
1450
1451        if ( $StateData{TypeName} =~ /^close/i ) {
1452            $Access = $TicketObject->TicketPermission(
1453                Type     => 'close',
1454                TicketID => $TicketID,
1455                UserID   => $Param{UserID},
1456            );
1457        }
1458
1459        # set pending time
1460        elsif ( $StateData{TypeName} =~ /^pending/i ) {
1461            $Access = $TicketObject->TicketPermission(
1462                Type     => 'close',
1463                TicketID => $TicketID,
1464                UserID   => $Param{UserID},
1465            );
1466        }
1467        if ( !$Access ) {
1468            return {
1469                ErrorCode    => 'TicketUpdate.AccessDenied',
1470                ErrorMessage => "TicketUpdate: Does not have permissions to update state!",
1471            };
1472        }
1473    }
1474
1475    return {
1476        Success => 1,
1477    };
1478}
1479
1480=head2 _TicketUpdate()
1481
1482updates a ticket and creates an article and sets dynamic fields and attachments if specified.
1483
1484    my $Response = $OperationObject->_TicketUpdate(
1485        TicketID     => 123
1486        Ticket       => $Ticket,                  # all ticket parameters
1487        Article      => $Article,                 # all attachment parameters
1488        DynamicField => $DynamicField,            # all dynamic field parameters
1489        Attachment   => $Attachment,              # all attachment parameters
1490        UserID       => 123,
1491        UserType     => 'Agent'                   # || 'Customer
1492    );
1493
1494    returns:
1495
1496    $Response = {
1497        Success => 1,                               # if everything is OK
1498        Data => {
1499            TicketID     => 123,
1500            TicketNumber => 'TN3422332',
1501            ArticleID    => 123,                    # if new article was created
1502        }
1503    }
1504
1505    $Response = {
1506        Success      => 0,                         # if unexpected error
1507        ErrorMessage => "$Param{ErrorCode}: $Param{ErrorMessage}",
1508    }
1509
1510=cut
1511
1512sub _TicketUpdate {
1513    my ( $Self, %Param ) = @_;
1514
1515    my $TicketID         = $Param{TicketID};
1516    my $Ticket           = $Param{Ticket};
1517    my $Article          = $Param{Article};
1518    my $DynamicFieldList = $Param{DynamicFieldList};
1519    my $AttachmentList   = $Param{AttachmentList};
1520
1521    my $Access = $Self->_CheckUpdatePermissions(%Param);
1522
1523    # if no permissions return error
1524    if ( !$Access->{Success} ) {
1525        return $Self->ReturnError( %{$Access} );
1526    }
1527
1528    my %CustomerUserData;
1529
1530    # get customer information
1531    if ( $Ticket->{CustomerUser} ) {
1532        %CustomerUserData = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerUserDataGet(
1533            User => $Ticket->{CustomerUser},
1534        );
1535    }
1536
1537    # get ticket object
1538    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
1539
1540    # get current ticket data
1541    my %TicketData = $TicketObject->TicketGet(
1542        TicketID      => $TicketID,
1543        DynamicFields => 0,
1544        UserID        => $Param{UserID},
1545    );
1546
1547    # update ticket parameters
1548    # update Ticket->Title
1549    if (
1550        defined $Ticket->{Title}
1551        && $Ticket->{Title} ne ''
1552        && $Ticket->{Title} ne $TicketData{Title}
1553        )
1554    {
1555        my $Success = $TicketObject->TicketTitleUpdate(
1556            Title    => $Ticket->{Title},
1557            TicketID => $TicketID,
1558            UserID   => $Param{UserID},
1559        );
1560        if ( !$Success ) {
1561            return {
1562                Success => 0,
1563                Errormessage =>
1564                    'Ticket title could not be updated, please contact system administrator!',
1565            };
1566        }
1567    }
1568
1569    # update Ticket->Queue
1570    if ( $Ticket->{Queue} || $Ticket->{QueueID} ) {
1571        my $Success;
1572        if ( defined $Ticket->{Queue} && $Ticket->{Queue} ne $TicketData{Queue} ) {
1573            $Success = $TicketObject->TicketQueueSet(
1574                Queue    => $Ticket->{Queue},
1575                TicketID => $TicketID,
1576                UserID   => $Param{UserID},
1577            );
1578        }
1579        elsif ( defined $Ticket->{QueueID} && $Ticket->{QueueID} ne $TicketData{QueueID} ) {
1580            $Success = $TicketObject->TicketQueueSet(
1581                QueueID  => $Ticket->{QueueID},
1582                TicketID => $TicketID,
1583                UserID   => $Param{UserID},
1584            );
1585        }
1586        else {
1587
1588            # data is the same as in ticket nothing to do
1589            $Success = 1;
1590        }
1591
1592        if ( !$Success ) {
1593            return {
1594                Success => 0,
1595                ErrorMessage =>
1596                    'Ticket queue could not be updated, please contact system administrator!',
1597            };
1598        }
1599    }
1600
1601    # update Ticket->Lock
1602    if ( $Ticket->{Lock} || $Ticket->{LockID} ) {
1603        my $Success;
1604        if ( defined $Ticket->{Lock} && $Ticket->{Lock} ne $TicketData{Lock} ) {
1605            $Success = $TicketObject->TicketLockSet(
1606                Lock     => $Ticket->{Lock},
1607                TicketID => $TicketID,
1608                UserID   => $Param{UserID},
1609            );
1610        }
1611        elsif ( defined $Ticket->{LockID} && $Ticket->{LockID} ne $TicketData{LockID} ) {
1612            $Success = $TicketObject->TicketLockSet(
1613                LockID   => $Ticket->{LockID},
1614                TicketID => $TicketID,
1615                UserID   => $Param{UserID},
1616            );
1617        }
1618        else {
1619
1620            # data is the same as in ticket nothing to do
1621            $Success = 1;
1622        }
1623
1624        if ( !$Success ) {
1625            return {
1626                Success => 0,
1627                Errormessage =>
1628                    'Ticket lock could not be updated, please contact system administrator!',
1629            };
1630        }
1631    }
1632
1633    # update Ticket->Type
1634    if ( $Ticket->{Type} || $Ticket->{TypeID} ) {
1635        my $Success;
1636        if ( defined $Ticket->{Type} && $Ticket->{Type} ne $TicketData{Type} ) {
1637            $Success = $TicketObject->TicketTypeSet(
1638                Type     => $Ticket->{Type},
1639                TicketID => $TicketID,
1640                UserID   => $Param{UserID},
1641            );
1642        }
1643        elsif ( defined $Ticket->{TypeID} && $Ticket->{TypeID} ne $TicketData{TypeID} )
1644        {
1645            $Success = $TicketObject->TicketTypeSet(
1646                TypeID   => $Ticket->{TypeID},
1647                TicketID => $TicketID,
1648                UserID   => $Param{UserID},
1649            );
1650        }
1651        else {
1652
1653            # data is the same as in ticket nothing to do
1654            $Success = 1;
1655        }
1656
1657        if ( !$Success ) {
1658            return {
1659                Success => 0,
1660                Errormessage =>
1661                    'Ticket type could not be updated, please contact system administrator!',
1662            };
1663        }
1664    }
1665
1666    # update Ticket>State
1667    # depending on the state, might require to unlock ticket or enables pending time set
1668    if ( $Ticket->{State} || $Ticket->{StateID} ) {
1669
1670        # get State Data
1671        my %StateData;
1672        my $StateID;
1673
1674        # get state object
1675        my $StateObject = $Kernel::OM->Get('Kernel::System::State');
1676
1677        if ( $Ticket->{StateID} ) {
1678            $StateID = $Ticket->{StateID};
1679        }
1680        else {
1681            $StateID = $StateObject->StateLookup(
1682                State => $Ticket->{State},
1683            );
1684        }
1685
1686        %StateData = $StateObject->StateGet(
1687            ID => $StateID,
1688        );
1689
1690        # force unlock if state type is close
1691        if ( $StateData{TypeName} =~ /^close/i ) {
1692
1693            # set lock
1694            $TicketObject->TicketLockSet(
1695                TicketID => $TicketID,
1696                Lock     => 'unlock',
1697                UserID   => $Param{UserID},
1698            );
1699        }
1700
1701        # set pending time
1702        elsif ( $StateData{TypeName} =~ /^pending/i ) {
1703
1704            # set pending time
1705            if ( defined $Ticket->{PendingTime} ) {
1706                my $Success = $TicketObject->TicketPendingTimeSet(
1707                    UserID   => $Param{UserID},
1708                    TicketID => $TicketID,
1709                    %{ $Ticket->{PendingTime} },
1710                );
1711
1712                if ( !$Success ) {
1713                    return {
1714                        Success => 0,
1715                        Errormessage =>
1716                            'Ticket pendig time could not be updated, please contact system'
1717                            . ' administrator!',
1718                    };
1719                }
1720            }
1721            else {
1722                return $Self->ReturnError(
1723                    ErrorCode    => 'TicketUpdate.MissingParameter',
1724                    ErrorMessage => 'Can\'t set a ticket on a pending state without pendig time!'
1725                );
1726            }
1727        }
1728
1729        my $Success;
1730        if ( defined $Ticket->{State} && $Ticket->{State} ne $TicketData{State} ) {
1731            $Success = $TicketObject->TicketStateSet(
1732                State    => $Ticket->{State},
1733                TicketID => $TicketID,
1734                UserID   => $Param{UserID},
1735            );
1736        }
1737        elsif ( defined $Ticket->{StateID} && $Ticket->{StateID} ne $TicketData{StateID} )
1738        {
1739            $Success = $TicketObject->TicketStateSet(
1740                StateID  => $Ticket->{StateID},
1741                TicketID => $TicketID,
1742                UserID   => $Param{UserID},
1743            );
1744        }
1745        else {
1746
1747            # data is the same as in ticket nothing to do
1748            $Success = 1;
1749        }
1750
1751        if ( !$Success ) {
1752            return {
1753                Success => 0,
1754                Errormessage =>
1755                    'Ticket state could not be updated, please contact system administrator!',
1756            };
1757        }
1758    }
1759
1760    # update Ticket->Service
1761    # this might reset SLA if current SLA is not available for the new service
1762    if ( $Ticket->{Service} || $Ticket->{ServiceID} ) {
1763
1764        # check if ticket has a SLA assigned
1765        if ( $TicketData{SLAID} ) {
1766
1767            # check if old SLA is still valid
1768            if (
1769                !$Self->ValidateSLA(
1770                    SLAID     => $TicketData{SLAID},
1771                    Service   => $Ticket->{Service} || '',
1772                    ServiceID => $Ticket->{ServiceID} || '',
1773                )
1774                )
1775            {
1776
1777                # remove current SLA if is not compatible with new service
1778                my $Success = $TicketObject->TicketSLASet(
1779                    SLAID    => '',
1780                    TicketID => $TicketID,
1781                    UserID   => $Param{UserID},
1782                );
1783            }
1784        }
1785
1786        my $Success;
1787
1788        # prevent comparison errors on undefined values
1789        if ( !defined $TicketData{Service} ) {
1790            $TicketData{Service} = '';
1791        }
1792        if ( !defined $TicketData{ServiceID} ) {
1793            $TicketData{ServiceID} = '';
1794        }
1795
1796        if ( defined $Ticket->{Service} && $Ticket->{Service} ne $TicketData{Service} ) {
1797            $Success = $TicketObject->TicketServiceSet(
1798                Service  => $Ticket->{Service},
1799                TicketID => $TicketID,
1800                UserID   => $Param{UserID},
1801            );
1802        }
1803        elsif ( defined $Ticket->{ServiceID} && $Ticket->{ServiceID} ne $TicketData{ServiceID} )
1804        {
1805            $Success = $TicketObject->TicketServiceSet(
1806                ServiceID => $Ticket->{ServiceID},
1807                TicketID  => $TicketID,
1808                UserID    => $Param{UserID},
1809            );
1810        }
1811        else {
1812
1813            # data is the same as in ticket nothing to do
1814            $Success = 1;
1815        }
1816
1817        if ( !$Success ) {
1818            return {
1819                Success => 0,
1820                Errormessage =>
1821                    'Ticket service could not be updated, please contact system administrator!',
1822            };
1823        }
1824    }
1825
1826    # update Ticket->SLA
1827    if ( $Ticket->{SLA} || $Ticket->{SLAID} ) {
1828        my $Success;
1829
1830        # prevent comparison errors on undefined values
1831        if ( !defined $TicketData{SLA} ) {
1832            $TicketData{SLA} = '';
1833        }
1834        if ( !defined $TicketData{SLAID} ) {
1835            $TicketData{SLAID} = '';
1836        }
1837
1838        if ( defined $Ticket->{SLA} && $Ticket->{SLA} ne $TicketData{SLA} ) {
1839            $Success = $TicketObject->TicketSLASet(
1840                SLA      => $Ticket->{SLA},
1841                TicketID => $TicketID,
1842                UserID   => $Param{UserID},
1843            );
1844        }
1845        elsif ( defined $Ticket->{SLAID} && $Ticket->{SLAID} ne $TicketData{SLAID} )
1846        {
1847            $Success = $TicketObject->TicketSLASet(
1848                SLAID    => $Ticket->{SLAID},
1849                TicketID => $TicketID,
1850                UserID   => $Param{UserID},
1851            );
1852        }
1853        else {
1854
1855            # data is the same as in ticket nothing to do
1856            $Success = 1;
1857        }
1858
1859        if ( !$Success ) {
1860            return {
1861                Success => 0,
1862                Errormessage =>
1863                    'Ticket SLA could not be updated, please contact system administrator!',
1864            };
1865        }
1866    }
1867
1868    # update Ticket->CustomerUser && Ticket->CustomerID
1869    if ( $Ticket->{CustomerUser} || $Ticket->{CustomerID} ) {
1870
1871        # set values to empty if they are not defined
1872        $TicketData{CustomerUserID} = $TicketData{CustomerUserID} || '';
1873        $TicketData{CustomerID}     = $TicketData{CustomerID}     || '';
1874        $Ticket->{CustomerUser}     = $Ticket->{CustomerUser}     || '';
1875        $Ticket->{CustomerID}       = $Ticket->{CustomerID}       || '';
1876
1877        my $Success;
1878        if (
1879            $Ticket->{CustomerUser} ne $TicketData{CustomerUserID}
1880            || $Ticket->{CustomerID} ne $TicketData{CustomerID}
1881            )
1882        {
1883            my $CustomerID = $CustomerUserData{UserCustomerID} || '';
1884
1885            # use user defined CustomerID if defined
1886            if ( defined $Ticket->{CustomerID} && $Ticket->{CustomerID} ne '' ) {
1887                $CustomerID = $Ticket->{CustomerID};
1888            }
1889
1890            $Success = $TicketObject->TicketCustomerSet(
1891                No       => $CustomerID,
1892                User     => $Ticket->{CustomerUser},
1893                TicketID => $TicketID,
1894                UserID   => $Param{UserID},
1895            );
1896        }
1897        else {
1898
1899            # data is the same as in ticket nothing to do
1900            $Success = 1;
1901        }
1902
1903        if ( !$Success ) {
1904            return {
1905                Success => 0,
1906                Errormessage =>
1907                    'Ticket customer user could not be updated, please contact system administrator!',
1908            };
1909        }
1910    }
1911
1912    # update Ticket->Priority
1913    if ( $Ticket->{Priority} || $Ticket->{PriorityID} ) {
1914        my $Success;
1915        if ( defined $Ticket->{Priority} && $Ticket->{Priority} ne $TicketData{Priority} ) {
1916            $Success = $TicketObject->TicketPrioritySet(
1917                Priority => $Ticket->{Priority},
1918                TicketID => $TicketID,
1919                UserID   => $Param{UserID},
1920            );
1921        }
1922        elsif ( defined $Ticket->{PriorityID} && $Ticket->{PriorityID} ne $TicketData{PriorityID} )
1923        {
1924            $Success = $TicketObject->TicketPrioritySet(
1925                PriorityID => $Ticket->{PriorityID},
1926                TicketID   => $TicketID,
1927                UserID     => $Param{UserID},
1928            );
1929        }
1930        else {
1931
1932            # data is the same as in ticket nothing to do
1933            $Success = 1;
1934        }
1935
1936        if ( !$Success ) {
1937            return {
1938                Success => 0,
1939                Errormessage =>
1940                    'Ticket priority could not be updated, please contact system administrator!',
1941            };
1942        }
1943    }
1944
1945    my $UnlockOnAway = 1;
1946
1947    # update Ticket->Owner
1948    if ( $Ticket->{Owner} || $Ticket->{OwnerID} ) {
1949        my $Success;
1950        if ( defined $Ticket->{Owner} && $Ticket->{Owner} ne $TicketData{Owner} ) {
1951            $Success = $TicketObject->TicketOwnerSet(
1952                NewUser  => $Ticket->{Owner},
1953                TicketID => $TicketID,
1954                UserID   => $Param{UserID},
1955            );
1956            $UnlockOnAway = 0;
1957        }
1958        elsif ( defined $Ticket->{OwnerID} && $Ticket->{OwnerID} ne $TicketData{OwnerID} )
1959        {
1960            $Success = $TicketObject->TicketOwnerSet(
1961                NewUserID => $Ticket->{OwnerID},
1962                TicketID  => $TicketID,
1963                UserID    => $Param{UserID},
1964            );
1965            $UnlockOnAway = 0;
1966        }
1967        else {
1968
1969            # data is the same as in ticket nothing to do
1970            $Success = 1;
1971        }
1972
1973        if ( !$Success ) {
1974            return {
1975                Success => 0,
1976                Errormessage =>
1977                    'Ticket owner could not be updated, please contact system administrator!',
1978            };
1979        }
1980    }
1981
1982    # update Ticket->Responsible
1983    if ( $Ticket->{Responsible} || $Ticket->{ResponsibleID} ) {
1984        my $Success;
1985        if (
1986            defined $Ticket->{Responsible}
1987            && $Ticket->{Responsible} ne $TicketData{Responsible}
1988            )
1989        {
1990            $Success = $TicketObject->TicketResponsibleSet(
1991                NewUser  => $Ticket->{Responsible},
1992                TicketID => $TicketID,
1993                UserID   => $Param{UserID},
1994            );
1995        }
1996        elsif (
1997            defined $Ticket->{ResponsibleID}
1998            && $Ticket->{ResponsibleID} ne $TicketData{ResponsibleID}
1999            )
2000        {
2001            $Success = $TicketObject->TicketResponsibleSet(
2002                NewUserID => $Ticket->{ResponsibleID},
2003                TicketID  => $TicketID,
2004                UserID    => $Param{UserID},
2005            );
2006        }
2007        else {
2008
2009            # data is the same as in ticket nothing to do
2010            $Success = 1;
2011        }
2012
2013        if ( !$Success ) {
2014            return {
2015                Success => 0,
2016                Errormessage =>
2017                    'Ticket responsible could not be updated, please contact system administrator!',
2018            };
2019        }
2020    }
2021
2022    my $ArticleID;
2023    if ( IsHashRefWithData($Article) ) {
2024
2025        # set Article From
2026        my $From;
2027        if ( $Article->{From} ) {
2028            $From = $Article->{From};
2029        }
2030        elsif ( $Param{UserType} eq 'Customer' ) {
2031
2032            # use data from customer user (if customer user is in database)
2033            if ( IsHashRefWithData( \%CustomerUserData ) ) {
2034                $From = '"'
2035                    . $CustomerUserData{UserFullname} . '"'
2036                    . ' <' . $CustomerUserData{UserEmail} . '>';
2037            }
2038
2039            # otherwise use customer user as sent from the request (it should be an email)
2040            else {
2041                $From = $Ticket->{CustomerUser};
2042            }
2043        }
2044        else {
2045            my %UserData = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
2046                UserID => $Param{UserID},
2047            );
2048            $From = $UserData{UserFullname};
2049        }
2050
2051        # Set Article To, Cc, Bcc.
2052        my ( $To, $Cc, $Bcc );
2053        if ( $Article->{To} ) {
2054            $To = $Article->{To};
2055        }
2056        if ( $Article->{Cc} ) {
2057            $Cc = $Article->{Cc};
2058        }
2059        if ( $Article->{Bcc} ) {
2060            $Bcc = $Article->{Bcc};
2061        }
2062
2063        # Fallback for To
2064        if ( !$To && $Article->{CommunicationChannel} eq 'Email' ) {
2065
2066            # Use data from customer user (if customer user is in database).
2067            if ( IsHashRefWithData( \%CustomerUserData ) ) {
2068                $To = '"' . $CustomerUserData{UserFullname} . '"'
2069                    . ' <' . $CustomerUserData{UserEmail} . '>';
2070            }
2071
2072            # Otherwise use customer user as sent from the request (it should be an email).
2073            else {
2074                $To = $Ticket->{CustomerUser} // $TicketData{CustomerUserID};
2075            }
2076        }
2077
2078        if ( !$Article->{CommunicationChannel} ) {
2079
2080            my %CommunicationChannel = $Kernel::OM->Get('Kernel::System::CommunicationChannel')->ChannelGet(
2081                ChannelID => $Article->{CommunicationChannelID},
2082            );
2083            $Article->{CommunicationChannel} = $CommunicationChannel{ChannelName};
2084        }
2085
2086        my $ArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForChannel(
2087            ChannelName => $Article->{CommunicationChannel},
2088        );
2089
2090        my $PlainBody = $Article->{Body};
2091
2092        # Convert article body to plain text, if HTML content was supplied. This is necessary since auto response code
2093        #   expects plain text content. Please see bug#13397 for more information.
2094        if ( $Article->{ContentType} =~ /text\/html/i || $Article->{MimeType} =~ /text\/html/i ) {
2095            $PlainBody = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii(
2096                String => $Article->{Body},
2097            );
2098        }
2099
2100        # Create article.
2101        $ArticleID = $ArticleBackendObject->ArticleCreate(
2102            NoAgentNotify        => $Article->{NoAgentNotify} || 0,
2103            TicketID             => $TicketID,
2104            SenderTypeID         => $Article->{SenderTypeID} || '',
2105            SenderType           => $Article->{SenderType} || '',
2106            IsVisibleForCustomer => $Article->{IsVisibleForCustomer},
2107            From                 => $From,
2108            To                   => $To,
2109            Cc                   => $Cc,
2110            Bcc                  => $Bcc,
2111            Subject              => $Article->{Subject},
2112            Body                 => $Article->{Body},
2113            MimeType             => $Article->{MimeType} || '',
2114            Charset              => $Article->{Charset} || '',
2115            ContentType          => $Article->{ContentType} || '',
2116            UserID               => $Param{UserID},
2117            HistoryType          => $Article->{HistoryType},
2118            HistoryComment       => $Article->{HistoryComment} || '%%',
2119            AutoResponseType     => $Article->{AutoResponseType},
2120            UnlockOnAway         => $UnlockOnAway,
2121            OrigHeader           => {
2122                From    => $From,
2123                To      => $To,
2124                Subject => $Article->{Subject},
2125                Body    => $PlainBody,
2126            },
2127        );
2128
2129        if ( !$ArticleID ) {
2130            return {
2131                Success => 0,
2132                ErrorMessage =>
2133                    'Article could not be created, please contact the system administrator'
2134            };
2135        }
2136
2137        # time accounting
2138        if ( $Article->{TimeUnit} ) {
2139            $TicketObject->TicketAccountTime(
2140                TicketID  => $TicketID,
2141                ArticleID => $ArticleID,
2142                TimeUnit  => $Article->{TimeUnit},
2143                UserID    => $Param{UserID},
2144            );
2145        }
2146    }
2147
2148    # set dynamic fields
2149    for my $DynamicField ( @{$DynamicFieldList} ) {
2150        my $Result = $Self->SetDynamicFieldValue(
2151            %{$DynamicField},
2152            TicketID  => $TicketID,
2153            ArticleID => $ArticleID || '',
2154            UserID    => $Param{UserID},
2155        );
2156
2157        if ( !$Result->{Success} ) {
2158            my $ErrorMessage =
2159                $Result->{ErrorMessage} || "Dynamic Field $DynamicField->{Name} could not be set,"
2160                . " please contact the system administrator";
2161
2162            return {
2163                Success      => 0,
2164                ErrorMessage => $ErrorMessage,
2165            };
2166        }
2167    }
2168
2169    # set attachments
2170
2171    for my $Attachment ( @{$AttachmentList} ) {
2172        my $Result = $Self->CreateAttachment(
2173            Attachment => $Attachment,
2174            TicketID   => $TicketID,
2175            ArticleID  => $ArticleID || '',
2176            UserID     => $Param{UserID}
2177        );
2178
2179        if ( !$Result->{Success} ) {
2180            my $ErrorMessage =
2181                $Result->{ErrorMessage} || "Attachment could not be created, please contact the "
2182                . " system administrator";
2183
2184            return {
2185                Success      => 0,
2186                ErrorMessage => $ErrorMessage,
2187            };
2188        }
2189    }
2190
2191    # get web service configuration
2192    my $Webservice = $Kernel::OM->Get('Kernel::System::GenericInterface::Webservice')->WebserviceGet(
2193        ID => $Self->{WebserviceID},
2194    );
2195
2196    my $IncludeTicketData;
2197
2198    # Get operation config, if operation name was supplied.
2199    if ( $Self->{Operation} ) {
2200        my $OperationConfig = $Webservice->{Config}->{Provider}->{Operation}->{ $Self->{Operation} };
2201        $IncludeTicketData = $OperationConfig->{IncludeTicketData};
2202    }
2203
2204    if ( !$IncludeTicketData ) {
2205        if ($ArticleID) {
2206            return {
2207                Success => 1,
2208                Data    => {
2209                    TicketID     => $TicketID,
2210                    TicketNumber => $TicketData{TicketNumber},
2211                    ArticleID    => $ArticleID,
2212                },
2213            };
2214        }
2215        return {
2216            Success => 1,
2217            Data    => {
2218                TicketID     => $TicketID,
2219                TicketNumber => $TicketData{TicketNumber},
2220            },
2221        };
2222    }
2223
2224    # get updated TicketData
2225    %TicketData = ();
2226    %TicketData = $TicketObject->TicketGet(
2227        TicketID      => $TicketID,
2228        DynamicFields => 1,
2229        UserID        => $Param{UserID},
2230    );
2231
2232    # extract all dynamic fields from main ticket hash.
2233    my %TicketDynamicFields;
2234    TICKETATTRIBUTE:
2235    for my $TicketAttribute ( sort keys %TicketData ) {
2236        if ( $TicketAttribute =~ m{\A DynamicField_(.*) \z}msx ) {
2237            $TicketDynamicFields{$1} = {
2238                Name  => $1,
2239                Value => $TicketData{$TicketAttribute},
2240            };
2241            delete $TicketData{$TicketAttribute};
2242        }
2243    }
2244
2245    # add dynamic fields as array into 'DynamicField' hash key if any
2246    if (%TicketDynamicFields) {
2247        $TicketData{DynamicField} = [ sort { $a->{Name} cmp $b->{Name} } values %TicketDynamicFields ];
2248    }
2249
2250    # get last ArticleID
2251    my $ArticleObject        = $Kernel::OM->Get('Kernel::System::Ticket::Article');
2252    my $ArticleBackendObject = $ArticleObject->BackendForArticle(
2253        ArticleID => $ArticleID,
2254        TicketID  => $TicketID
2255    );
2256
2257    my @Articles = $ArticleObject->ArticleList(
2258        TicketID => $TicketID,
2259        OnlyLast => 1,
2260    );
2261
2262    my $LastArticleID = $Articles[0]->{ArticleID};
2263
2264    # return ticket data if we have no article data
2265    if ( !$ArticleID && !$LastArticleID ) {
2266        return {
2267            Success => 1,
2268            Data    => {
2269                TicketID     => $TicketID,
2270                TicketNumber => $TicketData{TicketNumber},
2271                Ticket       => \%TicketData,
2272            },
2273        };
2274    }
2275
2276    # get Article and ArticleAttachement
2277    my %ArticleData = $ArticleBackendObject->ArticleGet(
2278        ArticleID     => $ArticleID || $LastArticleID,
2279        DynamicFields => 1,
2280        TicketID      => $TicketID,
2281    );
2282
2283    # prepare Article DynamicFields
2284    my @ArticleDynamicFields;
2285
2286    # remove all dynamic fields from main ticket hash and set them into an array.
2287    ARTICLEATTRIBUTE:
2288    for my $ArticleAttribute ( sort keys %ArticleData ) {
2289        if ( $ArticleAttribute =~ m{\A DynamicField_(.*) \z}msx ) {
2290            if ( !exists $TicketDynamicFields{$1} ) {
2291                push @ArticleDynamicFields, {
2292                    Name  => $1,
2293                    Value => $ArticleData{$ArticleAttribute},
2294                };
2295            }
2296
2297            delete $ArticleData{$ArticleAttribute};
2298        }
2299    }
2300
2301    # add dynamic fields array into 'DynamicField' hash key if any
2302    if (@ArticleDynamicFields) {
2303        $ArticleData{DynamicField} = \@ArticleDynamicFields;
2304    }
2305
2306    # add attachment if the request includes attachments
2307    if ( IsArrayRefWithData($AttachmentList) ) {
2308        my %AttachmentIndex = $ArticleBackendObject->ArticleAttachmentIndex(
2309            ArticleID => $ArticleData{ArticleID},
2310        );
2311
2312        my @Attachments;
2313        $Kernel::OM->Get('Kernel::System::Main')->Require('MIME::Base64');
2314        ATTACHMENT:
2315        for my $FileID ( sort keys %AttachmentIndex ) {
2316            next ATTACHMENT if !$FileID;
2317            my %Attachment = $ArticleBackendObject->ArticleAttachment(
2318                ArticleID => $ArticleData{ArticleID},
2319                FileID    => $FileID,
2320            );
2321
2322            next ATTACHMENT if !IsHashRefWithData( \%Attachment );
2323
2324            # convert content to base64, but prevent 76 chars brake, see bug#14500.
2325            $Attachment{Content} = MIME::Base64::encode_base64( $Attachment{Content}, '' );
2326            push @Attachments, {%Attachment};
2327        }
2328
2329        # set Attachments data
2330        if (@Attachments) {
2331            $ArticleData{Attachment} = \@Attachments;
2332        }
2333    }
2334
2335    $TicketData{Article} = \%ArticleData;
2336
2337    # return ticket data and article data
2338    return {
2339        Success => 1,
2340        Data    => {
2341            TicketID     => $TicketID,
2342            TicketNumber => $TicketData{TicketNumber},
2343            ArticleID    => $ArticleData{ArticleID},
2344            Ticket       => \%TicketData,
2345        },
2346    };
2347}
2348
23491;
2350
2351=end Internal:
2352
2353=head1 TERMS AND CONDITIONS
2354
2355This software is part of the OTRS project (L<https://otrs.org/>).
2356
2357This software comes with ABSOLUTELY NO WARRANTY. For details, see
2358the enclosed file COPYING for license information (GPL). If you
2359did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
2360
2361=cut
2362