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::AgentTicketMove;
10
11use strict;
12use warnings;
13
14use Kernel::System::VariableCheck qw(:all);
15use Kernel::Language qw(Translatable);
16
17our $ObjectManagerDisabled = 1;
18
19sub new {
20    my ( $Type, %Param ) = @_;
21
22    # allocate new hash for object
23    my $Self = {%Param};
24    bless( $Self, $Type );
25
26    # Try to load draft if requested.
27    if (
28        $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}")->{FormDraft}
29        && $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'LoadFormDraft' )
30        && $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormDraftID' )
31        )
32    {
33        $Self->{LoadedFormDraftID} = $Kernel::OM->Get('Kernel::System::Web::Request')->LoadFormDraft(
34            FormDraftID => $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormDraftID' ),
35            UserID      => $Self->{UserID},
36        );
37    }
38
39    # get form id
40    $Self->{FormID} = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'FormID' );
41
42    # create form id
43    if ( !$Self->{FormID} ) {
44        $Self->{FormID} = $Kernel::OM->Get('Kernel::System::Web::UploadCache')->FormIDCreate();
45    }
46
47    return $Self;
48}
49
50sub Run {
51    my ( $Self, %Param ) = @_;
52
53    # get needed objects
54    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
55    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
56
57    # check needed stuff
58    for my $Needed (qw(TicketID)) {
59        if ( !$Self->{$Needed} ) {
60            return $LayoutObject->ErrorScreen(
61                Message => $LayoutObject->{LanguageObject}->Translate( 'Need %s!', $Needed ),
62            );
63        }
64    }
65
66    # check permissions
67    my $Access = $TicketObject->TicketPermission(
68        Type     => 'move',
69        TicketID => $Self->{TicketID},
70        UserID   => $Self->{UserID}
71    );
72
73    # error screen, don't show ticket
74    if ( !$Access ) {
75        return $LayoutObject->NoPermission(
76            Message    => Translatable("You need move permissions!"),
77            WithHeader => 'yes',
78        );
79    }
80
81    # get ACL restrictions
82    my %PossibleActions = ( 1 => $Self->{Action} );
83
84    my $ACL = $TicketObject->TicketAcl(
85        Data          => \%PossibleActions,
86        Action        => $Self->{Action},
87        TicketID      => $Self->{TicketID},
88        ReturnType    => 'Action',
89        ReturnSubType => '-',
90        UserID        => $Self->{UserID},
91    );
92    my %AclAction = $TicketObject->TicketAclActionData();
93
94    # check if ACL restrictions exist
95    if ( $ACL || IsHashRefWithData( \%AclAction ) ) {
96
97        my %AclActionLookup = reverse %AclAction;
98
99        # show error screen if ACL prohibits this action
100        if ( !$AclActionLookup{ $Self->{Action} } ) {
101            return $LayoutObject->NoPermission( WithHeader => 'yes' );
102        }
103    }
104
105    # Check for failed draft loading request.
106    if (
107        $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'LoadFormDraft' )
108        && !$Self->{LoadedFormDraftID}
109        )
110    {
111        return $LayoutObject->ErrorScreen(
112            Message => Translatable('Loading draft failed!'),
113            Comment => Translatable('Please contact the administrator.'),
114        );
115    }
116
117    # check if ticket is locked
118    if ( $TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) {
119        my $AccessOk = $TicketObject->OwnerCheck(
120            TicketID => $Self->{TicketID},
121            OwnerID  => $Self->{UserID},
122        );
123        if ( !$AccessOk ) {
124            my $Output = $LayoutObject->Header(
125                Type      => 'Small',
126                BodyClass => 'Popup',
127            );
128            $Output .= $LayoutObject->Warning(
129                Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'),
130                Comment => Translatable('Please change the owner first.'),
131            );
132
133            # show back link
134            $LayoutObject->Block(
135                Name => 'TicketBack',
136                Data => { %Param, TicketID => $Self->{TicketID} },
137            );
138
139            $Output .= $LayoutObject->Footer(
140                Type => 'Small',
141            );
142            return $Output;
143        }
144    }
145
146    # ticket attributes
147    my %Ticket = $TicketObject->TicketGet(
148        TicketID      => $Self->{TicketID},
149        DynamicFields => 1,
150    );
151
152    # get param object
153    my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
154
155    # get params
156    my %GetParam;
157    for my $Parameter (
158        qw(Subject Body
159        NewUserID NewStateID NewPriorityID
160        OwnerAll NoSubmit DestQueueID DestQueue
161        StandardTemplateID CreateArticle FormDraftID Title
162        )
163        )
164    {
165        $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter ) || '';
166    }
167    for my $Parameter (qw(Year Month Day Hour Minute TimeUnits)) {
168        $GetParam{$Parameter} = $ParamObject->GetParam( Param => $Parameter );
169    }
170
171    # ACL compatibility translations
172    my %ACLCompatGetParam;
173    $ACLCompatGetParam{NewOwnerID} = $GetParam{NewUserID};
174    $ACLCompatGetParam{QueueID}    = $GetParam{DestQueueID};
175    $ACLCompatGetParam{Queue}      = $GetParam{DestQueue};
176
177    # get Dynamic fields form ParamObject
178    my %DynamicFieldValues;
179
180    # define the dynamic fields to show based on the object type
181    my $ObjectType = ['Ticket'];
182
183    # get config object
184    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
185
186    # get config for frontend module
187    my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
188
189    # only screens that add notes can modify Article dynamic fields
190    if ( $Config->{Note} ) {
191        $ObjectType = [ 'Ticket', 'Article' ];
192    }
193
194    # get the dynamic fields for this screen
195    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
196        Valid       => 1,
197        ObjectType  => $ObjectType,
198        FieldFilter => $Config->{DynamicField} || {},
199    );
200
201    # get dynamic field backend object
202    my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
203
204    # cycle trough the activated Dynamic Fields for this screen
205    DYNAMICFIELD:
206    for my $DynamicFieldConfig ( @{$DynamicField} ) {
207        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
208
209        # extract the dynamic field value from the web request
210        $DynamicFieldValues{ $DynamicFieldConfig->{Name} } =
211            $DynamicFieldBackendObject->EditFieldValueGet(
212            DynamicFieldConfig => $DynamicFieldConfig,
213            ParamObject        => $ParamObject,
214            LayoutObject       => $LayoutObject,
215            );
216    }
217
218    # convert dynamic field values into a structure for ACLs
219    my %DynamicFieldACLParameters;
220    DYNAMICFIELD:
221    for my $DynamicFieldItem ( sort keys %DynamicFieldValues ) {
222        next DYNAMICFIELD if !$DynamicFieldItem;
223        next DYNAMICFIELD if !defined $DynamicFieldValues{$DynamicFieldItem};
224
225        $DynamicFieldACLParameters{ 'DynamicField_' . $DynamicFieldItem } = $DynamicFieldValues{$DynamicFieldItem};
226    }
227    $GetParam{DynamicField} = \%DynamicFieldACLParameters;
228
229    # transform pending time, time stamp based on user time zone
230    if (
231        defined $GetParam{Year}
232        && defined $GetParam{Month}
233        && defined $GetParam{Day}
234        && defined $GetParam{Hour}
235        && defined $GetParam{Minute}
236        )
237    {
238        %GetParam = $LayoutObject->TransformDateSelection(
239            %GetParam,
240        );
241    }
242
243    # rewrap body if no rich text is used
244    if ( $GetParam{Body} && !$LayoutObject->{BrowserRichText} ) {
245        $GetParam{Body} = $LayoutObject->WrapPlainText(
246            MaxCharacters => $ConfigObject->Get('Ticket::Frontend::TextAreaNote'),
247            PlainText     => $GetParam{Body},
248        );
249    }
250
251    # error handling
252    my %Error;
253
254    # distinguish between action concerning attachments and the move action
255    my $IsUpload = 0;
256
257    # Get and validate draft action.
258    my $FormDraftAction = $ParamObject->GetParam( Param => 'FormDraftAction' );
259    if ( $FormDraftAction && !$Config->{FormDraft} ) {
260        return $LayoutObject->ErrorScreen(
261            Message => Translatable('FormDraft functionality disabled!'),
262            Comment => Translatable('Please contact the administrator.'),
263        );
264    }
265
266    my %FormDraftResponse;
267
268    # Check draft name.
269    if (
270        $FormDraftAction
271        && ( $FormDraftAction eq 'Add' || $FormDraftAction eq 'Update' )
272        )
273    {
274        my $Title = $ParamObject->GetParam( Param => 'FormDraftTitle' );
275
276        # A draft name is required.
277        if ( !$Title ) {
278
279            %FormDraftResponse = (
280                Success      => 0,
281                ErrorMessage => $Kernel::OM->Get('Kernel::Language')->Translate("Draft name is required!"),
282            );
283        }
284
285        # Chosen draft name must be unique.
286        else {
287            my $FormDraftList = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftListGet(
288                ObjectType => 'Ticket',
289                ObjectID   => $Self->{TicketID},
290                Action     => $Self->{Action},
291                UserID     => $Self->{UserID},
292            );
293            DRAFT:
294            for my $FormDraft ( @{$FormDraftList} ) {
295
296                # No existing draft with same name.
297                next DRAFT if $Title ne $FormDraft->{Title};
298
299                # Same name for update on existing draft.
300                if (
301                    $GetParam{FormDraftID}
302                    && $FormDraftAction eq 'Update'
303                    && $GetParam{FormDraftID} eq $FormDraft->{FormDraftID}
304                    )
305                {
306                    next DRAFT;
307                }
308
309                # Another draft with the chosen name already exists.
310                %FormDraftResponse = (
311                    Success      => 0,
312                    ErrorMessage => $Kernel::OM->Get('Kernel::Language')
313                        ->Translate( "FormDraft name %s is already in use!", $Title ),
314                );
315                $IsUpload = 1;
316                last DRAFT;
317            }
318        }
319    }
320
321    # Perform draft action instead of saving form data in ticket/article.
322    if ( $FormDraftAction && !%FormDraftResponse ) {
323
324        # Reset FormDraftID to prevent updating existing draft.
325        if ( $FormDraftAction eq 'Add' && $GetParam{FormDraftID} ) {
326            $ParamObject->{Query}->param(
327                -name  => 'FormDraftID',
328                -value => '',
329            );
330        }
331
332        my $FormDraftActionOk;
333        if (
334            $FormDraftAction eq 'Add'
335            ||
336            ( $FormDraftAction eq 'Update' && $GetParam{FormDraftID} )
337            )
338        {
339            $FormDraftActionOk = $ParamObject->SaveFormDraft(
340                UserID         => $Self->{UserID},
341                ObjectType     => 'Ticket',
342                ObjectID       => $Self->{TicketID},
343                OverrideParams => {
344                    NoSubmit     => 1,
345                    TicketUnlock => undef,
346                },
347            );
348        }
349        elsif ( $FormDraftAction eq 'Delete' && $GetParam{FormDraftID} ) {
350            $FormDraftActionOk = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete(
351                FormDraftID => $GetParam{FormDraftID},
352                UserID      => $Self->{UserID},
353            );
354        }
355
356        if ($FormDraftActionOk) {
357            $FormDraftResponse{Success} = 1;
358        }
359        else {
360            %FormDraftResponse = (
361                Success      => 0,
362                ErrorMessage => 'Could not perform requested draft action!',
363            );
364        }
365    }
366
367    if (%FormDraftResponse) {
368
369        # build JSON output
370        my $JSON = $LayoutObject->JSONEncode(
371            Data => \%FormDraftResponse,
372        );
373
374        # send JSON response
375        return $LayoutObject->Attachment(
376            ContentType => 'application/json; charset=' . $LayoutObject->{Charset},
377            Content     => $JSON,
378            Type        => 'inline',
379            NoCache     => 1,
380        );
381    }
382
383    # DestQueueID lookup
384    if ( !$GetParam{DestQueueID} && $GetParam{DestQueue} ) {
385        $GetParam{DestQueueID}
386            = $Kernel::OM->Get('Kernel::System::Queue')->QueueLookup( Queue => $GetParam{DestQueue} );
387    }
388    if ( !$GetParam{DestQueueID} ) {
389        $Error{DestQueue} = 1;
390    }
391
392    # Check if destination queue is restricted by ACL.
393    my $DestQueues = $Self->_GetQueues(
394        %GetParam,
395        %ACLCompatGetParam,
396        TicketID => $Self->{TicketID},
397    );
398    if ( $GetParam{DestQueueID} && !exists $DestQueues->{ $GetParam{DestQueueID} } ) {
399        return $LayoutObject->NoPermission( WithHeader => 'yes' );
400    }
401
402    # do not submit
403    if ( $GetParam{NoSubmit} ) {
404        $Error{NoSubmit} = 1;
405    }
406
407    # get upload cache object
408    my $UploadCacheObject = $Kernel::OM->Get('Kernel::System::Web::UploadCache');
409
410    # Check if TreeView list type is activated.
411    my $TreeView = 0;
412    if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) {
413        $TreeView = 1;
414    }
415
416    # Ajax update
417    if ( $Self->{Subaction} eq 'AJAXUpdate' ) {
418        my $ElementChanged = $ParamObject->GetParam( Param => 'ElementChanged' ) || '';
419
420        my $NewUsers = $Self->_GetUsers(
421            %GetParam,
422            %ACLCompatGetParam,
423            QueueID  => $GetParam{DestQueueID},
424            AllUsers => $GetParam{OwnerAll},
425        );
426        my $NextStates = $Self->_GetNextStates(
427            %GetParam,
428            %ACLCompatGetParam,
429            TicketID => $Self->{TicketID},
430            QueueID  => $GetParam{DestQueueID} || $Ticket{QueueID},
431        );
432        my $NextPriorities = $Self->_GetPriorities(
433            %GetParam,
434            %ACLCompatGetParam,
435            TicketID => $Self->{TicketID},
436            QueueID  => $GetParam{DestQueueID} || $Ticket{QueueID},
437        );
438
439        # update Dynamc Fields Possible Values via AJAX
440        my @DynamicFieldAJAX;
441
442        # cycle trough the activated Dynamic Fields for this screen
443        DYNAMICFIELD:
444        for my $DynamicFieldConfig ( @{$DynamicField} ) {
445            next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
446
447            my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
448                DynamicFieldConfig => $DynamicFieldConfig,
449                Behavior           => 'IsACLReducible',
450            );
451            next DYNAMICFIELD if !$IsACLReducible;
452
453            my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
454                DynamicFieldConfig => $DynamicFieldConfig,
455            );
456
457            # convert possible values key => value to key => key for ACLs using a Hash slice
458            my %AclData = %{$PossibleValues};
459            @AclData{ keys %AclData } = keys %AclData;
460
461            # set possible values filter from ACLs
462            my $ACL = $TicketObject->TicketAcl(
463                %GetParam,
464                %ACLCompatGetParam,
465                Action        => $Self->{Action},
466                TicketID      => $Self->{TicketID},
467                QueueID       => $GetParam{DestQueueID} || 0,
468                ReturnType    => 'Ticket',
469                ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
470                Data          => \%AclData,
471                UserID        => $Self->{UserID},
472            );
473            if ($ACL) {
474                my %Filter = $TicketObject->TicketAclData();
475
476                # convert Filer key => key back to key => value using map
477                %{$PossibleValues} = map { $_ => $PossibleValues->{$_} } keys %Filter;
478            }
479
480            my $DataValues = $DynamicFieldBackendObject->BuildSelectionDataGet(
481                DynamicFieldConfig => $DynamicFieldConfig,
482                PossibleValues     => $PossibleValues,
483                Value              => $DynamicFieldValues{ $DynamicFieldConfig->{Name} },
484            ) || $PossibleValues;
485
486            # add dynamic field to the list of fields to update
487            push(
488                @DynamicFieldAJAX,
489                {
490                    Name        => 'DynamicField_' . $DynamicFieldConfig->{Name},
491                    Data        => $DataValues,
492                    SelectedID  => $DynamicFieldValues{ $DynamicFieldConfig->{Name} },
493                    Translation => $DynamicFieldConfig->{Config}->{TranslatableValues} || 0,
494                    Max         => 100,
495                }
496            );
497        }
498
499        my $StandardTemplates = $Self->_GetStandardTemplates(
500            %GetParam,
501            QueueID  => $GetParam{DestQueueID} || '',
502            TicketID => $Self->{TicketID},
503        );
504
505        my @TemplateAJAX;
506
507        # update ticket body and attachments if needed.
508        if ( $ElementChanged eq 'StandardTemplateID' ) {
509            my @TicketAttachments;
510            my $TemplateText;
511
512            # remove all attachments from the Upload cache
513            my $RemoveSuccess = $UploadCacheObject->FormIDRemove(
514                FormID => $Self->{FormID},
515            );
516            if ( !$RemoveSuccess ) {
517                $Kernel::OM->Get('Kernel::System::Log')->Log(
518                    Priority => 'error',
519                    Message  => "Form attachments could not be deleted!",
520                );
521            }
522
523            # get the template text and set new attachments if a template is selected
524            if ( IsPositiveInteger( $GetParam{StandardTemplateID} ) ) {
525                my $TemplateGenerator = $Kernel::OM->Get('Kernel::System::TemplateGenerator');
526
527                # set template text, replace smart tags (limited as ticket is not created)
528                $TemplateText = $TemplateGenerator->Template(
529                    TemplateID => $GetParam{StandardTemplateID},
530                    TicketID   => $Self->{TicketID},
531                    UserID     => $Self->{UserID},
532                );
533
534                # create StdAttachmentObject
535                my $StdAttachmentObject = $Kernel::OM->Get('Kernel::System::StdAttachment');
536
537                # add std. attachments to ticket
538                my %AllStdAttachments = $StdAttachmentObject->StdAttachmentStandardTemplateMemberList(
539                    StandardTemplateID => $GetParam{StandardTemplateID},
540                );
541                for ( sort keys %AllStdAttachments ) {
542                    my %AttachmentsData = $StdAttachmentObject->StdAttachmentGet( ID => $_ );
543                    $UploadCacheObject->FormIDAddFile(
544                        FormID      => $Self->{FormID},
545                        Disposition => 'attachment',
546                        %AttachmentsData,
547                    );
548                }
549
550                # send a list of attachments in the upload cache back to the clientside JavaScript
551                # which renders then the list of currently uploaded attachments
552                @TicketAttachments = $UploadCacheObject->FormIDGetAllFilesMeta(
553                    FormID => $Self->{FormID},
554                );
555
556                for my $Attachment (@TicketAttachments) {
557                    $Attachment->{Filesize} = $LayoutObject->HumanReadableDataSize(
558                        Size => $Attachment->{Filesize},
559                    );
560                }
561            }
562
563            @TemplateAJAX = (
564                {
565                    Name => 'UseTemplateNote',
566                    Data => '0',
567                },
568                {
569                    Name => 'RichText',
570                    Data => $TemplateText || '',
571                },
572                {
573                    Name     => 'TicketAttachments',
574                    Data     => \@TicketAttachments,
575                    KeepData => 1,
576                },
577            );
578        }
579
580        my $JSON = $LayoutObject->BuildSelectionJSON(
581            [
582                {
583                    Name         => 'NewUserID',
584                    Data         => $NewUsers,
585                    SelectedID   => $GetParam{NewUserID},
586                    Translation  => 0,
587                    PossibleNone => 1,
588                    Max          => 100,
589                },
590                {
591                    Name         => 'NewStateID',
592                    Data         => $NextStates,
593                    SelectedID   => $GetParam{NewStateID},
594                    Translation  => 1,
595                    PossibleNone => 1,
596                    Max          => 100,
597                },
598                {
599                    Name         => 'NewPriorityID',
600                    Data         => $NextPriorities,
601                    SelectedID   => $GetParam{NewPriorityID},
602                    Translation  => 1,
603                    PossibleNone => 1,
604                    Max          => 100,
605                },
606                {
607                    Name         => 'StandardTemplateID',
608                    Data         => $StandardTemplates,
609                    SelectedID   => $GetParam{StandardTemplateID},
610                    PossibleNone => 1,
611                    Translation  => 1,
612                    Max          => 100,
613                },
614                {
615                    Name         => 'DestQueueID',
616                    Data         => $DestQueues,
617                    SelectedID   => $GetParam{DestQueueID},
618                    PossibleNone => 1,
619                    Translation  => 0,
620                    TreeView     => $TreeView,
621                    Max          => 100,
622                },
623                @DynamicFieldAJAX,
624                @TemplateAJAX,
625            ],
626        );
627        return $LayoutObject->Attachment(
628            ContentType => 'application/json; charset=' . $LayoutObject->{Charset},
629            Content     => $JSON,
630            Type        => 'inline',
631            NoCache     => 1,
632        );
633    }
634
635    # create HTML strings for all dynamic fields
636    my %DynamicFieldHTML;
637
638    # cycle trough the activated Dynamic Fields for this screen
639    DYNAMICFIELD:
640    for my $DynamicFieldConfig ( @{$DynamicField} ) {
641        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
642
643        my $PossibleValuesFilter;
644
645        my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
646            DynamicFieldConfig => $DynamicFieldConfig,
647            Behavior           => 'IsACLReducible',
648        );
649
650        if ($IsACLReducible) {
651
652            # get PossibleValues
653            my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
654                DynamicFieldConfig => $DynamicFieldConfig,
655            );
656
657            # check if field has PossibleValues property in its configuration
658            if ( IsHashRefWithData($PossibleValues) ) {
659
660                # convert possible values key => value to key => key for ACLs using a Hash slice
661                my %AclData = %{$PossibleValues};
662                @AclData{ keys %AclData } = keys %AclData;
663
664                # set possible values filter from ACLs
665                my $ACL = $TicketObject->TicketAcl(
666                    %GetParam,
667                    %ACLCompatGetParam,
668                    Action        => $Self->{Action},
669                    TicketID      => $Self->{TicketID},
670                    ReturnType    => 'Ticket',
671                    ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
672                    Data          => \%AclData,
673                    UserID        => $Self->{UserID},
674                );
675                if ($ACL) {
676                    my %Filter = $TicketObject->TicketAclData();
677
678                    # convert Filer key => key back to key => value using map
679                    %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} }
680                        keys %Filter;
681                }
682            }
683        }
684
685        # to store dynamic field value from database (or undefined)
686        my $Value;
687
688        # only get values for Ticket fields (all screens based on AgentTickeActionCommon
689        # generates a new article, then article fields will be always empty at the beginign)
690        if ( $DynamicFieldConfig->{ObjectType} eq 'Ticket' ) {
691
692            # get value stored on the database from Ticket
693            $Value = $Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} };
694        }
695
696        # get field html
697        $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } =
698            $DynamicFieldBackendObject->EditFieldRender(
699            DynamicFieldConfig   => $DynamicFieldConfig,
700            PossibleValuesFilter => $PossibleValuesFilter,
701            Value                => $Value,
702            Mandatory =>
703                $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2,
704            LayoutObject    => $LayoutObject,
705            ParamObject     => $ParamObject,
706            AJAXUpdate      => 1,
707            UpdatableFields => $Self->_GetFieldsToUpdate(),
708            );
709    }
710
711    # get state object
712    my $StateObject = $Kernel::OM->Get('Kernel::System::State');
713
714    # move action
715    if ( $Self->{Subaction} eq 'MoveTicket' ) {
716
717        # challenge token check for write action
718        $LayoutObject->ChallengeTokenCheck();
719
720        if ( $GetParam{DestQueueID} eq '' ) {
721            $Error{'DestQueueIDInvalid'} = 'ServerError';
722        }
723
724        # check time units
725        if (
726            $ConfigObject->Get('Ticket::Frontend::AccountTime')
727            && $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime')
728            && $GetParam{TimeUnits} eq ''
729            && $Config->{Note}
730            )
731        {
732            $Error{'TimeUnitsInvalid'} = ' ServerError';
733        }
734
735        # check pending time
736        if ( $GetParam{NewStateID} ) {
737            my %StateData = $StateObject->StateGet(
738                ID => $GetParam{NewStateID},
739            );
740
741            # check state type
742            if ( $StateData{TypeName} =~ /^pending/i ) {
743
744                # check needed stuff
745                for my $TimeParameter (qw(Year Month Day Hour Minute)) {
746                    if ( !defined $GetParam{$TimeParameter} ) {
747                        $Error{'DateInvalid'} = 'ServerError';
748                    }
749                }
750
751                # create a datetime object based on pending date
752                my $PendingDateTimeObject = $Kernel::OM->Create(
753                    'Kernel::System::DateTime',
754                    ObjectParams => {
755                        %GetParam,
756                        Second => 0,
757                    },
758                );
759
760                # get current system epoch
761                my $CurSystemDateTime = $Kernel::OM->Create('Kernel::System::DateTime');
762
763                if (
764                    !$PendingDateTimeObject
765                    || $PendingDateTimeObject < $CurSystemDateTime
766                    )
767                {
768                    $Error{'DateInvalid'} = 'ServerError';
769                }
770            }
771        }
772
773        if ( $Config->{Note} && $Config->{NoteMandatory} ) {
774
775            # check subject
776            if ( !$GetParam{Subject} ) {
777                $Error{'SubjectInvalid'} = 'ServerError';
778            }
779
780            # check body
781            if ( !$GetParam{Body} ) {
782                $Error{'BodyInvalid'} = 'ServerError';
783            }
784        }
785
786        # check mandatory state
787        if ( $Config->{State} && $Config->{StateMandatory} ) {
788            if ( !$GetParam{NewStateID} ) {
789                $Error{'NewStateInvalid'} = 'ServerError';
790            }
791        }
792
793        # clear DynamicFieldHTML
794        %DynamicFieldHTML = ();
795
796        # cycle trough the activated Dynamic Fields for this screen
797        DYNAMICFIELD:
798        for my $DynamicFieldConfig ( @{$DynamicField} ) {
799            next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
800
801            my $PossibleValuesFilter;
802
803            my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
804                DynamicFieldConfig => $DynamicFieldConfig,
805                Behavior           => 'IsACLReducible',
806            );
807
808            if ($IsACLReducible) {
809
810                # get PossibleValues
811                my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
812                    DynamicFieldConfig => $DynamicFieldConfig,
813                );
814
815                # check if field has PossibleValues property in its configuration
816                if ( IsHashRefWithData($PossibleValues) ) {
817
818                    # convert possible values key => value to key => key for ACLs using a Hash slice
819                    my %AclData = %{$PossibleValues};
820                    @AclData{ keys %AclData } = keys %AclData;
821
822                    # set possible values filter from ACLs
823                    my $ACL = $TicketObject->TicketAcl(
824                        %GetParam,
825                        %ACLCompatGetParam,
826                        Action        => $Self->{Action},
827                        TicketID      => $Self->{TicketID},
828                        ReturnType    => 'Ticket',
829                        ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
830                        Data          => \%AclData,
831                        UserID        => $Self->{UserID},
832                    );
833                    if ($ACL) {
834                        my %Filter = $TicketObject->TicketAclData();
835
836                        # convert Filer key => key back to key => value using map
837                        %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} }
838                            keys %Filter;
839                    }
840                }
841            }
842
843            my $ValidationResult = $DynamicFieldBackendObject->EditFieldValueValidate(
844                DynamicFieldConfig   => $DynamicFieldConfig,
845                PossibleValuesFilter => $PossibleValuesFilter,
846                ParamObject          => $ParamObject,
847                Mandatory =>
848                    $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2,
849            );
850
851            if ( !IsHashRefWithData($ValidationResult) ) {
852                return $LayoutObject->ErrorScreen(
853                    Message => $LayoutObject->{LanguageObject}->Translate(
854                        'Could not perform validation on field %s!',
855                        $DynamicFieldConfig->{Label},
856                    ),
857                    Comment => Translatable('Please contact the administrator.'),
858                );
859            }
860
861            # propagate validation error to the Error variable to be detected by the frontend
862            if ( $ValidationResult->{ServerError} ) {
863                $Error{ $DynamicFieldConfig->{Name} } = ' ServerError';
864            }
865
866            # get field html
867            $DynamicFieldHTML{ $DynamicFieldConfig->{Name} } = $DynamicFieldBackendObject->EditFieldRender(
868                DynamicFieldConfig   => $DynamicFieldConfig,
869                PossibleValuesFilter => $PossibleValuesFilter,
870                Mandatory =>
871                    $Config->{DynamicField}->{ $DynamicFieldConfig->{Name} } == 2,
872                ServerError  => $ValidationResult->{ServerError}  || '',
873                ErrorMessage => $ValidationResult->{ErrorMessage} || '',
874                LayoutObject => $LayoutObject,
875                ParamObject  => $ParamObject,
876                AJAXUpdate   => 1,
877                UpdatableFields => $Self->_GetFieldsToUpdate(),
878            );
879        }
880    }
881
882    # get params
883    my $TicketUnlock = $ParamObject->GetParam( Param => 'TicketUnlock' );
884
885    # check errors
886    if (%Error) {
887
888        my $Output = $LayoutObject->Header(
889            Type      => 'Small',
890            BodyClass => 'Popup',
891        );
892
893        # check if lock is required
894        if ( $Config->{RequiredLock} ) {
895
896            # get lock state && write (lock) permissions
897            if ( !$TicketObject->TicketLockGet( TicketID => $Self->{TicketID} ) ) {
898
899                my $Lock = $TicketObject->TicketLockSet(
900                    TicketID => $Self->{TicketID},
901                    Lock     => 'lock',
902                    UserID   => $Self->{UserID}
903                );
904
905                if ($Lock) {
906
907                    # Set new owner if ticket owner is different then logged user.
908                    if ( $Ticket{OwnerID} != $Self->{UserID} ) {
909
910                        # Remember previous owner, which will be used to restore ticket owner on undo action.
911                        $Param{PreviousOwner} = $Ticket{OwnerID};
912
913                        $TicketObject->TicketOwnerSet(
914                            TicketID  => $Self->{TicketID},
915                            UserID    => $Self->{UserID},
916                            NewUserID => $Self->{UserID},
917                        );
918                    }
919
920                    # Show lock state.
921                    $LayoutObject->Block(
922                        Name => 'PropertiesLock',
923                        Data => {
924                            %Param,
925                            TicketID => $Self->{TicketID}
926                        },
927                    );
928                    $TicketUnlock = 1;
929                }
930            }
931            else {
932                my $AccessOk = $TicketObject->OwnerCheck(
933                    TicketID => $Self->{TicketID},
934                    OwnerID  => $Self->{UserID},
935                );
936                if ( !$AccessOk ) {
937
938                    my $Output = $LayoutObject->Header(
939                        Type      => 'Small',
940                        BodyClass => 'Popup',
941                    );
942                    $Output .= $LayoutObject->Warning(
943                        Message => Translatable('Sorry, you need to be the ticket owner to perform this action.'),
944                        Comment => Translatable('Please change the owner first.'),
945                    );
946
947                    # show back link
948                    $LayoutObject->Block(
949                        Name => 'TicketBack',
950                        Data => { %Param, TicketID => $Self->{TicketID} },
951                    );
952
953                    $Output .= $LayoutObject->Footer(
954                        Type => 'Small',
955                    );
956                    return $Output;
957                }
958
959                # show back link
960                $LayoutObject->Block(
961                    Name => 'TicketBack',
962                    Data => { %Param, TicketID => $Self->{TicketID} },
963                );
964            }
965        }
966        else {
967
968            # show back link
969            $LayoutObject->Block(
970                Name => 'TicketBack',
971                Data => { %Param, TicketID => $Self->{TicketID} },
972            );
973        }
974
975        # fetch all queues
976        my %MoveQueues = $TicketObject->MoveList(
977            %GetParam,
978            %ACLCompatGetParam,
979            TicketID => $Self->{TicketID},
980            UserID   => $Self->{UserID},
981            Action   => $Self->{Action},
982            Type     => 'move_into',
983        );
984
985        # get next states
986        my $NextStates = $Self->_GetNextStates(
987            %GetParam,
988            %ACLCompatGetParam,
989            TicketID => $Self->{TicketID},
990            QueueID  => $GetParam{DestQueueID} || $Ticket{QueueID},
991        );
992
993        # get next priorities
994        my $NextPriorities = $Self->_GetPriorities(
995            %GetParam,
996            %ACLCompatGetParam,
997            TicketID => $Self->{TicketID},
998            QueueID  => $GetParam{DestQueueID} || $Ticket{QueueID},
999        );
1000
1001        # get old owners
1002        my @OldUserInfo = $TicketObject->TicketOwnerList(
1003            %GetParam,
1004            %ACLCompatGetParam,
1005            TicketID => $Self->{TicketID}
1006        );
1007
1008        # get all attachments meta data
1009        my @Attachments = $UploadCacheObject->FormIDGetAllFilesMeta(
1010            FormID => $Self->{FormID},
1011        );
1012
1013        # print change form
1014        $Output .= $Self->AgentMove(
1015            OldUser        => \@OldUserInfo,
1016            Attachments    => \@Attachments,
1017            MoveQueues     => \%MoveQueues,
1018            TicketID       => $Self->{TicketID},
1019            NextStates     => $NextStates,
1020            NextPriorities => $NextPriorities,
1021            TicketUnlock   => $TicketUnlock,
1022            TimeUnits      => $GetParam{TimeUnits},
1023            FormID         => $Self->{FormID},
1024
1025            %Ticket,
1026            DynamicFieldHTML => \%DynamicFieldHTML,
1027            %GetParam,
1028            %Error,
1029        );
1030        $Output .= $LayoutObject->Footer(
1031            Type => 'Small',
1032        );
1033        return $Output;
1034    }
1035
1036    # move ticket (send notification if no new owner is selected)
1037    my $BodyAsText = '';
1038    if ( $LayoutObject->{BrowserRichText} ) {
1039        $BodyAsText = $LayoutObject->RichText2Ascii(
1040            String => $GetParam{Body} || 0,
1041        );
1042    }
1043    else {
1044        $BodyAsText = $GetParam{Body} || 0;
1045    }
1046    my $Move = $TicketObject->TicketQueueSet(
1047        QueueID            => $GetParam{DestQueueID},
1048        UserID             => $Self->{UserID},
1049        TicketID           => $Self->{TicketID},
1050        SendNoNotification => $GetParam{NewUserID},
1051        Comment            => $BodyAsText,
1052    );
1053    if ( !$Move ) {
1054        return $LayoutObject->ErrorScreen();
1055    }
1056
1057    # set priority
1058    if ( $Config->{Priority} && $GetParam{NewPriorityID} ) {
1059        $TicketObject->TicketPrioritySet(
1060            TicketID   => $Self->{TicketID},
1061            PriorityID => $GetParam{NewPriorityID},
1062            UserID     => $Self->{UserID},
1063        );
1064    }
1065
1066    # set state
1067    if ( $Config->{State} && $GetParam{NewStateID} ) {
1068        $TicketObject->TicketStateSet(
1069            TicketID => $Self->{TicketID},
1070            StateID  => $GetParam{NewStateID},
1071            UserID   => $Self->{UserID},
1072        );
1073
1074        # unlock the ticket after close
1075        my %StateData = $StateObject->StateGet(
1076            ID => $GetParam{NewStateID},
1077        );
1078
1079        # set unlock on close
1080        if ( $StateData{TypeName} =~ /^close/i ) {
1081            $TicketObject->TicketLockSet(
1082                TicketID => $Self->{TicketID},
1083                Lock     => 'unlock',
1084                UserID   => $Self->{UserID},
1085            );
1086        }
1087
1088        # set pending time on pending state
1089        elsif ( $StateData{TypeName} =~ /^pending/i ) {
1090
1091            # set pending time
1092            $TicketObject->TicketPendingTimeSet(
1093                UserID   => $Self->{UserID},
1094                TicketID => $Self->{TicketID},
1095                Year     => $GetParam{Year},
1096                Month    => $GetParam{Month},
1097                Day      => $GetParam{Day},
1098                Hour     => $GetParam{Hour},
1099                Minute   => $GetParam{Minute},
1100            );
1101        }
1102    }
1103
1104    # check if new user is given and send notification
1105    if ( $GetParam{NewUserID} ) {
1106
1107        # lock
1108        $TicketObject->TicketLockSet(
1109            TicketID => $Self->{TicketID},
1110            Lock     => 'lock',
1111            UserID   => $Self->{UserID},
1112        );
1113
1114        # set owner
1115        $TicketObject->TicketOwnerSet(
1116            TicketID  => $Self->{TicketID},
1117            UserID    => $Self->{UserID},
1118            NewUserID => $GetParam{NewUserID},
1119            Comment   => $BodyAsText,
1120        );
1121    }
1122
1123    # force unlock if no new owner is set and ticket was unlocked
1124    else {
1125        if ($TicketUnlock) {
1126            $TicketObject->TicketLockSet(
1127                TicketID => $Self->{TicketID},
1128                Lock     => 'unlock',
1129                UserID   => $Self->{UserID},
1130            );
1131        }
1132    }
1133
1134    # add note (send no notification)
1135    my $ArticleID;
1136
1137    my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
1138
1139    if (
1140        $GetParam{CreateArticle}
1141        && $Config->{Note}
1142        && ( $GetParam{Body} || $GetParam{Subject} )
1143        )
1144    {
1145
1146        # get pre-loaded attachments
1147        my @AttachmentData = $UploadCacheObject->FormIDGetAllFilesData(
1148            FormID => $Self->{FormID},
1149        );
1150
1151        # get submitted attachment
1152        my %UploadStuff = $ParamObject->GetUploadAll(
1153            Param => 'FileUpload',
1154        );
1155        if (%UploadStuff) {
1156            push @AttachmentData, \%UploadStuff;
1157        }
1158
1159        my $MimeType = 'text/plain';
1160        if ( $LayoutObject->{BrowserRichText} ) {
1161            $MimeType = 'text/html';
1162
1163            # remove unused inline images
1164            my @NewAttachmentData;
1165            ATTACHMENT:
1166            for my $Attachment (@AttachmentData) {
1167                my $ContentID = $Attachment->{ContentID};
1168                if (
1169                    $ContentID
1170                    && ( $Attachment->{ContentType} =~ /image/i )
1171                    && ( $Attachment->{Disposition} eq 'inline' )
1172                    )
1173                {
1174                    my $ContentIDHTMLQuote = $LayoutObject->Ascii2Html(
1175                        Text => $ContentID,
1176                    );
1177                    next ATTACHMENT
1178                        if $GetParam{Body} !~ /(\Q$ContentIDHTMLQuote\E|\Q$ContentID\E)/i;
1179                }
1180
1181                # remember inline images and normal attachments
1182                push @NewAttachmentData, \%{$Attachment};
1183            }
1184            @AttachmentData = @NewAttachmentData;
1185
1186            # verify HTML document
1187            $GetParam{Body} = $LayoutObject->RichTextDocumentComplete(
1188                String => $GetParam{Body},
1189            );
1190        }
1191
1192        my $InternalArticleBackendObject = $ArticleObject->BackendForChannel( ChannelName => 'Internal' );
1193        $ArticleID = $InternalArticleBackendObject->ArticleCreate(
1194            TicketID             => $Self->{TicketID},
1195            IsVisibleForCustomer => 0,
1196            SenderType           => 'agent',
1197            From                 => "\"$Self->{UserFullname}\" <$Self->{UserEmail}>",
1198            Subject              => $GetParam{Subject},
1199            Body                 => $GetParam{Body},
1200            MimeType             => $MimeType,
1201            Charset              => $LayoutObject->{UserCharset},
1202            UserID               => $Self->{UserID},
1203            HistoryType          => 'AddNote',
1204            HistoryComment       => '%%Move',
1205            NoAgentNotify        => 1,
1206        );
1207        if ( !$ArticleID ) {
1208            return $LayoutObject->ErrorScreen();
1209        }
1210
1211        # write attachments
1212        for my $Attachment (@AttachmentData) {
1213            $InternalArticleBackendObject->ArticleWriteAttachment(
1214                %{$Attachment},
1215                ArticleID => $ArticleID,
1216                UserID    => $Self->{UserID},
1217            );
1218        }
1219
1220        # remove pre-submitted attachments
1221        $UploadCacheObject->FormIDRemove( FormID => $Self->{FormID} );
1222    }
1223
1224    # only set the dynamic fields if the new window was displayed (link), otherwise if ticket was
1225    # moved from the dropdown menu (form) in AgentTicketZoom, the value if the dynamic fields will
1226    # be undefined and it will set to empty in the DB, see bug#8481
1227    if ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'link' ) {
1228
1229        # cycle trough the activated Dynamic Fields for this screen
1230        DYNAMICFIELD:
1231        for my $DynamicFieldConfig ( @{$DynamicField} ) {
1232            next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1233
1234            # set the object ID (TicketID or ArticleID) depending on the field configration
1235            my $ObjectID = $DynamicFieldConfig->{ObjectType} eq 'Article' ? $ArticleID : $Self->{TicketID};
1236
1237            # set dynamic field; when ObjectType=Article and no article will be created ignore
1238            # this dynamic field
1239            if ($ObjectID) {
1240
1241                # set the value
1242                $DynamicFieldBackendObject->ValueSet(
1243                    DynamicFieldConfig => $DynamicFieldConfig,
1244                    ObjectID           => $ObjectID,
1245                    Value              => $DynamicFieldValues{ $DynamicFieldConfig->{Name} },
1246                    UserID             => $Self->{UserID},
1247                );
1248            }
1249        }
1250    }
1251
1252    # time accounting
1253    if ( $GetParam{TimeUnits} ) {
1254        $TicketObject->TicketAccountTime(
1255            TicketID  => $Self->{TicketID},
1256            ArticleID => $ArticleID,
1257            TimeUnit  => $GetParam{TimeUnits},
1258            UserID    => $Self->{UserID},
1259        );
1260    }
1261
1262    # If form was called based on a draft,
1263    #   delete draft since its content has now been used.
1264    if (
1265        $GetParam{FormDraftID}
1266        && !$Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftDelete(
1267            FormDraftID => $GetParam{FormDraftID},
1268            UserID      => $Self->{UserID},
1269        )
1270        )
1271    {
1272        return $LayoutObject->ErrorScreen(
1273            Message => Translatable('Could not delete draft!'),
1274            Comment => Translatable('Please contact the administrator.'),
1275        );
1276    }
1277
1278    # check permission for redirect
1279    my $AccessNew = $TicketObject->TicketPermission(
1280        Type     => 'ro',
1281        TicketID => $Self->{TicketID},
1282        UserID   => $Self->{UserID}
1283    );
1284
1285    my $NextScreen = $Config->{NextScreen} || '';
1286
1287    # redirect to last overview if we do not have ro permissions anymore,
1288    # or if SysConfig option is set.
1289    if ( !$AccessNew || $NextScreen eq 'LastScreenOverview' ) {
1290
1291        # Module directly called
1292        if ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'form' ) {
1293            return $LayoutObject->Redirect( OP => $Self->{LastScreenOverview} );
1294        }
1295
1296        # Module opened in popup
1297        elsif ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'link' ) {
1298            return $LayoutObject->PopupClose(
1299                URL => ( $Self->{LastScreenOverview} || 'Action=AgentDashboard' ),
1300            );
1301        }
1302    }
1303
1304    # Module directly called
1305    if ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'form' ) {
1306        return $LayoutObject->Redirect(
1307            OP => "Action=AgentTicketZoom;TicketID=$Self->{TicketID}"
1308                . ( $ArticleID ? ";ArticleID=$ArticleID" : '' ),
1309        );
1310    }
1311
1312    # Module opened in popup
1313    elsif ( $ConfigObject->Get('Ticket::Frontend::MoveType') eq 'link' ) {
1314        return $LayoutObject->PopupClose(
1315            URL => "Action=AgentTicketZoom;TicketID=$Self->{TicketID}"
1316                . ( $ArticleID ? ";ArticleID=$ArticleID" : '' ),
1317        );
1318    }
1319}
1320
1321sub AgentMove {
1322    my ( $Self, %Param ) = @_;
1323
1324    $Param{DestQueueIDInvalid} = $Param{DestQueueIDInvalid} || '';
1325
1326    # get config object
1327    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1328
1329    # get list type
1330    my $TreeView = 0;
1331    if ( $ConfigObject->Get('Ticket::Frontend::ListType') eq 'tree' ) {
1332        $TreeView = 1;
1333    }
1334
1335    # get config for frontend module
1336    my $Config = $ConfigObject->Get("Ticket::Frontend::$Self->{Action}");
1337
1338    # get layout object
1339    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
1340
1341    my %Data       = %{ $Param{MoveQueues} };
1342    my %MoveQueues = %Data;
1343    my %UsedData;
1344
1345    my $DynamicFieldNames = $Self->_GetFieldsToUpdate(
1346        OnlyDynamicFields => 1
1347    );
1348
1349    # send data to JS
1350    $LayoutObject->AddJSData(
1351        Key   => 'DynamicFieldNames',
1352        Value => $DynamicFieldNames
1353    );
1354
1355    # build next states string
1356    $Param{NextStatesStrg} = $LayoutObject->BuildSelection(
1357        Data         => $Param{NextStates},
1358        Name         => 'NewStateID',
1359        SelectedID   => $Param{NewStateID},
1360        Translation  => 1,
1361        PossibleNone => 1,
1362        Class        => 'Modernize '
1363            . ( $Config->{StateMandatory} ? 'Validate_Required ' : '' )
1364            . ( $Param{NewStateInvalid} || '' ),
1365    );
1366
1367    # build next priority string
1368    $Param{NextPrioritiesStrg} = $LayoutObject->BuildSelection(
1369        Data         => $Param{NextPriorities},
1370        Name         => 'NewPriorityID',
1371        SelectedID   => $Param{NewPriorityID},
1372        Translation  => 1,
1373        PossibleNone => 1,
1374        Class        => 'Modernize',
1375    );
1376
1377    # build owner string
1378    $Param{OwnerStrg} = $LayoutObject->BuildSelection(
1379        Data => $Self->_GetUsers(
1380            QueueID  => $Param{DestQueueID},
1381            AllUsers => $Param{OwnerAll},
1382        ),
1383        Name         => 'NewUserID',
1384        SelectedID   => $Param{NewUserID},
1385        Translation  => 0,
1386        PossibleNone => 1,
1387        Class        => 'Modernize',
1388        Filters      => {
1389            OldOwners => {
1390                Name   => $LayoutObject->{LanguageObject}->Translate('Previous Owner'),
1391                Values => $Self->_GetOldOwners(
1392                    QueueID  => $Param{DestQueueID},
1393                    AllUsers => $Param{OwnerAll},
1394                ),
1395            },
1396        },
1397    );
1398
1399    $LayoutObject->Block(
1400        Name => 'Owner',
1401        Data => \%Param,
1402    );
1403
1404    # set state
1405    if ( $Config->{State} ) {
1406        $LayoutObject->Block(
1407            Name => 'State',
1408            Data => {
1409                StateMandatory => $Config->{StateMandatory} || 0,
1410                %Param
1411            },
1412        );
1413    }
1414
1415    STATE_ID:
1416    for my $StateID ( sort keys %{ $Param{NextStates} } ) {
1417        next STATE_ID if !$StateID;
1418        my %StateData = $Kernel::OM->Get('Kernel::System::State')->StateGet( ID => $StateID );
1419        if ( $StateData{TypeName} =~ /pending/i ) {
1420
1421            # get used calendar
1422            my $Calendar = $Kernel::OM->Get('Kernel::System::Ticket')->TicketCalendarGet(
1423                QueueID => $Param{QueueID},
1424                SLAID   => $Param{SLAID},
1425            );
1426
1427            $Param{DateString} = $LayoutObject->BuildDateSelection(
1428                Format           => 'DateInputFormatLong',
1429                YearPeriodPast   => 0,
1430                YearPeriodFuture => 5,
1431                DiffTime         => $ConfigObject->Get('Ticket::Frontend::PendingDiffTime')
1432                    || 0,
1433                %Param,
1434                Class                => $Param{DateInvalid} || ' ',
1435                Validate             => 1,
1436                ValidateDateInFuture => 1,
1437                Calendar             => $Calendar,
1438            );
1439            $LayoutObject->Block(
1440                Name => 'StatePending',
1441                Data => \%Param,
1442            );
1443            last STATE_ID;
1444        }
1445    }
1446
1447    # set priority
1448    if ( $Config->{Priority} ) {
1449        $LayoutObject->Block(
1450            Name => 'Priority',
1451            Data => {%Param},
1452        );
1453    }
1454
1455    # set move queues
1456    $Param{MoveQueuesStrg} = $LayoutObject->AgentQueueListOption(
1457        Data           => { %MoveQueues, '' => '-' },
1458        Multiple       => 0,
1459        Size           => 0,
1460        Class          => 'Modernize Validate_Required' . ' ' . $Param{DestQueueIDInvalid},
1461        Name           => 'DestQueueID',
1462        SelectedID     => $Param{DestQueueID},
1463        TreeView       => $TreeView,
1464        CurrentQueueID => $Param{QueueID},
1465        OnChangeSubmit => 0,
1466    );
1467
1468    $LayoutObject->Block(
1469        Name => 'Queue',
1470        Data => {%Param},
1471    );
1472
1473    # define the dynamic fields to show based on the object type
1474    my $ObjectType = ['Ticket'];
1475
1476    # only screens that add notes can modify Article dynamic fields
1477    if ( $Config->{Note} ) {
1478        $ObjectType = [ 'Ticket', 'Article' ];
1479    }
1480
1481    # get the dynamic fields for this screen
1482    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
1483        Valid       => 1,
1484        ObjectType  => $ObjectType,
1485        FieldFilter => $Config->{DynamicField} || {},
1486    );
1487
1488    if ($DynamicField) {
1489        $LayoutObject->Block(
1490            Name => 'WidgetDynamicFields',
1491        );
1492    }
1493
1494    # Dynamic fields
1495    # cycle trough the activated Dynamic Fields for this screen
1496    DYNAMICFIELD:
1497    for my $DynamicFieldConfig ( @{$DynamicField} ) {
1498        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1499
1500        # skip fields that HTML could not be retrieved
1501        next DYNAMICFIELD if !IsHashRefWithData(
1502            $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} }
1503        );
1504
1505        # get the html strings form $Param
1506        my $DynamicFieldHTML = $Param{DynamicFieldHTML}->{ $DynamicFieldConfig->{Name} };
1507
1508        $LayoutObject->Block(
1509            Name => 'DynamicField',
1510            Data => {
1511                Name  => $DynamicFieldConfig->{Name},
1512                Label => $DynamicFieldHTML->{Label},
1513                Field => $DynamicFieldHTML->{Field},
1514            },
1515        );
1516
1517        # example of dynamic fields order customization
1518        $LayoutObject->Block(
1519            Name => 'DynamicField_' . $DynamicFieldConfig->{Name},
1520            Data => {
1521                Name  => $DynamicFieldConfig->{Name},
1522                Label => $DynamicFieldHTML->{Label},
1523                Field => $DynamicFieldHTML->{Field},
1524            },
1525        );
1526
1527    }
1528
1529    if ( $Config->{Note} ) {
1530
1531        $Param{WidgetStatus} = 'Collapsed';
1532
1533        if (
1534            $Config->{NoteMandatory}
1535            || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime')
1536            || $Param{IsUpload}
1537            || $Param{CreateArticle}
1538            )
1539        {
1540            $Param{WidgetStatus} = 'Expanded';
1541        }
1542
1543        if (
1544            $Config->{NoteMandatory}
1545            || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime')
1546            )
1547        {
1548            $Param{SubjectRequired} = 'Validate_Required';
1549            $Param{BodyRequired}    = 'Validate_Required';
1550        }
1551        else {
1552            $Param{SubjectRequired} = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle';
1553            $Param{BodyRequired}    = 'Validate_DependingRequiredAND Validate_Depending_CreateArticle';
1554        }
1555
1556        $LayoutObject->Block(
1557            Name => 'WidgetArticle',
1558            Data => {%Param},
1559        );
1560
1561        # fillup configured default vars
1562        if ( $Param{Body} eq '' && $Config->{Body} ) {
1563            $Param{Body} = $LayoutObject->Output(
1564                Template => $Config->{Body},
1565            );
1566        }
1567
1568        if ( $Param{Subject} eq '' && $Config->{Subject} ) {
1569            $Param{Subject} = $LayoutObject->Output(
1570                Template => $Config->{Subject},
1571            );
1572        }
1573
1574        $LayoutObject->Block(
1575            Name => 'Note',
1576            Data => {%Param},
1577        );
1578
1579        # build text template string
1580        my %StandardTemplates = $Kernel::OM->Get('Kernel::System::StandardTemplate')->StandardTemplateList(
1581            Valid => 1,
1582            Type  => 'Note',
1583        );
1584
1585        my $QueueStandardTemplates = $Self->_GetStandardTemplates(
1586            %Param,
1587            TicketID => $Self->{TicketID} || '',
1588        );
1589
1590        if ( IsHashRefWithData( \%StandardTemplates ) ) {
1591            $Param{StandardTemplateStrg} = $LayoutObject->BuildSelection(
1592                Data         => $QueueStandardTemplates || {},
1593                Name         => 'StandardTemplateID',
1594                SelectedID   => $Param{StandardTemplateID} || '',
1595                PossibleNone => 1,
1596                Sort         => 'AlphanumericValue',
1597                Translation  => 1,
1598                Max          => 200,
1599                Class        => 'Modernize',
1600            );
1601            $LayoutObject->Block(
1602                Name => 'StandardTemplate',
1603                Data => {%Param},
1604            );
1605        }
1606
1607        # show time accounting box
1608        if ( $ConfigObject->Get('Ticket::Frontend::AccountTime') ) {
1609            if ( $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime') ) {
1610                $LayoutObject->Block(
1611                    Name => 'TimeUnitsLabelMandatory',
1612                    Data => \%Param,
1613                );
1614            }
1615            else {
1616                $LayoutObject->Block(
1617                    Name => 'TimeUnitsLabel',
1618                    Data => \%Param,
1619                );
1620            }
1621            $LayoutObject->Block(
1622                Name => 'TimeUnits',
1623                Data => {
1624                    %Param,
1625                    TimeUnitsRequired => (
1626                        $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime')
1627                        ? 'Validate_Required'
1628                        : ''
1629                    ),
1630                }
1631            );
1632        }
1633
1634        # show attachments
1635        ATTACHMENT:
1636        for my $Attachment ( @{ $Param{Attachments} } ) {
1637            if (
1638                $Attachment->{ContentID}
1639                && $LayoutObject->{BrowserRichText}
1640                && ( $Attachment->{ContentType} =~ /image/i )
1641                && ( $Attachment->{Disposition} eq 'inline' )
1642                )
1643            {
1644                next ATTACHMENT;
1645            }
1646
1647            push @{ $Param{AttachmentList} }, $Attachment;
1648        }
1649
1650        # add rich text editor
1651        if ( $LayoutObject->{BrowserRichText} ) {
1652
1653            # use height/width defined for this screen
1654            $Param{RichTextHeight} = $Config->{RichTextHeight} || 0;
1655            $Param{RichTextWidth}  = $Config->{RichTextWidth}  || 0;
1656
1657            # set up rich text editor
1658            $LayoutObject->SetRichTextParameters(
1659                Data => \%Param,
1660            );
1661        }
1662
1663        if (
1664            $Config->{NoteMandatory}
1665            || $ConfigObject->Get('Ticket::Frontend::NeedAccountedTime')
1666            )
1667        {
1668            $LayoutObject->Block(
1669                Name => 'SubjectLabelMandatory',
1670            );
1671            $LayoutObject->Block(
1672                Name => 'RichTextLabelMandatory',
1673            );
1674        }
1675        else {
1676            $LayoutObject->Block(
1677                Name => 'SubjectLabel',
1678            );
1679            $LayoutObject->Block(
1680                Name => 'RichTextLabel',
1681            );
1682        }
1683    }
1684
1685    my $LoadedFormDraft;
1686    if ( $Self->{LoadedFormDraftID} ) {
1687        $LoadedFormDraft = $Kernel::OM->Get('Kernel::System::FormDraft')->FormDraftGet(
1688            FormDraftID => $Self->{LoadedFormDraftID},
1689            GetContent  => 0,
1690            UserID      => $Self->{UserID},
1691        );
1692
1693        my @Articles = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleList(
1694            TicketID => $Self->{TicketID},
1695            OnlyLast => 1,
1696        );
1697
1698        if (@Articles) {
1699            my $LastArticle = $Articles[0];
1700
1701            my $LastArticleSystemTime;
1702            if ( $LastArticle->{CreateTime} ) {
1703                my $LastArticleSystemTimeObject = $Kernel::OM->Create(
1704                    'Kernel::System::DateTime',
1705                    ObjectParams => {
1706                        String => $LastArticle->{CreateTime},
1707                    },
1708                );
1709                $LastArticleSystemTime = $LastArticleSystemTimeObject->ToEpoch();
1710            }
1711
1712            my $FormDraftSystemTimeObject = $Kernel::OM->Create(
1713                'Kernel::System::DateTime',
1714                ObjectParams => {
1715                    String => $LoadedFormDraft->{ChangeTime},
1716                },
1717            );
1718            my $FormDraftSystemTime = $FormDraftSystemTimeObject->ToEpoch();
1719
1720            if ( !$LastArticleSystemTime || $FormDraftSystemTime <= $LastArticleSystemTime ) {
1721                $Param{FormDraftOutdated} = 1;
1722            }
1723        }
1724    }
1725
1726    if ( IsHashRefWithData($LoadedFormDraft) ) {
1727
1728        $LoadedFormDraft->{ChangeByName} = $Kernel::OM->Get('Kernel::System::User')->UserName(
1729            UserID => $LoadedFormDraft->{ChangeBy},
1730        );
1731    }
1732
1733    return $LayoutObject->Output(
1734        TemplateFile => 'AgentTicketMove',
1735        Data         => {
1736            %Param,
1737            FormDraft      => $Config->{FormDraft},
1738            FormDraftID    => $Self->{LoadedFormDraftID},
1739            FormDraftTitle => $LoadedFormDraft ? $LoadedFormDraft->{Title} : '',
1740            FormDraftMeta  => $LoadedFormDraft,
1741        },
1742    );
1743}
1744
1745sub _GetUsers {
1746    my ( $Self, %Param ) = @_;
1747
1748    # get users
1749    my %ShownUsers;
1750    my %AllGroupsMembers = $Kernel::OM->Get('Kernel::System::User')->UserList(
1751        Type  => 'Long',
1752        Valid => 1,
1753    );
1754
1755    # get ticket object
1756    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
1757
1758    # just show only users with selected custom queue
1759    if ( $Param{QueueID} && !$Param{AllUsers} ) {
1760        my @UserIDs = $TicketObject->GetSubscribedUserIDsByQueueID(%Param);
1761        for my $UserGroupMember ( sort keys %AllGroupsMembers ) {
1762            my $Hit = 0;
1763            for my $UID (@UserIDs) {
1764                if ( $UID eq $UserGroupMember ) {
1765                    $Hit = 1;
1766                }
1767            }
1768            if ( !$Hit ) {
1769                delete $AllGroupsMembers{$UserGroupMember};
1770            }
1771        }
1772    }
1773
1774    # check show users
1775    if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::ChangeOwnerToEveryone') ) {
1776        %ShownUsers = %AllGroupsMembers;
1777    }
1778
1779    # show all users who are rw in the queue group
1780    elsif ( $Param{QueueID} ) {
1781        my $GID        = $Kernel::OM->Get('Kernel::System::Queue')->GetQueueGroupID( QueueID => $Param{QueueID} );
1782        my %MemberList = $Kernel::OM->Get('Kernel::System::Group')->PermissionGroupGet(
1783            GroupID => $GID,
1784            Type    => 'owner',
1785        );
1786        for my $MemberUsers ( sort keys %MemberList ) {
1787            if ( $AllGroupsMembers{$MemberUsers} ) {
1788                $ShownUsers{$MemberUsers} = $AllGroupsMembers{$MemberUsers};
1789            }
1790        }
1791    }
1792
1793    # workflow
1794    my $ACL = $TicketObject->TicketAcl(
1795        %Param,
1796        Action        => $Self->{Action},
1797        ReturnType    => 'Ticket',
1798        ReturnSubType => 'NewOwner',
1799        Data          => \%ShownUsers,
1800        UserID        => $Self->{UserID},
1801    );
1802
1803    return { $TicketObject->TicketAclData() } if $ACL;
1804
1805    return \%ShownUsers;
1806}
1807
1808sub _GetOldOwners {
1809    my ( $Self, %Param ) = @_;
1810
1811    # get ticket object
1812    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
1813
1814    my @OldUserInfo = $TicketObject->TicketOwnerList( TicketID => $Self->{TicketID} );
1815    my %UserHash;
1816    if (@OldUserInfo) {
1817        my $Counter = 1;
1818        USER:
1819        for my $User ( reverse @OldUserInfo ) {
1820            next USER if $UserHash{ $User->{UserID} };
1821            $UserHash{ $User->{UserID} } = "$Counter: $User->{UserFullname}";
1822            $Counter++;
1823        }
1824    }
1825
1826    # workflow
1827    my $ACL = $TicketObject->TicketAcl(
1828        %Param,
1829        Action        => $Self->{Action},
1830        ReturnType    => 'Ticket',
1831        ReturnSubType => 'OldOwner',
1832        Data          => \%UserHash,
1833        UserID        => $Self->{UserID},
1834    );
1835
1836    return { $TicketObject->TicketAclData() } if $ACL;
1837
1838    return \%UserHash;
1839}
1840
1841sub _GetPriorities {
1842    my ( $Self, %Param ) = @_;
1843
1844    # get priority
1845    my %Priorities;
1846    if ( $Param{QueueID} || $Param{TicketID} ) {
1847        %Priorities = $Kernel::OM->Get('Kernel::System::Ticket')->TicketPriorityList(
1848            %Param,
1849            Action => $Self->{Action},
1850            UserID => $Self->{UserID},
1851        );
1852    }
1853    return \%Priorities;
1854}
1855
1856sub _GetNextStates {
1857    my ( $Self, %Param ) = @_;
1858
1859    my %NextStates;
1860    if ( $Param{QueueID} || $Param{TicketID} ) {
1861        %NextStates = $Kernel::OM->Get('Kernel::System::Ticket')->TicketStateList(
1862            %Param,
1863            Action => $Self->{Action},
1864            UserID => $Self->{UserID},
1865        );
1866    }
1867    return \%NextStates;
1868}
1869
1870sub _GetFieldsToUpdate {
1871    my ( $Self, %Param ) = @_;
1872
1873    my @UpdatableFields;
1874
1875    # set the fields that can be updatable via AJAXUpdate
1876    if ( !$Param{OnlyDynamicFields} ) {
1877        @UpdatableFields = qw( DestQueueID NewUserID NewStateID NewPriorityID );
1878    }
1879
1880    # define the dynamic fields to show based on the object type
1881    my $ObjectType = ['Ticket'];
1882
1883    # get config for frontend module
1884    my $Config = $Kernel::OM->Get('Kernel::Config')->Get("Ticket::Frontend::$Self->{Action}");
1885
1886    # only screens that add notes can modify Article dynamic fields
1887    if ( $Config->{Note} ) {
1888        $ObjectType = [ 'Ticket', 'Article' ];
1889    }
1890
1891    # get the dynamic fields for this screen
1892    my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
1893        Valid       => 1,
1894        ObjectType  => $ObjectType,
1895        FieldFilter => $Config->{DynamicField} || {},
1896    );
1897
1898    # cycle trough the activated Dynamic Fields for this screen
1899    DYNAMICFIELD:
1900    for my $DynamicFieldConfig ( @{$DynamicField} ) {
1901        next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
1902
1903        my $IsACLReducible = $Kernel::OM->Get('Kernel::System::DynamicField::Backend')->HasBehavior(
1904            DynamicFieldConfig => $DynamicFieldConfig,
1905            Behavior           => 'IsACLReducible',
1906        );
1907        next DYNAMICFIELD if !$IsACLReducible;
1908
1909        push @UpdatableFields, 'DynamicField_' . $DynamicFieldConfig->{Name};
1910    }
1911
1912    return \@UpdatableFields;
1913}
1914
1915sub _GetStandardTemplates {
1916    my ( $Self, %Param ) = @_;
1917
1918    # get create templates
1919    my %Templates;
1920
1921    # check needed
1922    return \%Templates if !$Param{QueueID} && !$Param{TicketID};
1923
1924    my $QueueID = $Param{QueueID} || '';
1925    if ( !$Param{QueueID} && $Param{TicketID} ) {
1926
1927        # get QueueID from the ticket
1928        my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet(
1929            TicketID      => $Param{TicketID},
1930            DynamicFields => 0,
1931            UserID        => $Self->{UserID},
1932        );
1933        $QueueID = $Ticket{QueueID} || '';
1934    }
1935
1936    # fetch all std. templates
1937    my %StandardTemplates = $Kernel::OM->Get('Kernel::System::Queue')->QueueStandardTemplateMemberList(
1938        QueueID       => $QueueID,
1939        TemplateTypes => 1,
1940    );
1941
1942    # return empty hash if there are no templates for this screen
1943    return \%Templates if !IsHashRefWithData( $StandardTemplates{Note} );
1944
1945    # return just the templates for this screen
1946    return $StandardTemplates{Note};
1947}
1948
1949sub _GetQueues {
1950    my ( $Self, %Param ) = @_;
1951
1952    # Get Queues.
1953    my %Queues = $Kernel::OM->Get('Kernel::System::Ticket')->TicketMoveList(
1954        %Param,
1955        TicketID => $Self->{TicketID},
1956        UserID   => $Self->{UserID},
1957        Action   => $Self->{Action},
1958        Type     => 'move_into',
1959    );
1960    return \%Queues;
1961}
1962
19631;
1964