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