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::Output::HTML::Layout::Ticket;
10
11use strict;
12use warnings;
13
14use Kernel::System::VariableCheck qw(:all);
15use Kernel::Language qw(Translatable);
16
17our $ObjectManagerDisabled = 1;
18
19=head1 NAME
20
21Kernel::Output::HTML::Layout::Ticket - all Ticket-related HTML functions
22
23=head1 DESCRIPTION
24
25All Ticket-related HTML functions
26
27=head1 PUBLIC INTERFACE
28
29=head2 AgentCustomerViewTable()
30
31=cut
32
33sub AgentCustomerViewTable {
34    my ( $Self, %Param ) = @_;
35
36    # check customer params
37    if ( ref $Param{Data} ne 'HASH' ) {
38        $Self->FatalError( Message => 'Need Hash ref in Data param' );
39    }
40    elsif ( ref $Param{Data} eq 'HASH' && !%{ $Param{Data} } ) {
41        return $Self->{LanguageObject}->Translate('none');
42    }
43
44    # add ticket params if given
45    if ( $Param{Ticket} ) {
46        %{ $Param{Data} } = ( %{ $Param{Data} }, %{ $Param{Ticket} } );
47    }
48
49    my @MapNew;
50    my $Map = $Param{Data}->{Config}->{Map};
51    if ($Map) {
52        @MapNew = ( @{$Map} );
53    }
54
55    # check if customer company support is enabled
56    if ( $Param{Data}->{Config}->{CustomerCompanySupport} ) {
57        my $Map2 = $Param{Data}->{CompanyConfig}->{Map};
58        if ($Map2) {
59            push( @MapNew, @{$Map2} );
60        }
61    }
62
63    my $ShownType = 1;
64    if ( $Param{Type} && $Param{Type} eq Translatable('Lite') ) {
65        $ShownType = 2;
66
67        # check if min one lite view item is configured, if not, use
68        # the normal view also
69        my $Used = 0;
70        for my $Field (@MapNew) {
71            if ( $Field->[3] == 2 ) {
72                $Used = 1;
73            }
74        }
75        if ( !$Used ) {
76            $ShownType = 1;
77        }
78    }
79
80    # build html table
81    $Self->Block(
82        Name => 'Customer',
83        Data => $Param{Data},
84    );
85
86    # get needed objects
87    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
88    my $MainObject   = $Kernel::OM->Get('Kernel::System::Main');
89
90    # check Frontend::CustomerUser::Image
91    my $CustomerImage = $ConfigObject->Get('Frontend::CustomerUser::Image');
92    if ($CustomerImage) {
93        my %Modules = %{$CustomerImage};
94
95        MODULE:
96        for my $Module ( sort keys %Modules ) {
97            if ( !$MainObject->Require( $Modules{$Module}->{Module} ) ) {
98                $Self->FatalDie();
99            }
100
101            my $Object = $Modules{$Module}->{Module}->new(
102                %{$Self},
103                LayoutObject => $Self,
104            );
105
106            # run module
107            next MODULE if !$Object;
108
109            $Object->Run(
110                Config => $Modules{$Module},
111                Data   => $Param{Data},
112            );
113        }
114    }
115
116    my $DynamicFieldConfigs = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
117        ObjectType => [ 'CustomerUser', 'CustomerCompany', ],
118    );
119
120    my %DynamicFieldLookup = map { $_->{Name} => $_ } @{$DynamicFieldConfigs};
121
122    # Get dynamic field object.
123    my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
124
125    # build table
126    FIELD:
127    for my $Field (@MapNew) {
128        if ( $Field->[3] && $Field->[3] >= $ShownType && $Param{Data}->{ $Field->[0] } ) {
129            my %Record = (
130                %{ $Param{Data} },
131                Key   => $Field->[1],
132                Value => $Param{Data}->{ $Field->[0] },
133            );
134
135            # render dynamic field values
136            if ( $Field->[5] eq 'dynamic_field' ) {
137                if ( !IsArrayRefWithData( $Record{Value} ) ) {
138                    $Record{Value} = [ $Record{Value} ];
139                }
140
141                my $DynamicFieldConfig = $DynamicFieldLookup{ $Field->[2] };
142
143                next FIELD if !$DynamicFieldConfig;
144
145                my @RenderedValues;
146                VALUE:
147                for my $Value ( @{ $Record{Value} } ) {
148                    my $RenderedValue = $DynamicFieldBackendObject->DisplayValueRender(
149                        DynamicFieldConfig => $DynamicFieldConfig,
150                        Value              => $Value,
151                        HTMLOutput         => 0,
152                        LayoutObject       => $Self,
153                    );
154
155                    next VALUE if !IsHashRefWithData($RenderedValue) || !defined $RenderedValue->{Value};
156
157                    # If there is configured show link in DF, save as map value.
158                    $Field->[6] = $RenderedValue->{Link} ? $RenderedValue->{Link} : $Field->[6];
159
160                    push @RenderedValues, $RenderedValue->{Value};
161                }
162
163                $Record{Value} = join ', ', @RenderedValues;
164                $Record{Key}   = $DynamicFieldConfig->{Label};
165            }
166
167            if (
168                $Field->[6]
169                && (
170                    $Param{Data}->{TicketID}
171                    || $Param{Ticket}
172                    || $Field->[6] !~ m{Env\("CGIHandle"\)}
173                )
174                )
175            {
176                $Record{LinkStart} = "<a href=\"$Field->[6]\"";
177                if ( !$Param{Ticket} ) {
178                    $Record{LinkStart} .= " target=\"_blank\"";
179                }
180                elsif ( $Field->[8] ) {
181                    $Record{LinkStart} .= " target=\"$Field->[8]\"";
182                }
183                if ( $Field->[9] ) {
184                    $Record{LinkStart} .= " class=\"$Field->[9]\"";
185                }
186                $Record{LinkStart} .= ">";
187                $Record{LinkStop} = "</a>";
188            }
189            if ( $Field->[0] ) {
190                $Record{ValueShort} = $Self->Ascii2Html(
191                    Text => $Record{Value},
192                    Max  => $Param{Max}
193                );
194            }
195            $Self->Block(
196                Name => 'CustomerRow',
197                Data => \%Record,
198            );
199
200            if (
201                $Param{Data}->{Config}->{CustomerCompanySupport}
202                && $Field->[0] eq 'CustomerCompanyName'
203                )
204            {
205                my $CompanyValidID = $Param{Data}->{CustomerCompanyValidID};
206
207                if ($CompanyValidID) {
208                    my @ValidIDs       = $Kernel::OM->Get('Kernel::System::Valid')->ValidIDsGet();
209                    my $CompanyIsValid = grep { $CompanyValidID == $_ } @ValidIDs;
210
211                    if ( !$CompanyIsValid ) {
212                        $Self->Block(
213                            Name => 'CustomerRowCustomerCompanyInvalid',
214                        );
215                    }
216                }
217            }
218
219            if (
220                $ConfigObject->Get('ChatEngine::Active')
221                && $Field->[0] eq 'UserLogin'
222                )
223            {
224                # Check if agent has permission to start chats with the customer users.
225                my $EnableChat = 1;
226                my $ChatStartingAgentsGroup
227                    = $ConfigObject->Get('ChatEngine::PermissionGroup::ChatStartingAgents') || 'users';
228                my $ChatStartingAgentsGroupPermission = $Kernel::OM->Get('Kernel::System::Group')->PermissionCheck(
229                    UserID    => $Self->{UserID},
230                    GroupName => $ChatStartingAgentsGroup,
231                    Type      => 'rw',
232                );
233
234                if ( !$ChatStartingAgentsGroupPermission ) {
235                    $EnableChat = 0;
236                }
237                if (
238                    $EnableChat
239                    && !$ConfigObject->Get('ChatEngine::ChatDirection::AgentToCustomer')
240                    )
241                {
242                    $EnableChat = 0;
243                }
244
245                if ($EnableChat) {
246                    my $VideoChatEnabled = 0;
247                    my $VideoChatAgentsGroup
248                        = $ConfigObject->Get('ChatEngine::PermissionGroup::VideoChatAgents') || 'users';
249                    my $VideoChatAgentsGroupPermission = $Kernel::OM->Get('Kernel::System::Group')->PermissionCheck(
250                        UserID    => $Self->{UserID},
251                        GroupName => $VideoChatAgentsGroup,
252                        Type      => 'rw',
253                    );
254
255                    # Enable the video chat feature if system is entitled and agent is a member of configured group.
256                    if ($VideoChatAgentsGroupPermission) {
257                        if ( $Kernel::OM->Get('Kernel::System::Main')
258                            ->Require( 'Kernel::System::VideoChat', Silent => 1 ) )
259                        {
260                            $VideoChatEnabled = $Kernel::OM->Get('Kernel::System::VideoChat')->IsEnabled();
261                        }
262                    }
263
264                    my $CustomerEnableChat = 0;
265                    my $ChatAccess         = 0;
266                    my $VideoChatAvailable = 0;
267                    my $VideoChatSupport   = 0;
268
269                    # Default status is offline.
270                    my $UserState            = Translatable('Offline');
271                    my $UserStateDescription = $Self->{LanguageObject}->Translate('User is currently offline.');
272
273                    my $CustomerChatAvailability = $Kernel::OM->Get('Kernel::System::Chat')->CustomerAvailabilityGet(
274                        UserID => $Param{Data}->{UserID},
275                    );
276
277                    my $CustomerUserObject = $Kernel::OM->Get('Kernel::System::CustomerUser');
278
279                    my %CustomerUser = $CustomerUserObject->CustomerUserDataGet(
280                        User => $Param{Data}->{UserID},
281                    );
282                    $CustomerUser{UserFullname} = $CustomerUserObject->CustomerName(
283                        UserLogin => $Param{Data}->{UserID},
284                    );
285                    $VideoChatSupport = 1 if $CustomerUser{VideoChatHasWebRTC};
286
287                    if ( $CustomerChatAvailability == 3 ) {
288                        $UserState            = Translatable('Active');
289                        $CustomerEnableChat   = 1;
290                        $UserStateDescription = $Self->{LanguageObject}->Translate('User is currently active.');
291                        $VideoChatAvailable   = 1;
292                    }
293                    elsif ( $CustomerChatAvailability == 2 ) {
294                        $UserState            = Translatable('Away');
295                        $CustomerEnableChat   = 1;
296                        $UserStateDescription = $Self->{LanguageObject}->Translate('User was inactive for a while.');
297                    }
298
299                    $Self->Block(
300                        Name => 'CustomerRowUserStatus',
301                        Data => {
302                            %CustomerUser,
303                            UserState            => $UserState,
304                            UserStateDescription => $UserStateDescription,
305                        },
306                    );
307
308                    if ($CustomerEnableChat) {
309                        $Self->Block(
310                            Name => 'CustomerRowChatIcons',
311                            Data => {
312                                %{ $Param{Data} },
313                                %CustomerUser,
314                                VideoChatEnabled   => $VideoChatEnabled,
315                                VideoChatAvailable => $VideoChatAvailable,
316                                VideoChatSupport   => $VideoChatSupport,
317                            },
318                        );
319                    }
320                }
321            }
322        }
323    }
324
325    # check Frontend::CustomerUser::Item
326    my $CustomerItem      = $ConfigObject->Get('Frontend::CustomerUser::Item');
327    my $CustomerItemCount = 0;
328    if ($CustomerItem) {
329        $Self->Block(
330            Name => 'CustomerItem',
331        );
332        my %Modules = %{$CustomerItem};
333
334        MODULE:
335        for my $Module ( sort keys %Modules ) {
336            if ( !$MainObject->Require( $Modules{$Module}->{Module} ) ) {
337                $Self->FatalDie();
338            }
339
340            my $Object = $Modules{$Module}->{Module}->new(
341                %{$Self},
342                LayoutObject => $Self,
343            );
344
345            # run module
346            next MODULE if !$Object;
347
348            my $Run = $Object->Run(
349                Config => $Modules{$Module},
350                Data   => $Param{Data},
351            );
352
353            next MODULE if !$Run;
354
355            $CustomerItemCount++;
356        }
357    }
358
359    # create & return output
360    return $Self->Output(
361        TemplateFile => 'AgentCustomerTableView',
362        Data         => \%Param
363    );
364}
365
366sub AgentQueueListOption {
367    my ( $Self, %Param ) = @_;
368
369    my $Size           = $Param{Size}                      ? "size='$Param{Size}'"  : '';
370    my $MaxLevel       = defined( $Param{MaxLevel} )       ? $Param{MaxLevel}       : 10;
371    my $SelectedID     = defined( $Param{SelectedID} )     ? $Param{SelectedID}     : '';
372    my $Selected       = defined( $Param{Selected} )       ? $Param{Selected}       : '';
373    my $CurrentQueueID = defined( $Param{CurrentQueueID} ) ? $Param{CurrentQueueID} : '';
374    my $Class          = defined( $Param{Class} )          ? $Param{Class}          : '';
375    my $SelectedIDRefArray = $Param{SelectedIDRefArray} || '';
376    my $Multiple           = $Param{Multiple} ? 'multiple = "multiple"' : '';
377    my $TreeView           = $Param{TreeView} ? $Param{TreeView} : 0;
378    my $OptionTitle        = defined( $Param{OptionTitle} ) ? $Param{OptionTitle} : 0;
379    my $OnChangeSubmit     = defined( $Param{OnChangeSubmit} ) ? $Param{OnChangeSubmit} : '';
380
381    if ($OnChangeSubmit) {
382        $OnChangeSubmit = " onchange=\"submit();\"";
383    }
384    else {
385        $OnChangeSubmit = '';
386    }
387
388    # set OnChange if AJAX is used
389    if ( $Param{Ajax} ) {
390
391        # get log object
392        my $LogObject = $Kernel::OM->Get('Kernel::System::Log');
393
394        if ( !$Param{Ajax}->{Depend} ) {
395            $LogObject->Log(
396                Priority => 'error',
397                Message  => 'Need Depend Param Ajax option!',
398            );
399            $Self->FatalError();
400        }
401        if ( !$Param{Ajax}->{Update} ) {
402            $LogObject->Log(
403                Priority => 'error',
404                Message  => 'Need Update Param Ajax option()!',
405            );
406            $Self->FatalError();
407        }
408        $Param{OnChange} = "Core.AJAX.FormUpdate(\$('#"
409            . $Param{Name} . "'), '"
410            . $Param{Ajax}->{Subaction} . "',"
411            . " '$Param{Name}',"
412            . " ['"
413            . join( "', '", @{ $Param{Ajax}->{Update} } ) . "']);";
414    }
415
416    if ( $Param{OnChange} ) {
417        $OnChangeSubmit = " onchange=\"$Param{OnChange}\"";
418    }
419
420    # just show a simple list
421    if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Frontend::ListType') eq 'list' ) {
422
423        # transform data from Hash in Array because of ordering in frontend by Queue name
424        # it was a problem with name like '(some_queue)'
425        # see bug#10621 http://bugs.otrs.org/show_bug.cgi?id=10621
426        my %QueueDataHash  = %{ $Param{Data} || {} };
427        my @QueueDataArray = map {
428            {
429                Key   => $_,
430                Value => $QueueDataHash{$_},
431            }
432        } sort { $QueueDataHash{$a} cmp $QueueDataHash{$b} } keys %QueueDataHash;
433
434        # find index of first element in array @QueueDataArray for displaying in frontend
435        # at the top should be element with ' $QueueDataArray[$_]->{Key} = 0' like "- Move -"
436        # if such an element is found, it is moved to the top
437        my $MoveStr             = $Self->{LanguageObject}->Translate('Move');
438        my $ValueOfQueueNoKey   = '- ' . $MoveStr . ' -';
439        my ($FirstElementIndex) = grep {
440            $QueueDataArray[$_]->{Value} eq '-'
441                || $QueueDataArray[$_]->{Value} eq $ValueOfQueueNoKey
442        } 0 .. scalar(@QueueDataArray) - 1;
443        if ($FirstElementIndex) {
444            splice( @QueueDataArray, 0, 0, splice( @QueueDataArray, $FirstElementIndex, 1 ) );
445        }
446        $Param{Data} = \@QueueDataArray;
447
448        $Param{MoveQueuesStrg} = $Self->BuildSelection(
449            %Param,
450            HTMLQuote     => 0,
451            SelectedID    => $Param{SelectedID} || $Param{SelectedIDRefArray} || '',
452            SelectedValue => $Param{Selected},
453            Translation   => 0,
454        );
455        return $Param{MoveQueuesStrg};
456    }
457
458    # build tree list
459    $Param{MoveQueuesStrg} = '<select name="'
460        . $Param{Name}
461        . '" id="'
462        . $Param{Name}
463        . '" class="'
464        . $Class
465        . '" data-tree="true"'
466        . " $Size $Multiple $OnChangeSubmit>\n";
467    my %UsedData;
468    my %Data;
469
470    if ( $Param{Data} && ref $Param{Data} eq 'HASH' ) {
471        %Data = %{ $Param{Data} };
472    }
473    else {
474        return 'Need Data Ref in AgentQueueListOption()!';
475    }
476
477    # add suffix for correct sorting
478    my $KeyNoQueue;
479    my $ValueNoQueue;
480    my $MoveStr           = $Self->{LanguageObject}->Translate('Move');
481    my $ValueOfQueueNoKey = "- " . $MoveStr . " -";
482    DATA:
483    for ( sort { $Data{$a} cmp $Data{$b} } keys %Data ) {
484
485        # find value for default item in select box
486        # it can be "-" or "Move"
487        if (
488            $Data{$_} eq "-"
489            || $Data{$_} eq $ValueOfQueueNoKey
490            )
491        {
492            $KeyNoQueue   = $_;
493            $ValueNoQueue = $Data{$_};
494            next DATA;
495        }
496        $Data{$_} .= '::';
497    }
498
499    # get HTML utils object
500    my $HTMLUtilsObject = $Kernel::OM->Get('Kernel::System::HTMLUtils');
501
502    # set default item of select box
503    if ($ValueNoQueue) {
504        $Param{MoveQueuesStrg} .= '<option value="'
505            . $HTMLUtilsObject->ToHTML(
506            String             => $KeyNoQueue,
507            ReplaceDoubleSpace => 0,
508            )
509            . '">'
510            . $ValueNoQueue
511            . "</option>\n";
512    }
513
514    # build selection string
515    KEY:
516    for ( sort { $Data{$a} cmp $Data{$b} } keys %Data ) {
517
518        # default item of select box has set already
519        next KEY if ( $Data{$_} eq "-" || $Data{$_} eq $ValueOfQueueNoKey );
520
521        my @Queue = split( /::/, $Param{Data}->{$_} );
522        $UsedData{ $Param{Data}->{$_} } = 1;
523        my $UpQueue = $Param{Data}->{$_};
524        $UpQueue =~ s/^(.*)::.+?$/$1/g;
525        if ( !$Queue[$MaxLevel] && $Queue[-1] ne '' ) {
526            $Queue[-1] = $Self->Ascii2Html(
527                Text => $Queue[-1],
528                Max  => 50 - $#Queue
529            );
530            my $Space = '';
531            for ( my $i = 0; $i < $#Queue; $i++ ) {
532                $Space .= '&nbsp;&nbsp;';
533            }
534
535            # check if SelectedIDRefArray exists
536            if ($SelectedIDRefArray) {
537                for my $ID ( @{$SelectedIDRefArray} ) {
538                    if ( $ID eq $_ ) {
539                        $Param{SelectedIDRefArrayOK}->{$_} = 1;
540                    }
541                }
542            }
543
544            if ( !$UsedData{$UpQueue} ) {
545
546                # integrate the not selectable parent and root queues of this queue
547                # useful for ACLs and complex permission settings
548                for my $Index ( 0 .. ( scalar @Queue - 2 ) ) {
549
550                    # get the Full Queue Name (with all its parents separated by '::') this will
551                    # make a unique name and will be used to set the %DisabledQueueAlreadyUsed
552                    # using unique names will prevent erroneous hide of Sub-Queues with the
553                    # same name, refer to bug#8148
554                    my $FullQueueName;
555                    for my $Counter ( 0 .. $Index ) {
556                        $FullQueueName .= $Queue[$Counter];
557                        if ( int $Counter < int $Index ) {
558                            $FullQueueName .= '::';
559                        }
560                    }
561
562                    if ( !$UsedData{$FullQueueName} ) {
563                        my $DSpace               = '&nbsp;&nbsp;' x $Index;
564                        my $OptionTitleHTMLValue = '';
565                        if ($OptionTitle) {
566                            my $HTMLValue = $HTMLUtilsObject->ToHTML(
567                                String             => $Queue[$Index],
568                                ReplaceDoubleSpace => 0,
569                            );
570                            $OptionTitleHTMLValue = ' title="' . $HTMLValue . '"';
571                        }
572                        $Param{MoveQueuesStrg}
573                            .= '<option value="-" disabled="disabled"'
574                            . $OptionTitleHTMLValue
575                            . '>'
576                            . $DSpace
577                            . $Queue[$Index]
578                            . "</option>\n";
579                        $UsedData{$FullQueueName} = 1;
580                    }
581                }
582            }
583
584            # create selectable elements
585            my $String               = $Space . $Queue[-1];
586            my $OptionTitleHTMLValue = '';
587            if ($OptionTitle) {
588                my $HTMLValue = $HTMLUtilsObject->ToHTML(
589                    String             => $Queue[-1],
590                    ReplaceDoubleSpace => 0,
591                );
592                $OptionTitleHTMLValue = ' title="' . $HTMLValue . '"';
593            }
594            my $HTMLValue = $HTMLUtilsObject->ToHTML(
595                String             => $_,
596                ReplaceDoubleSpace => 0,
597            );
598            if (
599                $SelectedID eq $_
600                || $Selected eq $Param{Data}->{$_}
601                || $Param{SelectedIDRefArrayOK}->{$_}
602                )
603            {
604                $Param{MoveQueuesStrg}
605                    .= '<option selected="selected" value="'
606                    . $HTMLValue . '"'
607                    . $OptionTitleHTMLValue . '>'
608                    . $String
609                    . "</option>\n";
610            }
611            elsif ( $CurrentQueueID eq $_ )
612            {
613                $Param{MoveQueuesStrg}
614                    .= '<option value="-" disabled="disabled"'
615                    . $OptionTitleHTMLValue . '>'
616                    . $String
617                    . "</option>\n";
618            }
619            else {
620                $Param{MoveQueuesStrg}
621                    .= '<option value="'
622                    . $HTMLValue . '"'
623                    . $OptionTitleHTMLValue . '>'
624                    . $String
625                    . "</option>\n";
626            }
627        }
628    }
629    $Param{MoveQueuesStrg} .= "</select>\n";
630
631    if ( $Param{TreeView} ) {
632        my $TreeSelectionMessage = $Self->{LanguageObject}->Translate("Show Tree Selection");
633        $Param{MoveQueuesStrg}
634            .= ' <a href="#" title="'
635            . $TreeSelectionMessage
636            . '" class="ShowTreeSelection"><span>'
637            . $TreeSelectionMessage . '</span><i class="fa fa-sitemap"></i></a>';
638    }
639
640    return $Param{MoveQueuesStrg};
641}
642
643sub TicketListShow {
644    my ( $Self, %Param ) = @_;
645
646    # take object ref to local, remove it from %Param (prevent memory leak)
647    my $Env = $Param{Env};
648    delete $Param{Env};
649
650    # lookup latest used view mode
651    if ( !$Param{View} && $Self->{ 'UserTicketOverview' . $Env->{Action} } ) {
652        $Param{View} = $Self->{ 'UserTicketOverview' . $Env->{Action} };
653    }
654
655    # set default view mode to 'small'
656    my $View = $Param{View} || 'Small';
657
658    # set default view mode for AgentTicketQueue or AgentTicketService
659    if (
660        !$Param{View}
661        && (
662            $Env->{Action} eq 'AgentTicketQueue'
663            || $Env->{Action} eq 'AgentTicketService'
664        )
665        )
666    {
667        $View = 'Preview';
668    }
669
670    # store latest view mode
671    $Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID(
672        SessionID => $Self->{SessionID},
673        Key       => 'UserTicketOverview' . $Env->{Action},
674        Value     => $View,
675    );
676
677    # get config object
678    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
679
680    # update preferences if needed
681    my $Key = 'UserTicketOverview' . $Env->{Action};
682    if ( !$ConfigObject->Get('DemoSystem') && ( $Self->{$Key} // '' ) ne $View ) {
683        $Kernel::OM->Get('Kernel::System::User')->SetPreferences(
684            UserID => $Self->{UserID},
685            Key    => $Key,
686            Value  => $View,
687        );
688    }
689
690    # check backends
691    my $Backends = $ConfigObject->Get('Ticket::Frontend::Overview');
692    if ( !$Backends ) {
693        return $Self->FatalError(
694            Message => 'Need config option Ticket::Frontend::Overview',
695        );
696    }
697    if ( ref $Backends ne 'HASH' ) {
698        return $Self->FatalError(
699            Message => 'Config option Ticket::Frontend::Overview need to be HASH ref!',
700        );
701    }
702
703    # check if selected view is available
704    if ( !$Backends->{$View} ) {
705
706        # try to find fallback, take first configured view mode
707        KEY:
708        for my $Key ( sort keys %{$Backends} ) {
709            $Kernel::OM->Get('Kernel::System::Log')->Log(
710                Priority => 'error',
711                Message  => "No Config option found for view mode $View, took $Key instead!",
712            );
713            $View = $Key;
714            last KEY;
715        }
716    }
717
718    # get layout object
719    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
720
721    $LayoutObject->AddJSData(
722        Key   => 'View',
723        Value => $View,
724    );
725
726    # load overview backend module
727    if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Backends->{$View}->{Module} ) ) {
728        return $Env->{LayoutObject}->FatalError();
729    }
730    my $Object = $Backends->{$View}->{Module}->new( %{$Env} );
731    return if !$Object;
732
733    # retireve filter values
734    if ( $Param{FilterContentOnly} ) {
735        return $Object->FilterContent(
736            %Param,
737        );
738    }
739
740    # run action row backend module
741    $Param{ActionRow} = $Object->ActionRow(
742        %Param,
743        Config => $Backends->{$View},
744    );
745
746    # run overview backend module
747    $Param{SortOrderBar} = $Object->SortOrderBar(
748        %Param,
749        Config => $Backends->{$View},
750    );
751
752    # check start option, if higher then tickets available, set
753    # it to the last ticket page (Thanks to Stefan Schmidt!)
754    my $StartHit = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'StartHit' ) || 1;
755
756    # get personal page shown count
757    my $PageShownPreferencesKey = 'UserTicketOverview' . $View . 'PageShown';
758    my $PageShown               = $Self->{$PageShownPreferencesKey} || 10;
759    my $Group                   = 'TicketOverview' . $View . 'PageShown';
760
761    # get data selection
762    my %Data;
763    my $Config = $ConfigObject->Get('PreferencesGroups');
764    if ( $Config && $Config->{$Group} && $Config->{$Group}->{Data} ) {
765        %Data = %{ $Config->{$Group}->{Data} };
766    }
767
768    # calculate max. shown per page
769    if ( $StartHit > $Param{Total} ) {
770        my $Pages = int( ( $Param{Total} / $PageShown ) + 0.99999 );
771        $StartHit = ( ( $Pages - 1 ) * $PageShown ) + 1;
772    }
773
774    # build nav bar
775    my $Limit   = $Param{Limit} || 20_000;
776    my %PageNav = $Self->PageNavBar(
777        Limit     => $Limit,
778        StartHit  => $StartHit,
779        PageShown => $PageShown,
780        AllHits   => $Param{Total} || 0,
781        Action    => 'Action=' . $Self->{Action},
782        Link      => $Param{LinkPage},
783        IDPrefix  => $Self->{Action},
784    );
785
786    # build shown ticket per page
787    $Param{RequestedURL}    = $Param{RequestedURL} || "Action=$Self->{Action}";
788    $Param{Group}           = $Group;
789    $Param{PreferencesKey}  = $PageShownPreferencesKey;
790    $Param{PageShownString} = $Self->BuildSelection(
791        Name        => $PageShownPreferencesKey,
792        SelectedID  => $PageShown,
793        Translation => 0,
794        Data        => \%Data,
795        Sort        => 'NumericValue',
796        Class       => 'Modernize',
797    );
798
799    # nav bar at the beginning of a overview
800    $Param{View} = $View;
801    $Self->Block(
802        Name => 'OverviewNavBar',
803        Data => \%Param,
804    );
805
806    # back link
807    if ( $Param{LinkBack} ) {
808        $Self->Block(
809            Name => 'OverviewNavBarPageBack',
810            Data => \%Param,
811        );
812        $LayoutObject->AddJSData(
813            Key   => 'Profile',
814            Value => $Param{Profile},
815        );
816    }
817
818    # filter selection
819    if ( $Param{Filters} ) {
820        my @NavBarFilters;
821        for my $Prio ( sort keys %{ $Param{Filters} } ) {
822            push @NavBarFilters, $Param{Filters}->{$Prio};
823        }
824        $Self->Block(
825            Name => 'OverviewNavBarFilter',
826            Data => {
827                %Param,
828            },
829        );
830        my $Count = 0;
831        for my $Filter (@NavBarFilters) {
832            $Count++;
833            if ( $Count == scalar @NavBarFilters ) {
834                $Filter->{CSS} = 'Last';
835            }
836            $Self->Block(
837                Name => 'OverviewNavBarFilterItem',
838                Data => {
839                    %Param,
840                    %{$Filter},
841                },
842            );
843            if ( $Filter->{Filter} eq $Param{Filter} ) {
844                $Self->Block(
845                    Name => 'OverviewNavBarFilterItemSelected',
846                    Data => {
847                        %Param,
848                        %{$Filter},
849                    },
850                );
851            }
852            else {
853                $Self->Block(
854                    Name => 'OverviewNavBarFilterItemSelectedNot',
855                    Data => {
856                        %Param,
857                        %{$Filter},
858                    },
859                );
860            }
861        }
862    }
863
864    # view mode
865    for my $Backend (
866        sort { $Backends->{$a}->{ModulePriority} <=> $Backends->{$b}->{ModulePriority} }
867        keys %{$Backends}
868        )
869    {
870
871        $Self->Block(
872            Name => 'OverviewNavBarViewMode',
873            Data => {
874                %Param,
875                %{ $Backends->{$Backend} },
876                Filter => $Param{Filter},
877                View   => $Backend,
878            },
879        );
880        if ( $View eq $Backend ) {
881            $Self->Block(
882                Name => 'OverviewNavBarViewModeSelected',
883                Data => {
884                    %Param,
885                    %{ $Backends->{$Backend} },
886                    Filter => $Param{Filter},
887                    View   => $Backend,
888                },
889            );
890        }
891        else {
892            $Self->Block(
893                Name => 'OverviewNavBarViewModeNotSelected',
894                Data => {
895                    %Param,
896                    %{ $Backends->{$Backend} },
897                    Filter => $Param{Filter},
898                    View   => $Backend,
899                },
900            );
901        }
902    }
903
904    if (%PageNav) {
905        $Self->Block(
906            Name => 'OverviewNavBarPageNavBar',
907            Data => \%PageNav,
908        );
909
910        # don't show context settings in AJAX case (e. g. in customer ticket history),
911        #   because the submit with page reload will not work there
912        if ( !$Param{AJAX} ) {
913            $Self->Block(
914                Name => 'ContextSettings',
915                Data => {
916                    %PageNav,
917                    %Param,
918                },
919            );
920
921            # show column filter preferences
922            if ( $View eq 'Small' ) {
923
924                # set preferences keys
925                my $PrefKeyColumns = 'UserFilterColumnsEnabled' . '-' . $Env->{Action};
926
927                # create extra needed objects
928                my $JSONObject = $Kernel::OM->Get('Kernel::System::JSON');
929
930                # configure columns
931                my @ColumnsEnabled = @{ $Object->{ColumnsEnabled} };
932                my @ColumnsAvailable;
933
934                # remove duplicate columns
935                my %UniqueColumns;
936                my @ColumnsEnabledAux;
937
938                for my $Column (@ColumnsEnabled) {
939                    if ( !$UniqueColumns{$Column} ) {
940                        push @ColumnsEnabledAux, $Column;
941                    }
942                    $UniqueColumns{$Column} = 1;
943                }
944
945                # set filtered column list
946                @ColumnsEnabled = @ColumnsEnabledAux;
947
948                for my $ColumnName ( sort { $a cmp $b } @{ $Object->{ColumnsAvailable} } ) {
949                    if ( !grep { $_ eq $ColumnName } @ColumnsEnabled ) {
950                        push @ColumnsAvailable, $ColumnName;
951                    }
952                }
953
954                my %Columns;
955                for my $ColumnName ( sort @ColumnsAvailable ) {
956                    $Columns{Columns}->{$ColumnName} = ( grep { $ColumnName eq $_ } @ColumnsEnabled ) ? 1 : 0;
957                }
958
959                $Self->Block(
960                    Name => 'FilterColumnSettings',
961                    Data => {
962                        Columns          => $JSONObject->Encode( Data => \%Columns ),
963                        ColumnsEnabled   => $JSONObject->Encode( Data => \@ColumnsEnabled ),
964                        ColumnsAvailable => $JSONObject->Encode( Data => \@ColumnsAvailable ),
965                        NamePref         => $PrefKeyColumns,
966                        Desc             => Translatable('Shown Columns'),
967                        Name             => $Env->{Action},
968                        View             => $View,
969                        GroupName        => 'TicketOverviewFilterSettings',
970                        %Param,
971                    },
972                );
973            }
974        }    # end show column filters preferences
975
976        # check if there was stored filters, and print a link to delete them
977        if ( IsHashRefWithData( $Object->{StoredFilters} ) ) {
978            $Self->Block(
979                Name => 'DocumentActionRowRemoveColumnFilters',
980                Data => {
981                    CSS => "ContextSettings RemoveFilters",
982                    %Param,
983                },
984            );
985        }
986    }
987
988    if ( $Param{NavBar} ) {
989        if ( $Param{NavBar}->{MainName} ) {
990            $Self->Block(
991                Name => 'OverviewNavBarMain',
992                Data => $Param{NavBar},
993            );
994        }
995    }
996
997    my $OutputNavBar = $Self->Output(
998        TemplateFile => 'AgentTicketOverviewNavBar',
999        Data         => { %Param, },
1000    );
1001    my $OutputRaw = '';
1002    if ( !$Param{Output} ) {
1003        $Self->Print( Output => \$OutputNavBar );
1004    }
1005    else {
1006        $OutputRaw .= $OutputNavBar;
1007    }
1008
1009    # run overview backend module
1010    my $Output = $Object->Run(
1011        %Param,
1012        Config    => $Backends->{$View},
1013        Limit     => $Limit,
1014        StartHit  => $StartHit,
1015        PageShown => $PageShown,
1016        AllHits   => $Param{Total} || 0,
1017        Output    => $Param{Output} || '',
1018    );
1019    if ( !$Param{Output} ) {
1020        $Self->Print( Output => \$Output );
1021    }
1022    else {
1023        $OutputRaw .= $Output;
1024    }
1025
1026    return $OutputRaw;
1027}
1028
1029sub TicketMetaItemsCount {
1030    my ( $Self, %Param ) = @_;
1031    return ( 'Priority', 'New Article' );
1032}
1033
1034sub TicketMetaItems {
1035    my ( $Self, %Param ) = @_;
1036
1037    if ( ref $Param{Ticket} ne 'HASH' ) {
1038        $Self->FatalError( Message => 'Need Hash ref in Ticket param!' );
1039    }
1040
1041    # return attributes
1042    my @Result;
1043
1044    # show priority
1045    push @Result, {
1046
1047        #            Image => $Image,
1048        Title      => $Param{Ticket}->{Priority},
1049        Class      => 'Flag',
1050        ClassSpan  => 'PriorityID-' . $Param{Ticket}->{PriorityID},
1051        ClassTable => 'Flags',
1052    };
1053
1054    # get ticket object
1055    my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
1056
1057    my %Ticket = $TicketObject->TicketGet( TicketID => $Param{Ticket}->{TicketID} );
1058
1059    # Show if new message is in there, but show archived tickets as read.
1060    my %TicketFlag;
1061    if ( $Ticket{ArchiveFlag} ne 'y' ) {
1062        %TicketFlag = $TicketObject->TicketFlagGet(
1063            TicketID => $Param{Ticket}->{TicketID},
1064            UserID   => $Self->{UserID},
1065        );
1066    }
1067
1068    if ( $Ticket{ArchiveFlag} eq 'y' || $TicketFlag{Seen} ) {
1069        push @Result, undef;
1070    }
1071    else {
1072
1073        # just show ticket flags if agent belongs to the ticket
1074        my $ShowMeta;
1075        if (
1076            $Self->{UserID} == $Param{Ticket}->{OwnerID}
1077            || $Self->{UserID} == $Param{Ticket}->{ResponsibleID}
1078            )
1079        {
1080            $ShowMeta = 1;
1081        }
1082        if ( !$ShowMeta && $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Watcher') ) {
1083            my %Watch = $TicketObject->TicketWatchGet(
1084                TicketID => $Param{Ticket}->{TicketID},
1085            );
1086            if ( $Watch{ $Self->{UserID} } ) {
1087                $ShowMeta = 1;
1088            }
1089        }
1090
1091        # show ticket flags
1092        my $Image = 'meta-new-inactive.png';
1093        if ($ShowMeta) {
1094            $Image = 'meta-new.png';
1095            push @Result, {
1096                Image      => $Image,
1097                Title      => Translatable('Unread article(s) available'),
1098                Class      => 'UnreadArticles',
1099                ClassSpan  => 'UnreadArticles Remarkable',
1100                ClassTable => 'UnreadArticles',
1101            };
1102        }
1103        else {
1104            push @Result, {
1105                Image      => $Image,
1106                Title      => Translatable('Unread article(s) available'),
1107                Class      => 'UnreadArticles',
1108                ClassSpan  => 'UnreadArticles Ordinary',
1109                ClassTable => 'UnreadArticles',
1110            };
1111        }
1112    }
1113
1114    return @Result;
1115}
1116
11171;
1118
1119=head1 TERMS AND CONDITIONS
1120
1121This software is part of the OTRS project (L<https://otrs.org/>).
1122
1123This software comes with ABSOLUTELY NO WARRANTY. For details, see
1124the enclosed file COPYING for license information (GPL). If you
1125did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
1126
1127=cut
1128