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::Modules::AgentTicketForward;
10
11use strict;
12use warnings;
13
14use Kernel::System::VariableCheck qw(:all);
15use Kernel::Language qw(Translatable);
16use Mail::Address;
17
18our $ObjectManagerDisabled = 1;
19
20sub new {
21    my ( $Type, %Param ) = @_;
22
23    my $Self = {%Param};
24    bless( $Self, $Type );
25
26    # Try to load draft if requested.
27    if (
28        $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}")->{FormDraft}
29        && $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'LoadFormDraft' )
30        && $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormDraftID' )
31        )
32    {
33        $Self->{LoadedFormDraftID} = $Kernel::OM->Get('Kernel::System::Web::Request')->LoadFormDraft(
34            FormDraftID => $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormDraftID' ),
35            UserID      => $Self->{UserID},
36        );
37    }
38
39    $Self->{Debug} = $Param{Debug} || 0;
40
41    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
42
43    for (
44        qw(To Cc Bcc Subject Body InReplyTo References ComposeStateID IsVisibleForCustomerPresent
45        IsVisibleForCustomer ArticleID TimeUnits Year Month Day Hour Minute FormID FormDraftID Title)
46        )
47    {
48        my $Value = $ParamObject->GetParam( Param => $_ );
49        if ( defined $Value ) {
50            $Self->{GetParam}->{$_} = $Value;
51        }
52    }
53
54    $Self->{GetParam}->{ForwardTemplateID} = $ParamObject->GetParam( Param => 'ForwardTemplateID' ) || '';
55
56    # ACL compatibility translation
57    $Self->{ACLCompatGetParam}->{NextStateID} = $Self->{GetParam}->{ComposeStateID};
58
59    # create form id
60    if ( !$Self->{GetParam}->{FormID} ) {
61        $Self->{GetParam}->{FormID} = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate();
62    }
63
64    return $Self;
65}
66
67sub Run {
68    my ( $Self, %Param ) = @_;
69
70    my $Output;
71
72    # get ACL restrictions
73    my %PossibleActions = ( 1 => $Self->{Action} );
74
75    # get ticket object
76    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
77
78    # get ticket data
79    my %Ticket = $TicketObject->TicketGet(
80        TicketID      => $Self->{TicketID},
81        DynamicFields => 1,
82    );
83
84    $Self->{GetParam}->{From} = $Kernel::OM->Get('Kernel::System::TemplateGenerator')->Sender(
85        QueueID => $Ticket{QueueID},
86        UserID  => $Self->{UserID},
87    );
88
89    my $ACL = $TicketObject->TicketAcl(
90        Data          => \%PossibleActions,
91        Action        => $Self->{Action},
92        TicketID      => $Self->{TicketID},
93        ReturnType    => 'Action',
94        ReturnSubType => '-',
95        UserID        => $Self->{UserID},
96    );
97    my %AclAction = $TicketObject->TicketAclActionData();
98
99    # get layout object
100    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
101
102    # check if ACL restrictions exist
103    if ( $ACL || IsHashRefWithData( \%AclAction ) ) {
104
105        my %AclActionLookup = reverse %AclAction;
106
107        # show error screen if ACL prohibits this action
108        if ( !$AclActionLookup{ $Self->{Action} } ) {
109            return $LayoutObject->NoPermission( WithHeader => 'yes' );
110        }
111    }
112
113    # Check for failed draft loading request.
114    if (
115        $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'LoadFormDraft' )
116        && !$Self->{LoadedFormDraftID}
117        )
118    {
119        return $LayoutObject->ErrorScreen(
120            Message => Translatable('Loading draft failed!'),
121            Comment => Translatable('Please contact the administrator.'),
122        );
123    }
124
125    if ( $Self->{Subaction} eq 'SendEmail' ) {
126
127        # challenge token check for write action
128        $LayoutObject->ChallengeTokenCheck();
129
130        $Output = $Self->SendEmail();
131    }
132    elsif ( $Self->{Subaction} eq 'AJAXUpdate' ) {
133        $Output = $Self->AjaxUpdate();
134    }
135    elsif ( $Self->{LoadedFormDraftID} ) {
136        $Output = $Self->SendEmail();
137    }
138    else {
139        $Output = $Self->Form();
140    }
141    return $Output;
142}
143
144sub Form {
145    my ( $Self, %Param ) = @_;
146
147    my %Error;
148    my %ACLCompatGetParam = %{ $Self->{ACLCompatGetParam} };
149
150    # get layout object
151    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
152
153    # check needed stuff
154    if ( !$Self->{TicketID} ) {
155        return $LayoutObject->ErrorScreen(
156            Message => Translatable('Got no TicketID!'),
157            Comment => Translatable('System Error!'),
158        );
159    }
160
161    my $TicketObject  = $Kernel::OM->Get('Kernel::System::Ticket');
162    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
163
164    # get ticket data
165    my %Ticket = $TicketObject->TicketGet(
166        TicketID      => $Self->{TicketID},
167        DynamicFields => 1,
168    );
169
170    # get config object
171    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
172
173    # get config for frontend module
174    my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
175
176    # check permissions
177    my $Access = $TicketObject->TicketPermission(
178        Type     => $Config->{Permission},
179        TicketID => $Self->{TicketID},
180        UserID   => $Self->{UserID}
181    );
182
183    # error screen, don't show ticket
184    if ( !$Access ) {
185        return $LayoutObject->NoPermission( WithHeader => 'yes' );
186    }
187
188    my %GetParamExtended = $Self->_GetExtendedParams();
189
190    my %GetParam            = %{ $GetParamExtended{GetParam} };
191    my @MultipleCustomer    = @{ $GetParamExtended{MultipleCustomer} };
192    my @MultipleCustomerCc  = @{ $GetParamExtended{MultipleCustomerCc} };
193    my @MultipleCustomerBcc = @{ $GetParamExtended{MultipleCustomerBcc} };
194
195    # get lock state
196    my $Output = '';
197    if ( $Config->{RequiredLock} ) {
198        if ( !$TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) {
199
200            my $Lock = $TicketObject->TicketLockSet(
201                TicketID => $Self->{TicketID},
202                Lock     => 'lock',
203                UserID   => $Self->{UserID}
204            );
205
206            if ($Lock) {
207
208                # Set new owner if ticket owner is different then logged user.
209                if ( $Ticket{OwnerID} != $Self->{UserID} ) {
210
211                    # Remember previous owner, which will be used to restore ticket owner on undo action.
212                    $Param{PreviousOwner} = $Ticket{OwnerID};
213
214                    $TicketObject->TicketOwnerSet(
215                        TicketID  => $Self->{TicketID},
216                        UserID    => $Self->{UserID},
217                        NewUserID => $Self->{UserID},
218                    );
219                }
220
221                # Show lock state.
222                $LayoutObject->Block(
223                    Name => 'PropertiesLock',
224                    Data => {
225                        %Param,
226                        TicketID => $Self->{TicketID}
227                    },
228                );
229            }
230        }
231        else {
232            my $AccessOk = $TicketObject->OwnerCheck(
233                TicketID => $Self->{TicketID},
234                OwnerID  => $Self->{UserID},
235            );
236            if ( !$AccessOk ) {
237                my $Output = $LayoutObject->Header(
238                    Type      => 'Small',
239                    BodyClass => 'Popup',
240                );
241                $Output .= $LayoutObject->Warning(
242                    Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'),
243                    Comment => Translatable('Please change the owner first.'),
244                );
245                $Output .= $LayoutObject->Footer(
246                    Type => 'Small',
247                );
248                return $Output;
249            }
250            else {
251                $LayoutObject->Block(
252                    Name => 'TicketBack',
253                    Data => {
254                        %Param,
255                        TicketID => $Self->{TicketID},
256                    },
257                );
258            }
259        }
260    }
261    else {
262        $LayoutObject->Block(
263            Name => 'TicketBack',
264            Data => {
265                %Param,
266                TicketID => $Self->{TicketID},
267            },
268        );
269    }
270
271    # Get selected or last customer article.
272    my %Data;
273    if ( $GetParam{ArticleID} ) {
274        my $ArticleBackendObject = $ArticleObject->BackendForArticle(
275            TicketID  => $Self->{TicketID},
276            ArticleID => $GetParam{ArticleID},
277        );
278        %Data = $ArticleBackendObject->ArticleGet(
279            TicketID      => $Self->{TicketID},
280            ArticleID     => $GetParam{ArticleID},
281            DynamicFields => 1,
282        );
283
284        # Check if article exists.
285        if ( !%Data ) {
286            return $LayoutObject->ErrorScreen(
287                Message => $LayoutObject->{LanguageObject}
288                    ->Translate( 'Article %s could not be found!', $GetParam{ArticleID} ),
289            );
290        }
291    }
292    else {
293
294        # Get last customer article.
295        my @Articles = $ArticleObject->ArticleList(
296            TicketID   => $Self->{TicketID},
297            SenderType => 'customer',
298            OnlyLast   => 1,
299        );
300
301        # If the ticket has no customer article, get the last agent article.
302        if ( !@Articles ) {
303            @Articles = $ArticleObject->ArticleList(
304                TicketID   => $Self->{TicketID},
305                SenderType => 'agent',
306                OnlyLast   => 1,
307            );
308        }
309
310        # Finally, if everything failed, get latest article.
311        if ( !@Articles ) {
312            @Articles = $ArticleObject->ArticleList(
313                TicketID => $Self->{TicketID},
314                OnlyLast => 1,
315            );
316        }
317
318        for my $Article (@Articles) {
319            %Data = $ArticleObject->BackendForArticle( %{$Article} )->ArticleGet(
320                %{$Article},
321                DynamicFields => 1,
322            );
323        }
324    }
325
326    # prepare signature
327    my $TemplateGenerator = $Kernel::OM->Get('Kernel::System::TemplateGenerator');
328    $Data{Signature} = $TemplateGenerator->Signature(
329        TicketID  => $Self->{TicketID},
330        ArticleID => $Data{ArticleID},
331        Data      => \%Data,
332        UserID    => $Self->{UserID},
333    );
334
335    if ( $GetParam{ForwardTemplateID} ) {
336
337        # get template
338        $Data{StdTemplate} = $TemplateGenerator->Template(
339            TicketID   => $Self->{TicketID},
340            ArticleID  => $Data{ArticleID},
341            TemplateID => $GetParam{ForwardTemplateID},
342            Data       => \%Data,
343            UserID     => $Self->{UserID},
344        );
345
346        # get signature
347        $Data{Signature} = $TemplateGenerator->Signature(
348            TicketID => $Self->{TicketID},
349            Data     => \%Data,
350            UserID   => $Self->{UserID},
351        );
352    }
353
354    # upload cache object
355    my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache');
356
357    # body preparation for plain text processing
358    $Data{Body} = $LayoutObject->ArticleQuote(
359        TicketID           => $Data{TicketID},
360        ArticleID          => $Data{ArticleID},
361        FormID             => $Self->{GetParam}->{FormID},
362        UploadCacheObject  => $UploadCacheObject,
363        AttachmentsInclude => 1,
364    );
365
366    my %SafetyCheckResult = $Kernel::OM->Get('Kernel::System::HTMLUtils')->Safety(
367        String => $Data{Body},
368
369        # Strip out external content if BlockLoadingRemoteContent is enabled.
370        NoExtSrcLoad => $ConfigObject->Get('Ticket::Frontend::BlockLoadingRemoteContent'),
371
372        # Disallow potentially unsafe content.
373        NoApplet     => 1,
374        NoObject     => 1,
375        NoEmbed      => 1,
376        NoSVG        => 1,
377        NoJavaScript => 1,
378    );
379    $Data{Body} = $SafetyCheckResult{String};
380
381    # If article is not a MIMEBase article, include sender name for correct quoting.
382    if ( !$Data{From} ) {
383        my %ArticleFields = $LayoutObject->ArticleFields(
384            TicketID  => $Self->{TicketID},
385            ArticleID => $Data{ArticleID},
386        );
387        $Data{Sender} = $ArticleFields{Sender}->{Value} // '';
388    }
389
390    if ( $LayoutObject->{BrowserRichText} ) {
391
392        # prepare body, subject, ReplyTo ...
393        $Data{Body} = '<br/>' . $Data{Body};
394        if ( $Data{CreateTime} ) {
395            $Data{CreateTime} = $LayoutObject->{LanguageObject}->FormatTimeString( $Data{CreateTime} );
396            $Data{Body}       = $LayoutObject->{LanguageObject}->Translate('Date') .
397                ": $Data{CreateTime}<br/>" . $Data{Body};
398        }
399        for my $Key (qw(Subject ReplyTo Reply-To Cc To From Sender)) {
400            if ( $Data{$Key} ) {
401                my $KeyText = $LayoutObject->{LanguageObject}->Translate($Key);
402
403                my $Value = $LayoutObject->Ascii2RichText(
404                    String => $Data{$Key},
405                );
406                $Data{Body} = "$KeyText: $Value<br/>" . $Data{Body};
407            }
408        }
409
410        my $Quote = $LayoutObject->Ascii2RichText(
411            String => $ConfigObject->Get('Ticket::Frontend::Quote') || '',
412        );
413        if ($Quote) {
414
415            # quote text
416            $Data{Body} = "<blockquote type=\"cite\">$Data{Body}</blockquote>\n";
417
418            # cleanup not compat. tags
419            $Data{Body} = $LayoutObject->RichTextDocumentCleanup(
420                String => $Data{Body},
421            );
422        }
423        else {
424            $Data{Body} = "<br/>" . $Data{Body};
425        }
426        my $From = $LayoutObject->Ascii2RichText(
427            String => $Data{From} || $Data{Sender},
428        );
429
430        my $ForwardedMessageFrom = $LayoutObject->{LanguageObject}->Translate('Forwarded message from');
431        my $EndForwardedMessage  = $LayoutObject->{LanguageObject}->Translate('End forwarded message');
432
433        $Data{Body} = "<br/>---- $ForwardedMessageFrom $From ---<br/><br/>" . $Data{Body};
434        $Data{Body} .= "<br/>---- $EndForwardedMessage ---<br/>";
435        $Data{Body} = $Data{Signature} . $Data{Body};
436
437        if ( $GetParam{ForwardTemplateID} ) {
438            $Data{Body} = $Data{StdTemplate} . '<br/>' . $Data{Body};
439        }
440
441        $Data{ContentType} = 'text/html';
442    }
443    else {
444
445        # prepare body, subject, ReplyTo ...
446        $Data{Body} =~ s/\t/ /g;
447        my $Quote = $ConfigObject->Get('Ticket::Frontend::Quote');
448        if ($Quote) {
449            $Data{Body} =~ s/\n/\n$Quote /g;
450            $Data{Body} = "\n$Quote " . $Data{Body};
451        }
452        else {
453            $Data{Body} = "\n" . $Data{Body};
454        }
455        if ( $Data{CreateTime} ) {
456            $Data{CreateTime} = $LayoutObject->{LanguageObject}->FormatTimeString( $Data{CreateTime} );
457            $Data{Body}       = $LayoutObject->{LanguageObject}->Translate('Date') .
458                ": $Data{CreateTime}\n" . $Data{Body};
459        }
460        for (qw(Subject ReplyTo Reply-To Cc To From Sender)) {
461            if ( $Data{$_} ) {
462                $Data{Body} = $LayoutObject->{LanguageObject}->Translate($_) .
463                    ": $Data{$_}\n" . $Data{Body};
464            }
465        }
466
467        my $ForwardedMessageFrom = $LayoutObject->{LanguageObject}->Translate('Forwarded message from');
468        my $EndForwardedMessage  = $LayoutObject->{LanguageObject}->Translate('End forwarded message');
469
470        my $From = $Data{From} || $Data{Sender};
471
472        $Data{Body} = "\n---- $ForwardedMessageFrom $From ---\n\n" . $Data{Body};
473        $Data{Body} .= "\n---- $EndForwardedMessage ---\n";
474        $Data{Body} = $Data{Signature} . $Data{Body};
475
476        if ( $GetParam{ForwardTemplateID} ) {
477            $Data{Body} = $Data{StdTemplate} . "\n" . $Data{Body};
478        }
479    }
480
481    # get std. attachment object
482    my $StdAttachmentObject = $Kernel::OM->Get('Kernel::System::StdAttachment');
483
484    # add std. attachments to email
485    if ( $GetParam{ForwardTemplateID} ) {
486        my %AllStdAttachments = $StdAttachmentObject->StdAttachmentStandardTemplateMemberList(
487            StandardTemplateID => $GetParam{ForwardTemplateID},
488        );
489        for ( sort keys %AllStdAttachments ) {
490            my %AttachmentsData = $StdAttachmentObject->StdAttachmentGet( ID => $_ );
491            $UploadCacheObject->FormIDAddFile(
492                FormID      => $GetParam{FormID},
493                Disposition => 'attachment',
494                %AttachmentsData,
495            );
496        }
497    }
498
499    # get all attachments meta data
500    my @Attachments = $UploadCacheObject->FormIDGetAllFilesMeta(
501        FormID => $GetParam{FormID},
502    );
503
504    # check some values
505    for (qw(To Cc Bcc)) {
506        if ( $Data{$_} ) {
507            delete $Data{$_};
508        }
509    }
510
511    # put & get attributes like sender address
512    %Data = $TemplateGenerator->Attributes(
513        TicketID  => $Self->{TicketID},
514        ArticleID => $GetParam{ArticleID},
515        Data      => \%Data,
516        UserID    => $Self->{UserID},
517        Action    => 'Forward',
518    );
519
520    # get param object
521    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
522
523    # run compose modules
524    if ( ref( $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') ) eq 'HASH' ) {
525
526        # use ticket QueueID in compose modules
527        $GetParam{QueueID} = $Ticket{QueueID};
528
529        my %Jobs = %{ $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') };
530        for my $Job ( sort keys %Jobs ) {
531
532            # load module
533            if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Jobs{$Job}->{Module} ) ) {
534                return $LayoutObject->FatalError();
535            }
536            my $Object = $Jobs{$Job}->{Module}->new(
537                %{$Self},
538                Debug => $Self->{Debug},
539            );
540
541            # get params
542            PARAMETER:
543            for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) {
544                if ( $Jobs{$Job}->{ParamType} && $Jobs{$Job}->{ParamType} ne 'Single' ) {
545                    @{ $GetParam{$Parameter} } = $ParamObject->GetArray( Param => $Parameter );
546                    next PARAMETER;
547                }
548
549                $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter );
550            }
551
552            # run module
553            my $NewParams = $Object->Run( %Data, %GetParam, Config => $Jobs{$Job} );
554
555            if ($NewParams) {
556                for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) {
557                    $GetParam{$Parameter} = $NewParams;
558                }
559            }
560
561            # get errors
562            %Error = ( %Error, $Object->Error( %GetParam, Config => $Jobs{$Job} ) );
563        }
564    }
565
566    # create html strings for all dynamic fields
567    my %DynamicFieldHTML;
568
569    # get the dynamic fields for this screen
570    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
571        Valid       => 1,
572        ObjectType  => [ 'Ticket', 'Article' ],
573        FieldFilter => $Config->{DynamicField} || {},
574    );
575
576    # cycle through the activated Dynamic Fields for this screen
577    DYNAMICFIELD:
578    for my $DynamicFieldConfig ( @{$DynamicField} ) {
579        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
580
581        my $PossibleValuesFilter;
582
583        # get backend object
584        my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
585
586        my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
587            DynamicFieldConfig => $DynamicFieldConfig,
588            Behavior           => 'IsACLReducible',
589        );
590
591        if ($IsACLReducible) {
592
593            # get PossibleValues
594            my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
595                DynamicFieldConfig => $DynamicFieldConfig,
596            );
597
598            # check if field has PossibleValues property in its configuration
599            if ( IsHashRefWithData($PossibleValues) ) {
600
601                # convert possible values key => value to key => key for ACLs using a Hash slice
602                my %AclData = %{$PossibleValues};
603                @AclData{ keys %AclData } = keys %AclData;
604
605                # set possible values filter from ACLs
606                my $ACL = $TicketObject->TicketAcl(
607                    %GetParam,
608                    %ACLCompatGetParam,
609                    Action        => $Self->{Action},
610                    TicketID      => $Self->{TicketID},
611                    ReturnType    => 'Ticket',
612                    ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
613                    Data          => \%AclData,
614                    UserID        => $Self->{UserID},
615                );
616                if ($ACL) {
617                    my %Filter = $TicketObject->TicketAclData();
618
619                    # convert Filer key => key back to key => value using map
620                    %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} }
621                        keys %Filter;
622                }
623            }
624        }
625
626        # to store dynamic field value from database (or undefined)
627        my $Value;
628
629        # only get values for Ticket fields (all screens based on AgentTickeActionCommon
630        # create a new article, then article fields will be always empty at the beginning)
631        if ( $DynamicFieldConfig->{ObjectType} eq 'Ticket' ) {
632
633            # get value stored on the database from Ticket
634            $Value = $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} };
635        }
636
637        # get field html
638        $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } =
639            $DynamicFieldBackendObject->EditFieldRender(
640            DynamicFieldConfig   => $DynamicFieldConfig,
641            PossibleValuesFilter => $PossibleValuesFilter,
642            Value                => $Value,
643            Mandatory =>
644                $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2,
645            LayoutObject    => $LayoutObject,
646            ParamObject     => $ParamObject,
647            AJAXUpdate      => 1,
648            UpdatableFields => $Self->_GetFieldsToUpdate(),
649            );
650    }
651
652    # build view ...
653    # start with page ...
654    $Output .= $LayoutObject->Header(
655        Value     => $Ticket{TicketNumber},
656        Type      => 'Small',
657        BodyClass => 'Popup',
658    );
659
660    # build references string
661    my $References = defined $Data{References} ? $Data{References} . ' ' : '';
662    $References .= defined $Data{MessageID} ? $Data{MessageID} : '';
663
664    $Output .= $Self->_Mask(
665        TicketNumber => $Ticket{TicketNumber},
666        TicketID     => $Self->{TicketID},
667        Title        => $Ticket{Title},
668        QueueID      => $Ticket{QueueID},
669        SLAID        => $Ticket{SLAID},
670        NextStates   => $Self->_GetNextStates(
671            %GetParam,
672            %ACLCompatGetParam,
673        ),
674        TimeUnitsRequired => (
675            $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime')
676            ? 'Validate_Required'
677            : ''
678        ),
679        Errors              => \%Error,
680        MultipleCustomer    => \@MultipleCustomer,
681        MultipleCustomerCc  => \@MultipleCustomerCc,
682        MultipleCustomerBcc => \@MultipleCustomerBcc,
683        Attachments         => \@Attachments,
684        %Data,
685        %GetParam,
686        InReplyTo        => $Data{MessageID},
687        References       => $References,
688        DynamicFieldHTML => \%DynamicFieldHTML,
689    );
690    $Output .= $LayoutObject->Footer(
691        Type => 'Small',
692    );
693
694    return $Output;
695}
696
697sub SendEmail {
698    my ( $Self, %Param ) = @_;
699
700    my %Error;
701    my %ACLCompatGetParam = %{ $Self->{ACLCompatGetParam} };
702
703    my %GetParamExtended = $Self->_GetExtendedParams();
704
705    my %GetParam            = %{ $GetParamExtended{GetParam} };
706    my @MultipleCustomer    = @{ $GetParamExtended{MultipleCustomer} };
707    my @MultipleCustomerCc  = @{ $GetParamExtended{MultipleCustomerCc} };
708    my @MultipleCustomerBcc = @{ $GetParamExtended{MultipleCustomerBcc} };
709
710    my %DynamicFieldValues;
711
712    # get config object
713    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
714
715    # get config for frontend module
716    my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
717
718    # get the dynamic fields for this screen
719    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
720        Valid       => 1,
721        ObjectType  => [ 'Ticket', 'Article' ],
722        FieldFilter => $Config->{DynamicField} || {},
723    );
724
725    # get needed objects
726    my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
727    my $LayoutObject              = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
728    my $ParamObject               = $Kernel::OM->Get('Kernel::System::Web::Request');
729
730    # Get and validate draft action.
731    my $FormDraftAction = $ParamObject->GetParam( Param => 'FormDraftAction' );
732    if ( $FormDraftAction && !$Config->{FormDraft} ) {
733        return $LayoutObject->ErrorScreen(
734            Message => Translatable('FormDraft functionality disabled!'),
735            Comment => Translatable('Please contact the administrator.'),
736        );
737    }
738
739    my %FormDraftResponse;
740
741    # Check draft name.
742    if (
743        $FormDraftAction
744        && ( $FormDraftAction eq 'Add' || $FormDraftAction eq 'Update' )
745        )
746    {
747        my $Title = $ParamObject->GetParam( Param => 'FormDraftTitle' );
748
749        # A draft name is required.
750        if ( !$Title ) {
751
752            %FormDraftResponse = (
753                Success      => 0,
754                ErrorMessage => $Kernel::OM->Get('Kernel::Language')->Translate("Draft name is required!"),
755            );
756        }
757
758        # Chosen draft name must be unique.
759        else {
760            my $FormDraftList = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftListGet(
761                ObjectType => 'Ticket',
762                ObjectID   => $Self->{TicketID},
763                Action     => $Self->{Action},
764                UserID     => $Self->{UserID},
765            );
766            DRAFT:
767            for my $FormDraft ( @{$FormDraftList} ) {
768
769                # No existing draft with same name.
770                next DRAFT if $Title ne $FormDraft->{Title};
771
772                # Same name for update on existing draft.
773                if (
774                    $GetParam{FormDraftID}
775                    && $FormDraftAction eq 'Update'
776                    && $GetParam{FormDraftID} eq $FormDraft->{FormDraftID}
777                    )
778                {
779                    next DRAFT;
780                }
781
782                # Another draft with the chosen name already exists.
783                %FormDraftResponse = (
784                    Success      => 0,
785                    ErrorMessage => $Kernel::OM->Get('Kernel::Language')
786                        ->Translate( "FormDraft name %s is already in use!", $Title ),
787                );
788                last DRAFT;
789            }
790        }
791    }
792
793    # Perform draft action instead of saving form data in ticket/article.
794    if ( $FormDraftAction && !%FormDraftResponse ) {
795
796        # Reset FormDraftID to prevent updating existing draft.
797        if ( $FormDraftAction eq 'Add' && $GetParam{FormDraftID} ) {
798            $ParamObject->{Query}->param(
799                -name  => 'FormDraftID',
800                -value => '',
801            );
802        }
803
804        my $FormDraftActionOk;
805        if (
806            $FormDraftAction eq 'Add'
807            ||
808            ( $FormDraftAction eq 'Update' && $GetParam{FormDraftID} )
809            )
810        {
811            $FormDraftActionOk = $ParamObject->SaveFormDraft(
812                UserID     => $Self->{UserID},
813                ObjectType => 'Ticket',
814                ObjectID   => $Self->{TicketID},
815            );
816        }
817        elsif ( $FormDraftAction eq 'Delete' && $GetParam{FormDraftID} ) {
818            $FormDraftActionOk = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete(
819                FormDraftID => $GetParam{FormDraftID},
820                UserID      => $Self->{UserID},
821            );
822        }
823
824        if ($FormDraftActionOk) {
825            $FormDraftResponse{Success} = 1;
826        }
827        else {
828            %FormDraftResponse = (
829                Success      => 0,
830                ErrorMessage => 'Could not perform requested draft action!',
831            );
832        }
833    }
834
835    if (%FormDraftResponse) {
836
837        # build JSON output
838        my $JSON = $LayoutObject->JSONEncode(
839            Data => \%FormDraftResponse,
840        );
841
842        # send JSON response
843        return $LayoutObject->Attachment(
844            ContentType => 'application/json; charset=' . $LayoutObject->{Charset},
845            Content     => $JSON,
846            Type        => 'inline',
847            NoCache     => 1,
848        );
849    }
850
851    # cycle through the activated Dynamic Fields for this screen
852    DYNAMICFIELD:
853    for my $DynamicFieldConfig ( @{$DynamicField} ) {
854        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
855
856        # extract the dynamic field value from the web request
857        $DynamicFieldValues{ $DynamicFieldConfig->{Name} } =
858            $DynamicFieldBackendObject->EditFieldValueGet(
859            DynamicFieldConfig => $DynamicFieldConfig,
860            ParamObject        => $ParamObject,
861            LayoutObject       => $LayoutObject,
862            );
863    }
864
865    # convert dynamic field values into a structure for ACLs
866    my %DynamicFieldACLParameters;
867    DYNAMICFIELD:
868    for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) {
869        next DYNAMICFIELD if !$DynamicFieldItem;
870        next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem};
871
872        $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem};
873    }
874    $GetParam{DynamicField} = \%DynamicFieldACLParameters;
875
876    my $QueueID = $Self->{QueueID};
877    my %StateData;
878
879    if ( $GetParam{ComposeStateID} ) {
880        %StateData = $Kernel::OM->Get('Kernel::System::State')->StateGet(
881            ID => $GetParam{ComposeStateID},
882        );
883    }
884
885    my $NextState = $StateData{Name};
886
887    # check pending date
888    if ( defined $StateData{TypeName} && $StateData{TypeName} =~ /^pending/i ) {
889
890        # create a datetime object bsed on pending date
891        my $PendingDateTimeObject = $Kernel::OM->Create(
892            'Kernel::System::DateTime',
893            ObjectParams => {
894                %GetParam,
895                Second => 0,
896            },
897        );
898
899        # get current system epoch
900        my $CurSystemDateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
901
902        if ( !$PendingDateTimeObject || $PendingDateTimeObject < $CurSystemDateTimeObject ) {
903            $Error{'DateInvalid'} = 'ServerError';
904        }
905    }
906
907    # check To
908    if ( !$GetParam{To} ) {
909        $Error{'ToInvalid'} = 'ServerError';
910    }
911
912    # check body
913    if ( !$GetParam{Body} ) {
914        $Error{'BodyInvalid'} = 'ServerError';
915    }
916
917    # check subject
918    if ( !$GetParam{Subject} ) {
919        $Error{'SubjectInvalid'} = 'ServerError';
920    }
921
922    if (
923        $ConfigObject->Get('Ticket::Frontend::AccountTime')
924        && $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime')
925        && $GetParam{TimeUnits} eq ''
926        )
927    {
928        $Error{'TimeUnitsInvalid'} = 'ServerError';
929    }
930
931    # get ticket object
932    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
933
934    # prepare subject
935    my %Ticket = $TicketObject->TicketGet(
936        TicketID      => $Self->{TicketID},
937        DynamicFields => 1,
938    );
939
940    $GetParam{Subject} = $TicketObject->TicketSubjectBuild(
941        TicketNumber => $Ticket{TicketNumber},
942        Action       => 'Forward',
943        Subject      => $GetParam{Subject} || '',
944    );
945
946    # create html strings for all dynamic fields
947    my %DynamicFieldHTML;
948
949    # cycle through the activated Dynamic Fields for this screen
950    DYNAMICFIELD:
951    for my $DynamicFieldConfig ( @{$DynamicField} ) {
952        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
953
954        my $PossibleValuesFilter;
955
956        my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
957            DynamicFieldConfig => $DynamicFieldConfig,
958            Behavior           => 'IsACLReducible',
959        );
960
961        if ($IsACLReducible) {
962
963            # get PossibleValues
964            my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
965                DynamicFieldConfig => $DynamicFieldConfig,
966            );
967
968            # check if field has PossibleValues property in its configuration
969            if ( IsHashRefWithData($PossibleValues) ) {
970
971                # convert possible values key => value to key => key for ACLs using a Hash slice
972                my %AclData = %{$PossibleValues};
973                @AclData{ keys %AclData } = keys %AclData;
974
975                # set possible values filter from ACLs
976                my $ACL = $TicketObject->TicketAcl(
977                    %GetParam,
978                    %ACLCompatGetParam,
979                    Action        => $Self->{Action},
980                    TicketID      => $Self->{TicketID},
981                    ReturnType    => 'Ticket',
982                    ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
983                    Data          => \%AclData,
984                    UserID        => $Self->{UserID},
985                );
986                if ($ACL) {
987                    my %Filter = $TicketObject->TicketAclData();
988
989                    # convert Filer key => key back to key => value using map
990                    %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} }
991                        keys %Filter;
992                }
993            }
994        }
995
996        my $ValidationResult = $DynamicFieldBackendObject->EditFieldValueValidate(
997            DynamicFieldConfig   => $DynamicFieldConfig,
998            PossibleValuesFilter => $PossibleValuesFilter,
999            ParamObject          => $ParamObject,
1000            Mandatory =>
1001                $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2,
1002        );
1003
1004        if ( !IsHashRefWithData($ValidationResult) ) {
1005            return $LayoutObject->ErrorScreen(
1006                Message =>
1007                    $LayoutObject->{LanguageObject}
1008                    ->Translate( 'Could not perform validation on field %s!', $DynamicFieldConfig->{Label} ),
1009                Comment => Translatable('Please contact the administrator.'),
1010            );
1011        }
1012
1013        # propagate validation error to the Error variable to be detected by the frontend
1014        if ( $ValidationResult->{ServerError} ) {
1015            $Error{ $DynamicFieldConfig->{Name} } = ' ServerError';
1016        }
1017
1018        # get field html
1019        $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } = $DynamicFieldBackendObject->EditFieldRender(
1020            DynamicFieldConfig   => $DynamicFieldConfig,
1021            PossibleValuesFilter => $PossibleValuesFilter,
1022            Mandatory =>
1023                $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2,
1024            ServerError  => $ValidationResult->{ServerError}  || '',
1025            ErrorMessage => $ValidationResult->{ErrorMessage} || '',
1026            LayoutObject => $LayoutObject,
1027            ParamObject  => $ParamObject,
1028            AJAXUpdate   => 1,
1029            UpdatableFields => $Self->_GetFieldsToUpdate(),
1030        );
1031    }
1032
1033    # transform pending time, time stamp based on user time zone
1034    if (
1035        defined $GetParam{Year}
1036        && defined $GetParam{Month}
1037        && defined $GetParam{Day}
1038        && defined $GetParam{Hour}
1039        && defined $GetParam{Minute}
1040        )
1041    {
1042        %GetParam = $LayoutObject->TransformDateSelection(
1043            %GetParam,
1044        );
1045    }
1046
1047    # get check item object
1048    my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
1049
1050    # check some values
1051    LINE:
1052    for my $Line (qw(To Cc Bcc)) {
1053        next LINE if !$GetParam{$Line};
1054        for my $Email ( Mail::Address->parse( $GetParam{$Line} ) ) {
1055            if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) {
1056                $Error{ $Line . 'ErrorType' } = $Line . $CheckItemObject->CheckErrorType() . 'ServerErrorMsg';
1057                $Error{ $Line . 'Invalid' }   = 'ServerError';
1058            }
1059            my $IsLocal = $Kernel::OM->Get('Kernel::System::SystemAddress')->SystemAddressIsLocalAddress(
1060                Address => $Email->address(),
1061            );
1062
1063            if ($IsLocal) {
1064                $Error{ $Line . 'IsLocalAddress' } = 'ServerError';
1065            }
1066        }
1067    }
1068
1069    # Make sure sender is correct one. See bug#14872 ( https://bugs.otrs.org/show_bug.cgi?id=14872 ).
1070    $GetParam{From} = $Kernel::OM->Get('Kernel::System::TemplateGenerator')->Sender(
1071        QueueID => $Ticket{QueueID},
1072        UserID  => $Self->{UserID},
1073    );
1074
1075    if ( $Self->{LoadedFormDraftID} ) {
1076
1077        # Make sure we don't save form if a draft was loaded.
1078        %Error = ( LoadedFormDraft => 1 );
1079    }
1080
1081    # run compose modules
1082    my %ArticleParam;
1083    if ( ref( $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') ) eq 'HASH' ) {
1084        my %Jobs = %{ $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') };
1085        for my $Job ( sort keys %Jobs ) {
1086
1087            # load module
1088            if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Jobs{$Job}->{Module} ) ) {
1089                return $LayoutObject->FatalError();
1090            }
1091            my $Object = $Jobs{$Job}->{Module}->new(
1092                %{$Self},
1093                Debug => $Self->{Debug},
1094            );
1095
1096            my $Multiple;
1097
1098            # get params
1099            PARAMETER:
1100            for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) {
1101                if ( $Jobs{$Job}->{ParamType} && $Jobs{$Job}->{ParamType} ne 'Single' ) {
1102                    @{ $GetParam{$Parameter} } = $ParamObject->GetArray( Param => $Parameter );
1103                    $Multiple = 1;
1104                    next PARAMETER;
1105                }
1106
1107                $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter );
1108            }
1109
1110            # run module
1111            $Object->Run(
1112                %GetParam,
1113                StoreNew => 1,
1114                Config   => $Jobs{$Job},
1115            );
1116
1117            # get options that have been removed from the selection
1118            # and add them back to the selection so that the submit
1119            # will contain options that were hidden from the agent
1120            my $Key = $Object->Option( %GetParam, Config => $Jobs{$Job} );
1121
1122            if ( $Object->can('GetOptionsToRemoveAJAX') ) {
1123                my @RemovedOptions = $Object->GetOptionsToRemoveAJAX(%GetParam);
1124                if (@RemovedOptions) {
1125                    if ($Multiple) {
1126                        for my $RemovedOption (@RemovedOptions) {
1127                            push @{ $GetParam{$Key} }, $RemovedOption;
1128                        }
1129                    }
1130                    else {
1131                        $GetParam{$Key} = shift @RemovedOptions;
1132                    }
1133                }
1134            }
1135
1136            # ticket params
1137            %ArticleParam = (
1138                %ArticleParam,
1139                $Object->ArticleOption(
1140                    %GetParam,
1141                    %ArticleParam,
1142                    Config => $Jobs{$Job},
1143                ),
1144            );
1145
1146            # get errors
1147            %Error = (
1148                %Error,
1149                $Object->Error(
1150                    %GetParam,
1151                    Config => $Jobs{$Job},
1152                ),
1153            );
1154        }
1155    }
1156
1157    # get upload cache object
1158    my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache');
1159
1160    # get all attachments meta data
1161    my @Attachments = $UploadCacheObject->FormIDGetAllFilesMeta(
1162        FormID => $GetParam{FormID},
1163    );
1164
1165    # check if there is an error
1166    if (%Error) {
1167
1168        my $QueueID = $TicketObject->TicketQueueID( TicketID => $Self->{TicketID} );
1169        my $Output  = $LayoutObject->Header(
1170            Type      => 'Small',
1171            BodyClass => 'Popup',
1172        );
1173        $Output .= $Self->_Mask(
1174            TicketNumber => $Ticket{TicketNumber},
1175            Title        => $Ticket{Title},
1176            TicketID     => $Self->{TicketID},
1177            QueueID      => $QueueID,
1178            SLAID        => $Ticket{SLAID},
1179            NextStates   => $Self->_GetNextStates(
1180                %GetParam,
1181                %ACLCompatGetParam,
1182            ),
1183            Errors              => \%Error,
1184            MultipleCustomer    => \@MultipleCustomer,
1185            MultipleCustomerCc  => \@MultipleCustomerCc,
1186            MultipleCustomerBcc => \@MultipleCustomerBcc,
1187            Attachments         => \@Attachments,
1188            DynamicFieldHTML    => \%DynamicFieldHTML,
1189            %GetParam,
1190        );
1191        $Output .= $LayoutObject->Footer(
1192            Type => 'Small',
1193        );
1194        return $Output;
1195    }
1196
1197    # replace <OTRS_TICKET_STATE> with next ticket state name
1198    if ($NextState) {
1199        $GetParam{Body} =~ s/(&lt;|<)OTRS_TICKET_STATE(&gt;|>)/$NextState/g;
1200    }
1201
1202    # get pre loaded attachments
1203    my @AttachmentData = $UploadCacheObject->FormIDGetAllFilesData(
1204        FormID => $GetParam{FormID},
1205    );
1206
1207    # get submit attachment
1208    my %UploadStuff = $ParamObject->GetUploadAll(
1209        Param => 'FileUpload',
1210    );
1211    if (%UploadStuff) {
1212        push @AttachmentData, \%UploadStuff;
1213    }
1214
1215    my $MimeType = 'text/plain';
1216    if ( $LayoutObject->{BrowserRichText} ) {
1217        $MimeType = 'text/html';
1218
1219        # remove unused inline images
1220        my @NewAttachmentData;
1221        ATTACHMENT:
1222        for my $Attachment (@AttachmentData) {
1223            my $ContentID = $Attachment->{ContentID};
1224            if ( $ContentID && ( $Attachment->{ContentType} =~ /image/i ) ) {
1225                my $ContentIDHTMLQuote = $LayoutObject->Ascii2Html(
1226                    Text => $ContentID,
1227                );
1228
1229                # workaround for link encode of rich text editor, see bug#5053
1230                my $ContentIDLinkEncode = $LayoutObject->LinkEncode($ContentID);
1231                $GetParam{Body} =~ s/(ContentID=)$ContentIDLinkEncode/$1$ContentID/g;
1232
1233                # IF the image is referenced in the body set it as inline.
1234                if ( $GetParam{Body} =~ /(\Q$ContentIDHTMLQuote\E|\Q$ContentID\E)/i ) {
1235                    $Attachment->{Disposition} = 'inline';
1236                }
1237                elsif ( $Attachment->{Disposition} eq 'inline' ) {
1238
1239                    # Ignore attachment if not linked in body.
1240                    next ATTACHMENT;
1241                }
1242            }
1243
1244            # remember inline images and normal attachments
1245            push @NewAttachmentData, \%{$Attachment};
1246        }
1247        @AttachmentData = @NewAttachmentData;
1248
1249        # verify HTML document
1250        $GetParam{Body} = $LayoutObject->RichTextDocumentComplete(
1251            String => $GetParam{Body},
1252        );
1253    }
1254
1255    # send email
1256    my $To = '';
1257    KEY:
1258    for my $Key (qw(To Cc Bcc)) {
1259        next KEY if !$GetParam{$Key};
1260        if ($To) {
1261            $To .= ', ';
1262        }
1263        $To .= $GetParam{$Key};
1264    }
1265
1266    my $IsVisibleForCustomer = $Config->{IsVisibleForCustomerDefault};
1267    if ( $GetParam{IsVisibleForCustomerPresent} ) {
1268        $IsVisibleForCustomer = $GetParam{IsVisibleForCustomer} ? 1 : 0;
1269    }
1270
1271    my $EmailArticleBackendObject = $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForChannel(
1272        ChannelName => 'Email',
1273    );
1274
1275    # Get attributes like sender address.
1276    my %Data = $Kernel::OM->Get('Kernel::System::TemplateGenerator')->Attributes(
1277        TicketID => $Self->{TicketID},
1278        Data     => {},
1279        UserID   => $Self->{UserID},
1280    );
1281
1282    my $ArticleID = $EmailArticleBackendObject->ArticleSend(
1283        TicketID             => $Self->{TicketID},
1284        SenderType           => 'agent',
1285        IsVisibleForCustomer => $IsVisibleForCustomer,
1286        HistoryType          => 'Forward',
1287        HistoryComment       => "\%\%$To",
1288        From                 => $Data{From},
1289        To                   => $GetParam{To},
1290        Cc                   => $GetParam{Cc},
1291        Bcc                  => $GetParam{Bcc},
1292        Subject              => $GetParam{Subject},
1293        UserID               => $Self->{UserID},
1294        Body                 => $GetParam{Body},
1295        InReplyTo            => $GetParam{InReplyTo},
1296        References           => $GetParam{References},
1297        Charset              => $LayoutObject->{UserCharset},
1298        MimeType             => $MimeType,
1299        Attachment           => \@AttachmentData,
1300        %ArticleParam,
1301    );
1302
1303    # error page
1304    if ( !$ArticleID ) {
1305        return $LayoutObject->ErrorScreen(
1306            Comment => Translatable('Please contact the administrator.'),
1307        );
1308    }
1309
1310    # time accounting
1311    if ( $GetParam{TimeUnits} ) {
1312        $TicketObject->TicketAccountTime(
1313            TicketID  => $Self->{TicketID},
1314            ArticleID => $ArticleID,
1315            TimeUnit  => $GetParam{TimeUnits},
1316            UserID    => $Self->{UserID},
1317        );
1318    }
1319
1320    # set dynamic fields
1321    # cycle through the activated Dynamic Fields for this screen
1322    DYNAMICFIELD:
1323    for my $DynamicFieldConfig ( @{$DynamicField} ) {
1324        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1325
1326        # set the object ID (TicketID or ArticleID) depending on the field configuration
1327        my $ObjectID = $DynamicFieldConfig->{ObjectType} eq 'Article' ? $ArticleID : $Self->{TicketID};
1328
1329        # set the value
1330        my $Success = $DynamicFieldBackendObject->ValueSet(
1331            DynamicFieldConfig => $DynamicFieldConfig,
1332            ObjectID           => $ObjectID,
1333            Value              => $DynamicFieldValues{ $DynamicFieldConfig->{Name} },
1334            UserID             => $Self->{UserID},
1335        );
1336    }
1337
1338    # set state
1339    if ($NextState) {
1340        $TicketObject->TicketStateSet(
1341            TicketID  => $Self->{TicketID},
1342            ArticleID => $ArticleID,
1343            State     => $NextState,
1344            UserID    => $Self->{UserID},
1345        );
1346
1347        # should I set an unlock?
1348        if ( $StateData{TypeName} =~ /^close/i ) {
1349            $TicketObject->TicketLockSet(
1350                TicketID => $Self->{TicketID},
1351                Lock     => 'unlock',
1352                UserID   => $Self->{UserID},
1353            );
1354        }
1355
1356        # set pending time
1357        elsif ( $StateData{TypeName} =~ /^pending/i ) {
1358            $TicketObject->TicketPendingTimeSet(
1359                UserID   => $Self->{UserID},
1360                TicketID => $Self->{TicketID},
1361                %GetParam,
1362            );
1363        }
1364    }
1365
1366    # remove pre-submitted attachments
1367    $UploadCacheObject->FormIDRemove( FormID => $GetParam{FormID} );
1368
1369    # If form was called based on a draft,
1370    #   delete draft since its content has now been used.
1371    if (
1372        $GetParam{FormDraftID}
1373        && !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete(
1374            FormDraftID => $GetParam{FormDraftID},
1375            UserID      => $Self->{UserID},
1376        )
1377        )
1378    {
1379        return $LayoutObject->ErrorScreen(
1380            Message => Translatable('Could not delete draft!'),
1381            Comment => Translatable('Please contact the administrator.'),
1382        );
1383    }
1384
1385    # redirect
1386    if (
1387        defined $StateData{TypeName}
1388        && $StateData{TypeName} =~ /^close/i
1389        && !$ConfigObject->Get('Ticket::Frontend::RedirectAfterCloseDisabled')
1390        )
1391    {
1392        return $LayoutObject->PopupClose(
1393            URL => ( $Self->{LastScreenOverview} || 'Action=AgentDashboard' ),
1394        );
1395    }
1396
1397    return $LayoutObject->PopupClose(
1398        URL => "Action=AgentTicketZoom;TicketID=$Self->{TicketID};ArticleID=$ArticleID",
1399    );
1400}
1401
1402sub AjaxUpdate {
1403    my ( $Self, %Param ) = @_;
1404
1405    my %Error;
1406    my %ACLCompatGetParam = %{ $Self->{ACLCompatGetParam} };
1407
1408    my %GetParamExtended = $Self->_GetExtendedParams();
1409
1410    my %GetParam            = %{ $GetParamExtended{GetParam} };
1411    my @MultipleCustomer    = @{ $GetParamExtended{MultipleCustomer} };
1412    my @MultipleCustomerCc  = @{ $GetParamExtended{MultipleCustomerCc} };
1413    my @MultipleCustomerBcc = @{ $GetParamExtended{MultipleCustomerBcc} };
1414
1415    my @ExtendedData;
1416
1417    # get needed objects
1418    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1419    my $ParamObject  = $Kernel::OM->Get('Kernel::System::Web::Request');
1420
1421    # run compose modules
1422    if ( ref $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') eq 'HASH' ) {
1423        my %Jobs = %{ $ConfigObject->Get('Ticket::Frontend::ArticleComposeModule') };
1424        JOB:
1425        for my $Job ( sort keys %Jobs ) {
1426
1427            # load module
1428            next JOB if !$Kernel::OM->Get('Kernel::System::Main')->Require( $Jobs{$Job}->{Module} );
1429
1430            my $Object = $Jobs{$Job}->{Module}->new(
1431                %{$Self},
1432                Debug => $Self->{Debug},
1433            );
1434
1435            my $Multiple;
1436
1437            # get params
1438            PARAMETER:
1439            for my $Parameter ( $Object->Option( %GetParam, Config => $Jobs{$Job} ) ) {
1440                if ( $Jobs{$Job}->{ParamType} && $Jobs{$Job}->{ParamType} ne 'Single' ) {
1441                    @{ $GetParam{$Parameter} } = $ParamObject->GetArray( Param => $Parameter );
1442                    $Multiple = 1;
1443                    next PARAMETER;
1444                }
1445
1446                $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter );
1447            }
1448
1449            # run module
1450            my %Data = $Object->Data( %GetParam, Config => $Jobs{$Job} );
1451
1452            # get AJAX param values
1453            if ( $Object->can('GetParamAJAX') ) {
1454                %GetParam = ( %GetParam, $Object->GetParamAJAX(%GetParam) );
1455            }
1456
1457            # get options that have to be removed from the selection visible
1458            # to the agent. These options will be added again on submit.
1459            if ( $Object->can('GetOptionsToRemoveAJAX') ) {
1460                my @OptionsToRemove = $Object->GetOptionsToRemoveAJAX(%GetParam);
1461
1462                for my $OptionToRemove (@OptionsToRemove) {
1463                    delete $Data{$OptionToRemove};
1464                }
1465            }
1466
1467            my $Key = $Object->Option( %GetParam, Config => $Jobs{$Job} );
1468
1469            if ($Key) {
1470                push @ExtendedData, {
1471                    Name         => $Key,
1472                    Data         => \%Data,
1473                    SelectedID   => $GetParam{$Key},
1474                    Translation  => 1,
1475                    PossibleNone => 1,
1476                    Multiple     => $Multiple,
1477                    Max          => 100,
1478                };
1479            }
1480        }
1481    }
1482
1483    my %DynamicFieldValues;
1484
1485    # get config for frontend module
1486    my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
1487
1488    # get the dynamic fields for this screen
1489    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
1490        Valid       => 1,
1491        ObjectType  => [ 'Ticket', 'Article' ],
1492        FieldFilter => $Config->{DynamicField} || {},
1493    );
1494
1495    # get needed objects
1496    my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
1497    my $LayoutObject              = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
1498
1499    # cycle through the activated Dynamic Fields for this screen
1500    DYNAMICFIELD:
1501    for my $DynamicFieldConfig ( @{$DynamicField} ) {
1502        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1503
1504        # extract the dynamic field value from the web request
1505        $DynamicFieldValues{ $DynamicFieldConfig->{Name} } =
1506            $DynamicFieldBackendObject->EditFieldValueGet(
1507            DynamicFieldConfig => $DynamicFieldConfig,
1508            ParamObject        => $ParamObject,
1509            LayoutObject       => $LayoutObject,
1510            );
1511    }
1512
1513    # convert dynamic field values into a structure for ACLs
1514    my %DynamicFieldACLParameters;
1515    DYNAMICFIELD:
1516    for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) {
1517        next DYNAMICFIELD if !$DynamicFieldItem;
1518        next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem};
1519
1520        $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem};
1521    }
1522    $GetParam{DynamicField} = \%DynamicFieldACLParameters;
1523
1524    my $NextStates = $Self->_GetNextStates(
1525        %GetParam,
1526        %ACLCompatGetParam,
1527    );
1528
1529    # update Dynamic Fields Possible Values via AJAX
1530    my @DynamicFieldAJAX;
1531
1532    # get ticket object
1533    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
1534
1535    # cycle through the activated Dynamic Fields for this screen
1536    DYNAMICFIELD:
1537    for my $DynamicFieldConfig ( @{$DynamicField} ) {
1538        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1539
1540        my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
1541            DynamicFieldConfig => $DynamicFieldConfig,
1542            Behavior           => 'IsACLReducible',
1543        );
1544        next DYNAMICFIELD if !$IsACLReducible;
1545
1546        my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
1547            DynamicFieldConfig => $DynamicFieldConfig,
1548        );
1549
1550        # convert possible values key => value to key => key for ACLs using a Hash slice
1551        my %AclData = %{$PossibleValues};
1552        @AclData{ keys %AclData } = keys %AclData;
1553
1554        # set possible values filter from ACLs
1555        my $ACL = $TicketObject->TicketAcl(
1556            %GetParam,
1557            %ACLCompatGetParam,
1558            Action        => $Self->{Action},
1559            TicketID      => $Self->{TicketID},
1560            QueueID       => $Self->{QueueID},
1561            ReturnType    => 'Ticket',
1562            ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
1563            Data          => \%AclData,
1564            UserID        => $Self->{UserID},
1565        );
1566        if ($ACL) {
1567            my %Filter = $TicketObject->TicketAclData();
1568
1569            # convert Filer key => key back to key => value using map
1570            %{$PossibleValues} = map { $_ => $PossibleValues->{$_} } keys %Filter;
1571        }
1572
1573        my $DataValues = $DynamicFieldBackendObject->BuildSelectionDataGet(
1574            DynamicFieldConfig => $DynamicFieldConfig,
1575            PossibleValues     => $PossibleValues,
1576            Value              => $DynamicFieldValues{ $DynamicFieldConfig->{Name} },
1577        ) || $PossibleValues;
1578
1579        # add dynamic field to the list of fields to update
1580        push @DynamicFieldAJAX, {
1581            Name        => 'DynamicField_' . $DynamicFieldConfig->{Name},
1582            Data        => $DataValues,
1583            SelectedID  => $DynamicFieldValues{ $DynamicFieldConfig->{Name} },
1584            Translation => $DynamicFieldConfig->{Config}->{TranslatableValues} || 0,
1585            Max         => 100,
1586        };
1587    }
1588
1589    my $JSON = $LayoutObject->BuildSelectionJSON(
1590        [
1591            {
1592                Name         => 'ComposeStateID',
1593                Data         => $NextStates,
1594                SelectedID   => $GetParam{ComposeStateID},
1595                Translation  => 1,
1596                PossibleNone => 1,
1597                Max          => 100,
1598            },
1599            @ExtendedData,
1600            @DynamicFieldAJAX,
1601        ],
1602    );
1603    return $LayoutObject->Attachment(
1604        ContentType => 'application/json; charset=' . $LayoutObject->{Charset},
1605        Content     => $JSON,
1606        Type        => 'inline',
1607        NoCache     => 1,
1608    );
1609}
1610
1611sub _GetNextStates {
1612    my ( $Self, %Param ) = @_;
1613
1614    # get next states
1615    my %NextStates = $Kernel::OM->Get('Kernel::System::Ticket')->TicketStateList(
1616        %Param,
1617        Action   => $Self->{Action},
1618        TicketID => $Self->{TicketID},
1619        UserID   => $Self->{UserID},
1620    );
1621    return \%NextStates;
1622}
1623
1624sub _Mask {
1625    my ( $Self, %Param ) = @_;
1626
1627    my $DynamicFieldNames = $Self->_GetFieldsToUpdate(
1628        OnlyDynamicFields => 1
1629    );
1630
1631    # create a string with the quoted dynamic field names separated by commas
1632    if ( IsArrayRefWithData($DynamicFieldNames) ) {
1633        for my $Field ( @{$DynamicFieldNames} ) {
1634            $Param{DynamicFieldNamesStrg} .= ", '" . $Field . "'";
1635        }
1636    }
1637
1638    # get config object
1639    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1640
1641    # get config for frontend module
1642    my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
1643
1644    # build next states string
1645    my %State;
1646    if ( !$Param{ComposeStateID} ) {
1647        $State{SelectedValue} = $Config->{StateDefault};
1648    }
1649    else {
1650        $State{SelectedID} = $Param{ComposeStateID};
1651    }
1652
1653    # get layout object
1654    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
1655
1656    $Param{NextStatesStrg} = $LayoutObject->BuildSelection(
1657        Data         => $Param{NextStates},
1658        Name         => 'ComposeStateID',
1659        Class        => 'Modernize',
1660        PossibleNone => 1,
1661        %State,
1662    );
1663
1664    $Param{IsVisibleForCustomer} = $Config->{IsVisibleForCustomerDefault};
1665    if ( $Self->{GetParam}->{IsVisibleForCustomerPresent} ) {
1666        $Param{IsVisibleForCustomer} = $Self->{GetParam}->{IsVisibleForCustomer} ? 1 : 0;
1667    }
1668
1669    # prepare errors!
1670    if ( $Param{Errors} ) {
1671        for my $Error ( sort keys %{ $Param{Errors} } ) {
1672            $Param{$Error} = $LayoutObject->Ascii2Html(
1673                Text => $Param{Errors}->{$Error},
1674            );
1675        }
1676    }
1677
1678    # get used calendar
1679    my $Calendar = $Kernel::OM->Get('Kernel::System::Ticket')->TicketCalendarGet(
1680        QueueID => $Param{QueueID},
1681        SLAID   => $Param{SLAID},
1682    );
1683
1684    # pending data string
1685    $Param{PendingDateString} = $LayoutObject->BuildDateSelection(
1686        %Param,
1687        YearPeriodPast       => 0,
1688        YearPeriodFuture     => 5,
1689        Format               => 'DateInputFormatLong',
1690        DiffTime             => $ConfigObject->Get('Ticket::Frontend::PendingDiffTime') || 0,
1691        Class                => $Param{Errors}->{DateInvalid} || ' ',
1692        Validate             => 1,
1693        ValidateDateInFuture => 1,
1694        Calendar             => $Calendar,
1695    );
1696
1697    # Multiple-Autocomplete
1698    # Cc
1699    my $CustomerCounterCc = 0;
1700    if ( $Param{MultipleCustomerCc} ) {
1701        for my $Item ( @{ $Param{MultipleCustomerCc} } ) {
1702            $LayoutObject->Block(
1703                Name => 'CcMultipleCustomer',
1704                Data => $Item,
1705            );
1706            $LayoutObject->Block(
1707                Name => 'Cc' . $Item->{CustomerErrorMsg},
1708                Data => $Item,
1709            );
1710            if ( $Item->{CustomerError} ) {
1711                $LayoutObject->Block(
1712                    Name => 'CcCustomerErrorExplantion',
1713                );
1714            }
1715            $CustomerCounterCc++;
1716        }
1717    }
1718
1719    if ( !$CustomerCounterCc ) {
1720        $Param{CcCustomerHiddenContainer} = 'Hidden';
1721    }
1722
1723    # set customer counter
1724    $LayoutObject->Block(
1725        Name => 'CcMultipleCustomerCounter',
1726        Data => {
1727            CustomerCounter => $CustomerCounterCc++,
1728        },
1729    );
1730
1731    # Bcc
1732    my $CustomerCounterBcc = 0;
1733    if ( $Param{MultipleCustomerBcc} ) {
1734        for my $Item ( @{ $Param{MultipleCustomerBcc} } ) {
1735            $LayoutObject->Block(
1736                Name => 'BccMultipleCustomer',
1737                Data => $Item,
1738            );
1739            $LayoutObject->Block(
1740                Name => 'Bcc' . $Item->{CustomerErrorMsg},
1741                Data => $Item,
1742            );
1743            if ( $Item->{CustomerError} ) {
1744                $LayoutObject->Block(
1745                    Name => 'BccCustomerErrorExplantion',
1746                );
1747            }
1748            $CustomerCounterBcc++;
1749        }
1750    }
1751
1752    if ( !$CustomerCounterBcc ) {
1753        $Param{BccCustomerHiddenContainer} = 'Hidden';
1754    }
1755
1756    # set customer counter
1757    $LayoutObject->Block(
1758        Name => 'BccMultipleCustomerCounter',
1759        Data => {
1760            CustomerCounter => $CustomerCounterBcc++,
1761        },
1762    );
1763
1764    # To
1765    my $CustomerCounter = 0;
1766    if ( $Param{MultipleCustomer} ) {
1767        for my $Item ( @{ $Param{MultipleCustomer} } ) {
1768            $LayoutObject->Block(
1769                Name => 'MultipleCustomer',
1770                Data => $Item,
1771            );
1772            $LayoutObject->Block(
1773                Name => $Item->{CustomerErrorMsg},
1774                Data => $Item,
1775            );
1776            if ( $Item->{CustomerError} ) {
1777                $LayoutObject->Block(
1778                    Name => 'CustomerErrorExplantion',
1779                );
1780            }
1781            $CustomerCounter++;
1782        }
1783    }
1784
1785    if ( !$CustomerCounter ) {
1786        $Param{CustomerHiddenContainer} = 'Hidden';
1787    }
1788
1789    # set customer counter
1790    $LayoutObject->Block(
1791        Name => 'MultipleCustomerCounter',
1792        Data => {
1793            CustomerCounter => $CustomerCounter++,
1794        },
1795    );
1796
1797    if ( $Param{ToInvalid} && $Param{Errors} && !$Param{Errors}->{ToErrorType} ) {
1798        $LayoutObject->Block(
1799            Name => 'ToServerErrorMsg',
1800        );
1801    }
1802
1803    if ( $Param{ToIsLocalAddress} && $Param{Errors} && !$Param{Errors}->{ToErrorType} ) {
1804        $LayoutObject->Block(
1805            Name => 'ToIsLocalAddressServerErrorMsg',
1806            Data => \%Param,
1807        );
1808    }
1809
1810    if ( $Param{CcInvalid} && $Param{Errors} && !$Param{Errors}->{CcErrorType} ) {
1811        $LayoutObject->Block(
1812            Name => 'CcServerErrorMsg',
1813        );
1814    }
1815
1816    if ( $Param{CcIsLocalAddress} && $Param{Errors} && !$Param{Errors}->{CcErrorType} ) {
1817        $LayoutObject->Block(
1818            Name => 'CcIsLocalAddressServerErrorMsg',
1819            Data => \%Param,
1820        );
1821    }
1822
1823    if ( $Param{BccInvalid} && $Param{Errors} && !$Param{Errors}->{BccErrorType} ) {
1824        $LayoutObject->Block(
1825            Name => 'BccServerErrorMsg',
1826        );
1827    }
1828
1829    if ( $Param{BccIsLocalAddress} && $Param{Errors} && !$Param{Errors}->{BccErrorType} ) {
1830        $LayoutObject->Block(
1831            Name => 'BccIsLocalAddressServerErrorMsg',
1832            Data => \%Param,
1833        );
1834    }
1835
1836    # get the dynamic fields for this screen
1837    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
1838        Valid       => 1,
1839        ObjectType  => [ 'Ticket', 'Article' ],
1840        FieldFilter => $Config->{DynamicField} || {},
1841    );
1842
1843    # Dynamic fields
1844    # cycle through the activated Dynamic Fields for this screen
1845    DYNAMICFIELD:
1846    for my $DynamicFieldConfig ( @{$DynamicField} ) {
1847        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1848
1849        # skip fields that HTML could not be retrieved
1850        next DYNAMICFIELD if !IsHashRefWithData(
1851            $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} }
1852        );
1853
1854        # get the html strings form $Param
1855        my $DynamicFieldHTML = $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} };
1856
1857        $LayoutObject->Block(
1858            Name => 'DynamicField',
1859            Data => {
1860                Name  => $DynamicFieldConfig->{Name},
1861                Label => $DynamicFieldHTML->{Label},
1862                Field => $DynamicFieldHTML->{Field},
1863            },
1864        );
1865
1866        # example of dynamic fields order customization
1867        $LayoutObject->Block(
1868            Name => 'DynamicField_' . $DynamicFieldConfig->{Name},
1869            Data => {
1870                Name  => $DynamicFieldConfig->{Name},
1871                Label => $DynamicFieldHTML->{Label},
1872                Field => $DynamicFieldHTML->{Field},
1873            },
1874        );
1875    }
1876
1877    # show time accounting box
1878    if ( $ConfigObject->Get('Ticket::Frontend::AccountTime') ) {
1879        if ( $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') ) {
1880            $LayoutObject->Block(
1881                Name => 'TimeUnitsLabelMandatory',
1882                Data => \%Param,
1883            );
1884        }
1885        else {
1886            $LayoutObject->Block(
1887                Name => 'TimeUnitsLabel',
1888                Data => \%Param,
1889            );
1890        }
1891        $LayoutObject->Block(
1892            Name => 'TimeUnits',
1893            Data => \%Param,
1894        );
1895    }
1896
1897    # Show the customer user address book if the module is registered and java script support is available.
1898    if (
1899        $ConfigObject->Get('Frontend::Module')->{AgentCustomerUserAddressBook}
1900        && $LayoutObject->{BrowserJavaScriptSupport}
1901        )
1902    {
1903        $Param{OptionCustomerUserAddressBook} = 1;
1904    }
1905
1906    # show attachments
1907    ATTACHMENT:
1908    for my $Attachment ( @{ $Param{Attachments} } ) {
1909        if (
1910            $Attachment->{ContentID}
1911            && $LayoutObject->{BrowserRichText}
1912            && ( $Attachment->{ContentType} =~ /image/i )
1913            )
1914        {
1915            my $ContentIDLinkEncode = $LayoutObject->LinkEncode( $Attachment->{ContentID} );
1916            if ( $Param{Body} =~ /ContentID=\Q$ContentIDLinkEncode\E/i ) {
1917                next ATTACHMENT;
1918            }
1919        }
1920
1921        push @{ $Param{AttachmentList} }, $Attachment;
1922    }
1923
1924    # add rich text editor
1925    if ( $LayoutObject->{BrowserRichText} ) {
1926
1927        # use height/width defined for this screen
1928        $Param{RichTextHeight} = $Config->{RichTextHeight} || 0;
1929        $Param{RichTextWidth}  = $Config->{RichTextWidth}  || 0;
1930
1931        # set up rich text editor
1932        $LayoutObject->SetRichTextParameters(
1933            Data => \%Param,
1934        );
1935    }
1936
1937    $LayoutObject->AddJSData(
1938        Key   => 'DynamicFieldNames',
1939        Value => $DynamicFieldNames,
1940    );
1941
1942    my $LoadedFormDraft;
1943    if ( $Self->{LoadedFormDraftID} ) {
1944        $LoadedFormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet(
1945            FormDraftID => $Self->{LoadedFormDraftID},
1946            GetContent  => 0,
1947            UserID      => $Self->{UserID},
1948        );
1949
1950        my @Articles = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleList(
1951            TicketID => $Self->{TicketID},
1952            OnlyLast => 1,
1953        );
1954
1955        if (@Articles) {
1956            my $LastArticle = $Articles[0];
1957
1958            my $LastArticleSystemTime;
1959            if ( $LastArticle->{CreateTime} ) {
1960                my $LastArticleSystemTimeObject = $Kernel::OM->Create(
1961                    'Kernel::System::DateTime',
1962                    ObjectParams => {
1963                        String => $LastArticle->{CreateTime},
1964                    },
1965                );
1966                $LastArticleSystemTime = $LastArticleSystemTimeObject->ToEpoch();
1967            }
1968
1969            my $FormDraftSystemTimeObject = $Kernel::OM->Create(
1970                'Kernel::System::DateTime',
1971                ObjectParams => {
1972                    String => $LoadedFormDraft->{ChangeTime},
1973                },
1974            );
1975            my $FormDraftSystemTime = $FormDraftSystemTimeObject->ToEpoch();
1976
1977            if ( !$LastArticleSystemTime || $FormDraftSystemTime <= $LastArticleSystemTime ) {
1978                $Param{FormDraftOutdated} = 1;
1979            }
1980        }
1981    }
1982
1983    if ( IsHashRefWithData($LoadedFormDraft) ) {
1984
1985        $LoadedFormDraft->{ChangeByName} = $Kernel::OM->Get('Kernel::System::User')->UserName(
1986            UserID => $LoadedFormDraft->{ChangeBy},
1987        );
1988    }
1989
1990    # create & return output
1991    return $LayoutObject->Output(
1992        TemplateFile => 'AgentTicketForward',
1993        Data         => {
1994            %Param,
1995            FormDraft      => $Config->{FormDraft},
1996            FormDraftID    => $Self->{LoadedFormDraftID},
1997            FormDraftTitle => $LoadedFormDraft ? $LoadedFormDraft->{Title} : '',
1998            FormDraftMeta  => $LoadedFormDraft,
1999        },
2000    );
2001}
2002
2003sub _GetFieldsToUpdate {
2004    my ( $Self, %Param ) = @_;
2005
2006    my @UpdatableFields;
2007
2008    # set the fields that can be updateable via AJAXUpdate
2009    if ( !$Param{OnlyDynamicFields} ) {
2010        @UpdatableFields = qw( ComposeStateID );
2011    }
2012
2013    # get config for frontend module
2014    my $Config = $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}");
2015
2016    # get the dynamic fields for this screen
2017    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
2018        Valid       => 1,
2019        ObjectType  => [ 'Ticket', 'Article' ],
2020        FieldFilter => $Config->{DynamicField} || {},
2021    );
2022
2023    # cycle through the activated Dynamic Fields for this screen
2024    DYNAMICFIELD:
2025    for my $DynamicFieldConfig ( @{$DynamicField} ) {
2026        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
2027
2028        my $IsACLReducible = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->HasBehavior(
2029            DynamicFieldConfig => $DynamicFieldConfig,
2030            Behavior           => 'IsACLReducible',
2031        );
2032        next DYNAMICFIELD if !$IsACLReducible;
2033
2034        push @UpdatableFields, 'DynamicField_' . $DynamicFieldConfig->{Name};
2035    }
2036
2037    return \@UpdatableFields;
2038}
2039
2040sub _GetExtendedParams {
2041    my ( $Self, %Param ) = @_;
2042
2043    my %GetParam = %{ $Self->{GetParam} };
2044
2045    # hash for check duplicated entries
2046    my %AddressesList;
2047
2048    # get param object
2049    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
2050
2051    my @MultipleCustomer;
2052    my $CustomersNumber = $ParamObject->GetParam( Param => 'CustomerTicketCounterToCustomer' ) || 0;
2053    my $Selected        = $ParamObject->GetParam( Param => 'CustomerSelected' )                || '';
2054
2055    # get check item object
2056    my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
2057
2058    if ($CustomersNumber) {
2059        my $CustomerCounter = 1;
2060        for my $Count ( 1 ... $CustomersNumber ) {
2061            my $CustomerElement  = $ParamObject->GetParam( Param => 'CustomerTicketText_' . $Count );
2062            my $CustomerSelected = ( $Selected eq $Count ? 'checked="checked"' : '' );
2063            my $CustomerKey      = $ParamObject->GetParam( Param => 'CustomerKey_' . $Count )
2064                || '';
2065            my $CustomerQueue = $ParamObject->GetParam( Param => 'CustomerQueue_' . $Count )
2066                || '';
2067            if ($CustomerElement) {
2068
2069                if ( $GetParam{To} ) {
2070                    $GetParam{To} .= ', ' . $CustomerElement;
2071                }
2072                else {
2073                    $GetParam{To} = $CustomerElement;
2074                }
2075
2076                # check email address
2077                my $CustomerErrorMsg = 'CustomerGenericServerErrorMsg';
2078                my $CustomerError    = '';
2079                for my $Email ( Mail::Address->parse($CustomerElement) ) {
2080                    if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) {
2081                        $CustomerErrorMsg = $CheckItemObject->CheckErrorType()
2082                            . 'ServerErrorMsg';
2083                        $CustomerError = 'ServerError';
2084                    }
2085                }
2086
2087                # check for duplicated entries
2088                if ( defined $AddressesList{$CustomerElement} && $CustomerError eq '' ) {
2089                    $CustomerErrorMsg = 'IsDuplicatedServerErrorMsg';
2090                    $CustomerError    = 'ServerError';
2091                }
2092
2093                my $CustomerDisabled = '';
2094                my $CountAux         = $CustomerCounter++;
2095                if ( $CustomerError ne '' ) {
2096                    $CustomerDisabled = 'disabled="disabled"';
2097                    $CountAux         = $Count . 'Error';
2098                }
2099
2100                if ( $CustomerQueue ne '' ) {
2101                    $CustomerQueue = $Count;
2102                }
2103
2104                push @MultipleCustomer, {
2105                    Count            => $CountAux,
2106                    CustomerElement  => $CustomerElement,
2107                    CustomerSelected => $CustomerSelected,
2108                    CustomerKey      => $CustomerKey,
2109                    CustomerError    => $CustomerError,
2110                    CustomerErrorMsg => $CustomerErrorMsg,
2111                    CustomerDisabled => $CustomerDisabled,
2112                    CustomerQueue    => $CustomerQueue,
2113                };
2114                $AddressesList{$CustomerElement} = 1;
2115            }
2116        }
2117    }
2118
2119    my @MultipleCustomerCc;
2120    my $CustomersNumberCc = $ParamObject->GetParam( Param => 'CustomerTicketCounterCcCustomer' ) || 0;
2121
2122    if ($CustomersNumberCc) {
2123        my $CustomerCounterCc = 1;
2124        for my $Count ( 1 ... $CustomersNumberCc ) {
2125            my $CustomerElementCc = $ParamObject->GetParam( Param => 'CcCustomerTicketText_' . $Count );
2126            my $CustomerKeyCc     = $ParamObject->GetParam( Param => 'CcCustomerKey_' . $Count )
2127                || '';
2128            my $CustomerQueueCc = $ParamObject->GetParam( Param => 'CcCustomerQueue_' . $Count )
2129                || '';
2130
2131            if ($CustomerElementCc) {
2132
2133                if ( $GetParam{Cc} ) {
2134                    $GetParam{Cc} .= ', ' . $CustomerElementCc;
2135                }
2136                else {
2137                    $GetParam{Cc} = $CustomerElementCc;
2138                }
2139
2140                # check email address
2141                my $CustomerErrorMsgCc = 'CustomerGenericServerErrorMsg';
2142                my $CustomerErrorCc    = '';
2143                for my $Email ( Mail::Address->parse($CustomerElementCc) ) {
2144                    if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) {
2145                        $CustomerErrorMsgCc = $CheckItemObject->CheckErrorType()
2146                            . 'ServerErrorMsg';
2147                        $CustomerErrorCc = 'ServerError';
2148                    }
2149                }
2150
2151                # check for duplicated entries
2152                if ( defined $AddressesList{$CustomerElementCc} && $CustomerErrorCc eq '' ) {
2153                    $CustomerErrorMsgCc = 'IsDuplicatedServerErrorMsg';
2154                    $CustomerErrorCc    = 'ServerError';
2155                }
2156
2157                my $CustomerDisabledCc = '';
2158                my $CountAuxCc         = $CustomerCounterCc++;
2159                if ( $CustomerErrorCc ne '' ) {
2160                    $CustomerDisabledCc = 'disabled="disabled"';
2161                    $CountAuxCc         = $Count . 'Error';
2162                }
2163
2164                if ( $CustomerQueueCc ne '' ) {
2165                    $CustomerQueueCc = $Count;
2166                }
2167
2168                push @MultipleCustomerCc, {
2169                    Count            => $CountAuxCc,
2170                    CustomerElement  => $CustomerElementCc,
2171                    CustomerKey      => $CustomerKeyCc,
2172                    CustomerError    => $CustomerErrorCc,
2173                    CustomerErrorMsg => $CustomerErrorMsgCc,
2174                    CustomerDisabled => $CustomerDisabledCc,
2175                    CustomerQueue    => $CustomerQueueCc,
2176                };
2177                $AddressesList{$CustomerElementCc} = 1;
2178            }
2179        }
2180    }
2181
2182    my @MultipleCustomerBcc;
2183    my $CustomersNumberBcc = $ParamObject->GetParam( Param => 'CustomerTicketCounterBccCustomer' ) || 0;
2184
2185    if ($CustomersNumberBcc) {
2186        my $CustomerCounterBcc = 1;
2187        for my $Count ( 1 ... $CustomersNumberBcc ) {
2188            my $CustomerElementBcc = $ParamObject->GetParam( Param => 'BccCustomerTicketText_' . $Count );
2189            my $CustomerKeyBcc     = $ParamObject->GetParam( Param => 'BccCustomerKey_' . $Count )
2190                || '';
2191            my $CustomerQueueBcc = $ParamObject->GetParam( Param => 'BccCustomerQueue_' . $Count )
2192                || '';
2193
2194            if ($CustomerElementBcc) {
2195
2196                if ( $GetParam{Bcc} ) {
2197                    $GetParam{Bcc} .= ', ' . $CustomerElementBcc;
2198                }
2199                else {
2200                    $GetParam{Bcc} = $CustomerElementBcc;
2201                }
2202
2203                # check email address
2204                my $CustomerErrorMsgBcc = 'CustomerGenericServerErrorMsg';
2205                my $CustomerErrorBcc    = '';
2206                for my $Email ( Mail::Address->parse($CustomerElementBcc) ) {
2207                    if ( !$CheckItemObject->CheckEmail( Address => $Email->address() ) ) {
2208                        $CustomerErrorMsgBcc = $CheckItemObject->CheckErrorType()
2209                            . 'ServerErrorMsg';
2210                        $CustomerErrorBcc = 'ServerError';
2211                    }
2212                }
2213
2214                # check for duplicated entries
2215                if ( defined $AddressesList{$CustomerElementBcc} && $CustomerErrorBcc eq '' ) {
2216                    $CustomerErrorMsgBcc = 'IsDuplicatedServerErrorMsg';
2217                    $CustomerErrorBcc    = 'ServerError';
2218                }
2219
2220                my $CustomerDisabledBcc = '';
2221                my $CountAuxBcc         = $CustomerCounterBcc++;
2222                if ( $CustomerErrorBcc ne '' ) {
2223                    $CustomerDisabledBcc = 'disabled="disabled"';
2224                    $CountAuxBcc         = $Count . 'Error';
2225                }
2226
2227                if ( $CustomerQueueBcc ne '' ) {
2228                    $CustomerQueueBcc = $Count;
2229                }
2230
2231                push @MultipleCustomerBcc, {
2232                    Count            => $CountAuxBcc,
2233                    CustomerElement  => $CustomerElementBcc,
2234                    CustomerKey      => $CustomerKeyBcc,
2235                    CustomerError    => $CustomerErrorBcc,
2236                    CustomerErrorMsg => $CustomerErrorMsgBcc,
2237                    CustomerDisabled => $CustomerDisabledBcc,
2238                    CustomerQueue    => $CustomerQueueBcc,
2239                };
2240                $AddressesList{$CustomerElementBcc} = 1;
2241            }
2242        }
2243    }
2244
2245    return (
2246        GetParam            => \%GetParam,
2247        MultipleCustomer    => \@MultipleCustomer,
2248        MultipleCustomerCc  => \@MultipleCustomerCc,
2249        MultipleCustomerBcc => \@MultipleCustomerBcc,
2250    );
2251}
2252
22531;
2254