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;
10
11use strict;
12use warnings;
13
14use URI::Escape qw();
15use Digest::MD5 qw(md5_hex);
16
17use Kernel::System::VariableCheck qw(:all);
18use Kernel::Language qw(Translatable);
19
20our @ObjectDependencies = (
21    'Kernel::Config',
22    'Kernel::Language',
23    'Kernel::System::AuthSession',
24    'Kernel::System::Cache',
25    'Kernel::System::Chat',
26    'Kernel::System::CustomerGroup',
27    'Kernel::System::DateTime',
28    'Kernel::System::Group',
29    'Kernel::System::Encode',
30    'Kernel::System::HTMLUtils',
31    'Kernel::System::JSON',
32    'Kernel::System::Log',
33    'Kernel::System::Main',
34    'Kernel::System::OTRSBusiness',
35    'Kernel::System::State',
36    'Kernel::System::Storable',
37    'Kernel::System::SystemMaintenance',
38    'Kernel::System::User',
39    'Kernel::System::VideoChat',
40    'Kernel::System::Web::Request',
41);
42
43=head1 NAME
44
45Kernel::Output::HTML::Layout - all generic html functions
46
47=head1 DESCRIPTION
48
49All generic html functions. E. g. to get options fields, template processing, ...
50
51=head1 PUBLIC INTERFACE
52
53=head2 new()
54
55create a new object. Do not use it directly, instead use:
56
57    use Kernel::System::ObjectManager;
58    local $Kernel::OM = Kernel::System::ObjectManager->new(
59        'Kernel::Output::HTML::Layout' => {
60            Lang    => 'de',
61        },
62    );
63    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
64
65From the web installer, a special Option C<InstallerOnly> is passed
66to indicate that a database connection is not yet available.
67
68    use Kernel::System::ObjectManager;
69    local $Kernel::OM = Kernel::System::ObjectManager->new(
70        'Kernel::Output::HTML::Layout' => {
71            InstallerOnly => 1,
72        },
73    );
74    my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
75
76=cut
77
78sub new {
79    my ( $Type, %Param ) = @_;
80
81    # allocate new hash for object
82    my $Self = {%Param};
83    bless( $Self, $Type );
84
85    # set debug
86    $Self->{Debug} = 0;
87
88    # reset block data
89    delete $Self->{BlockData};
90
91    # empty action if not defined
92    $Self->{Action} = '' if !defined $Self->{Action};
93
94    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
95
96    # get/set some common params
97    if ( !$Self->{UserTheme} ) {
98        $Self->{UserTheme} = $ConfigObject->Get('DefaultTheme');
99    }
100
101    $Self->{UserTimeZone} ||= Kernel::System::DateTime->UserDefaultTimeZoneGet();
102
103    # Determine the language to use based on the browser setting, if there
104    #   is none yet.
105    if ( !$Self->{UserLanguage} ) {
106        my @BrowserLanguages = split /\s*,\s*/, $Self->{Lang} || $ENV{HTTP_ACCEPT_LANGUAGE} || '';
107        my %Data             = %{ $ConfigObject->Get('DefaultUsedLanguages') };
108        LANGUAGE:
109        for my $BrowserLang (@BrowserLanguages) {
110            for my $Language ( reverse sort keys %Data ) {
111
112                # check xx_XX and xx-XX type
113                my $LanguageOtherType = $Language;
114                $LanguageOtherType =~ s/_/-/;
115                if ( $BrowserLang =~ /^($Language|$LanguageOtherType)/i ) {
116                    $Self->{UserLanguage} = $Language;
117                    last LANGUAGE;
118                }
119            }
120            if ( !$Self->{UserLanguage} ) {
121                for my $Language ( reverse sort keys %Data ) {
122
123                    # If Browser requests 'vi', also offer 'vi_VI' even though we don't have 'vi'
124                    if ( $Language =~ m/^$BrowserLang/smxi ) {
125                        $Self->{UserLanguage} = $Language;
126                        last LANGUAGE;
127                    }
128                }
129            }
130        }
131        $Self->{UserLanguage} ||= $ConfigObject->Get('DefaultLanguage') || 'en';
132    }
133
134    # create language object
135    if ( !$Self->{LanguageObject} ) {
136        $Kernel::OM->ObjectParamAdd(
137            'Kernel::Language' => {
138                UserTimeZone => $Self->{UserTimeZone},
139                UserLanguage => $Self->{UserLanguage},
140                Action       => $Self->{Action},
141            },
142        );
143        $Self->{LanguageObject} = $Kernel::OM->Get('Kernel::Language');
144    }
145
146    # set charset if there is no charset given
147    $Self->{UserCharset} = 'utf-8';
148    $Self->{Charset}     = $Self->{UserCharset};                            # just for compat.
149    $Self->{SessionID}   = $Param{SessionID} || '';
150    $Self->{SessionName} = $Param{SessionName} || 'SessionID';
151    $Self->{CGIHandle}   = $ENV{SCRIPT_NAME} || 'No-$ENV{"SCRIPT_NAME"}';
152
153    # baselink
154    $Self->{Baselink} = $Self->{CGIHandle} . '?';
155    $Self->{Time}     = $Self->{LanguageObject}->Time(
156        Action => 'GET',
157        Format => 'DateFormat',
158    );
159    $Self->{TimeLong} = $Self->{LanguageObject}->Time(
160        Action => 'GET',
161        Format => 'DateFormatLong',
162    );
163
164    # set text direction
165    $Self->{TextDirection} = $Self->{LanguageObject}->{TextDirection};
166
167    # check Frontend::Output::FilterElementPost
168    $Self->{FilterElementPost} = {};
169
170    my %FilterElementPost = %{ $ConfigObject->Get('Frontend::Output::FilterElementPost') // {} };
171
172    FILTER:
173    for my $Filter ( sort keys %FilterElementPost ) {
174
175        # extract filter config
176        my $FilterConfig = $FilterElementPost{$Filter};
177
178        next FILTER if !$FilterConfig || ref $FilterConfig ne 'HASH';
179
180        # extract template list
181        my %TemplateList = %{ $FilterConfig->{Templates} || {} };
182
183        if ( !%TemplateList || $TemplateList{ALL} ) {
184
185            $Kernel::OM->Get('Kernel::System::Log')->Log(
186                Priority => 'error',
187                Message  => <<EOF,
188$FilterConfig->{Module} will be ignored because it wants to operate on all templates or does not specify a template list.
189EOF
190            );
191
192            next FILTER;
193        }
194
195        $Self->{FilterElementPost}->{$Filter} = $FilterElementPost{$Filter};
196    }
197
198    # check Frontend::Output::FilterContent
199    $Self->{FilterContent} = $ConfigObject->Get('Frontend::Output::FilterContent');
200
201    # check Frontend::Output::FilterText
202    $Self->{FilterText} = $ConfigObject->Get('Frontend::Output::FilterText');
203
204    # check browser
205    $Self->{Browser}        = 'Unknown';
206    $Self->{BrowserVersion} = 0;
207    $Self->{Platform}       = '';
208    $Self->{IsMobile}       = 0;
209
210    $Self->{BrowserJavaScriptSupport} = 1;
211    $Self->{BrowserRichText}          = 1;
212
213    my $HttpUserAgent = ( defined $ENV{HTTP_USER_AGENT} ? lc $ENV{HTTP_USER_AGENT} : '' );
214
215    if ( !$HttpUserAgent ) {
216        $Self->{Browser} = 'Unknown - no $ENV{"HTTP_USER_AGENT"}';
217    }
218    elsif ($HttpUserAgent) {
219
220        # check, if we are on a mobile platform.
221        # tablets are handled like desktops
222        # only phones are "mobile"
223        if ( $HttpUserAgent =~ /mobile/ ) {
224            $Self->{IsMobile} = 1;
225        }
226
227        # android
228        if ( $HttpUserAgent =~ /android/ ) {
229            $Self->{Platform} = 'Android';
230        }
231
232        # edge / spartan
233        if ( $HttpUserAgent =~ /edge/ ) {
234            $Self->{Browser} = 'Edge';
235        }
236
237        # msie
238        elsif (
239            $HttpUserAgent =~ /msie\s([0-9.]+)/
240            || $HttpUserAgent =~ /internet\sexplorer\/([0-9.]+)/
241            )
242        {
243            $Self->{Browser} = 'MSIE';
244
245            if ( $1 =~ /(\d+)\.(\d+)/ ) {
246                $Self->{BrowserMajorVersion} = $1;
247                $Self->{BrowserMinorVersion} = $2;
248            }
249
250            # older windows mobile phones (until IE9), that still have 'MSIE' in the user agent string
251            if ( $Self->{IsMobile} ) {
252                $Self->{Platform} = 'Windows Phone';
253            }
254        }
255
256        # mobile ie
257        elsif ( $HttpUserAgent =~ /iemobile/ ) {
258            $Self->{Browser}  = 'MSIE';
259            $Self->{Platform} = 'Windows Phone';
260        }
261
262        # mobile ie (second try)
263        elsif ( $HttpUserAgent =~ /trident/ ) {
264            $Self->{Browser} = 'MSIE';
265
266            if ( $HttpUserAgent =~ /rv:([0-9])+\.([0-9])+/ ) {
267                $Self->{BrowserMajorVersion} = $2;
268                $Self->{BrowserMinorVersion} = $3;
269            }
270        }
271
272        # iOS
273        elsif ( $HttpUserAgent =~ /(ipad|iphone|ipod)/ ) {
274            $Self->{Platform} = 'iOS';
275            $Self->{Browser}  = 'Safari';
276
277            if ( $HttpUserAgent =~ /(ipad|iphone|ipod);.*cpu.*os ([0-9]+)_/ ) {
278                $Self->{BrowserVersion} = $2;
279            }
280
281            if ( $HttpUserAgent =~ /crios/ ) {
282                $Self->{Browser} = 'Chrome';
283            }
284
285            # RichText is supported in iOS6+.
286            if ( $Self->{BrowserVersion} >= 6 ) {
287                $Self->{BrowserRichText} = 1;
288            }
289            else {
290                $Self->{BrowserRichText} = 0;
291            }
292        }
293
294        # safari
295        elsif ( $HttpUserAgent =~ /safari/ ) {
296
297            # chrome
298            if ( $HttpUserAgent =~ /chrome/ ) {
299                $Self->{Browser} = 'Chrome';
300            }
301            else {
302                $Self->{Browser} = 'Safari';
303            }
304        }
305
306        # konqueror
307        elsif ( $HttpUserAgent =~ /konqueror/ ) {
308            $Self->{Browser} = 'Konqueror';
309
310            # on konquerer disable rich text editor
311            $Self->{BrowserRichText} = 0;
312        }
313
314        # firefox
315        elsif ( $HttpUserAgent =~ /firefox/ ) {
316            $Self->{Browser} = 'Firefox';
317        }
318
319        # opera
320        elsif ( $HttpUserAgent =~ /^opera.*/ ) {
321            $Self->{Browser} = 'Opera';
322        }
323
324        # netscape
325        elsif ( $HttpUserAgent =~ /netscape/ ) {
326            $Self->{Browser} = 'Netscape';
327        }
328
329        # w3m
330        elsif ( $HttpUserAgent =~ /^w3m.*/ ) {
331            $Self->{Browser}                  = 'w3m';
332            $Self->{BrowserJavaScriptSupport} = 0;
333        }
334
335        # lynx
336        elsif ( $HttpUserAgent =~ /^lynx.*/ ) {
337            $Self->{Browser}                  = 'Lynx';
338            $Self->{BrowserJavaScriptSupport} = 0;
339        }
340
341        # links
342        elsif ( $HttpUserAgent =~ /^links.*/ ) {
343            $Self->{Browser} = 'Links';
344        }
345        else {
346            $Self->{Browser} = 'Unknown - ' . $HttpUserAgent;
347        }
348    }
349
350    # check mobile devices to disable richtext support
351    if (
352        $Self->{IsMobile}
353        && $Self->{Platform} ne 'iOS'
354        && $Self->{Platform} ne 'Android'
355        && $Self->{Platform} ne 'Windows Phone'
356        )
357    {
358        $Self->{BrowserRichText} = 0;
359    }
360
361    # check if rich text can be active
362    if ( !$Self->{BrowserJavaScriptSupport} || !$Self->{BrowserRichText} ) {
363        $ConfigObject->Set(
364            Key   => 'Frontend::RichText',
365            Value => 0,
366        );
367    }
368
369    # check if rich text is active
370    if ( !$ConfigObject->Get('Frontend::RichText') ) {
371        $Self->{BrowserRichText} = 0;
372    }
373
374    # load theme
375    my $Theme = $Self->{UserTheme} || $ConfigObject->Get('DefaultTheme') || Translatable('Standard');
376
377    # force a theme based on host name
378    my $DefaultThemeHostBased = $ConfigObject->Get('DefaultTheme::HostBased');
379    if ( $DefaultThemeHostBased && $ENV{HTTP_HOST} ) {
380
381        THEME:
382        for my $RegExp ( sort keys %{$DefaultThemeHostBased} ) {
383
384            # do not use empty regexp or theme directories
385            next THEME if !$RegExp;
386            next THEME if $RegExp eq '';
387            next THEME if !$DefaultThemeHostBased->{$RegExp};
388
389            # check if regexp is matching
390            if ( $ENV{HTTP_HOST} =~ /$RegExp/i ) {
391                $Theme = $DefaultThemeHostBased->{$RegExp};
392            }
393        }
394    }
395
396    # locate template files
397    $Self->{TemplateDir}         = $ConfigObject->Get('TemplateDir') . '/HTML/Templates/' . $Theme;
398    $Self->{StandardTemplateDir} = $ConfigObject->Get('TemplateDir') . '/HTML/Templates/' . 'Standard';
399
400    # Check if 'Standard' fallback exists
401    if ( !-e $Self->{StandardTemplateDir} ) {
402        $Self->FatalDie(
403            Message =>
404                "No existing template directory found ('$Self->{TemplateDir}')! Check your Home in Kernel/Config.pm."
405        );
406    }
407
408    if ( !-e $Self->{TemplateDir} ) {
409        $Kernel::OM->Get('Kernel::System::Log')->Log(
410            Priority => 'error',
411            Message =>
412                "No existing template directory found ('$Self->{TemplateDir}')!.
413                Default theme used instead.",
414        );
415
416        # Set TemplateDir to 'Standard' as a fallback.
417        $Theme = 'Standard';
418        $Self->{TemplateDir} = $Self->{StandardTemplateDir};
419    }
420
421    $Self->{CustomTemplateDir}         = $ConfigObject->Get('CustomTemplateDir') . '/HTML/Templates/' . $Theme;
422    $Self->{CustomStandardTemplateDir} = $ConfigObject->Get('CustomTemplateDir') . '/HTML/Templates/' . 'Standard';
423
424    # get main object
425    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
426
427    # load sub layout files
428    my $NewDir = $ConfigObject->Get('TemplateDir') . '/HTML/Layout';
429    if ( -e $NewDir ) {
430        my @NewFiles = $MainObject->DirectoryRead(
431            Directory => $NewDir,
432            Filter    => '*.pm',
433        );
434        for my $NewFile (@NewFiles) {
435            if ( $NewFile !~ /Layout.pm$/ ) {
436                $NewFile =~ s{\A.*\/(.+?).pm\z}{$1}xms;
437                my $NewClassName = "Kernel::Output::HTML::Layout::$NewFile";
438                if ( !$MainObject->RequireBaseClass($NewClassName) ) {
439                    $Self->FatalDie(
440                        Message => "Could not load class Kernel::Output::HTML::Layout::$NewFile.",
441                    );
442                }
443            }
444        }
445    }
446
447    if ( $Self->{SessionID} && $Self->{UserChallengeToken} ) {
448        $Self->{ChallengeTokenParam} = "ChallengeToken=$Self->{UserChallengeToken};";
449    }
450
451    # load NavigationModule if defined
452    if ( $Self->{ModuleReg} ) {
453        my $NavigationModule = $Kernel::OM->Get('Kernel::Config')->Get("Frontend::NavigationModule");
454        if ( $NavigationModule->{ $Param{Action} } ) {
455            $Self->{NavigationModule} = $NavigationModule->{ $Param{Action} };
456        }
457    }
458
459    return $Self;
460}
461
462sub SetEnv {
463    my ( $Self, %Param ) = @_;
464
465    for (qw(Key Value)) {
466        if ( !defined $Param{$_} ) {
467            $Kernel::OM->Get('Kernel::System::Log')->Log(
468                Priority => 'error',
469                Message  => "Need $_!"
470            );
471            $Self->FatalError();
472        }
473    }
474    $Self->{EnvNewRef}->{ $Param{Key} } = $Param{Value};
475    return 1;
476}
477
478=head2 Block()
479
480call a block and pass data to it (optional) to generate the block's output.
481
482    $LayoutObject->Block(
483        Name => 'Row',
484        Data => {
485            Time => ...,
486        },
487    );
488
489=cut
490
491sub Block {
492    my ( $Self, %Param ) = @_;
493
494    if ( !$Param{Name} ) {
495        $Kernel::OM->Get('Kernel::System::Log')->Log(
496            Priority => 'error',
497            Message  => 'Need Name!'
498        );
499        return;
500    }
501    push @{ $Self->{BlockData} },
502        {
503        Name => $Param{Name},
504        Data => $Param{Data},
505        };
506
507    return 1;
508}
509
510=head2 JSONEncode()
511
512Encode perl data structure to JSON string
513
514    my $JSON = $LayoutObject->JSONEncode(
515        Data        => $Data,
516        NoQuotes    => 0|1, # optional: no double quotes at the start and the end of JSON string
517    );
518
519=cut
520
521sub JSONEncode {
522    my ( $Self, %Param ) = @_;
523
524    # check for needed data
525    return if !defined $Param{Data};
526
527    # get JSON encoded data
528    my $JSON = $Kernel::OM->Get('Kernel::System::JSON')->Encode(
529        Data => $Param{Data},
530    ) || '""';
531
532    # remove trailing and trailing double quotes if requested
533    if ( $Param{NoQuotes} ) {
534        $JSON =~ s{ \A "(.*)" \z }{$1}smx;
535    }
536
537    return $JSON;
538}
539
540=head2 Redirect()
541
542return html for browser to redirect
543
544    my $HTML = $LayoutObject->Redirect(
545        OP => "Action=AdminUserGroup;Subaction=User;ID=$UserID",
546    );
547
548    my $HTML = $LayoutObject->Redirect(
549        ExtURL => "http://some.example.com/",
550    );
551
552During login action, C<Login => 1> should be passed to Redirect(),
553which indicates that if the browser has cookie support, it is OK
554for the session cookie to be not yet set.
555
556=cut
557
558sub Redirect {
559    my ( $Self, %Param ) = @_;
560
561    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
562
563    # add cookies if exists
564    my $Cookies = '';
565    if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
566        for ( sort keys %{ $Self->{SetCookies} } ) {
567            $Cookies .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
568        }
569    }
570
571    # create & return output
572    if ( $Param{ExtURL} ) {
573
574        # external redirect
575        $Param{Redirect} = $Param{ExtURL};
576        return $Cookies
577            . $Self->Output(
578            TemplateFile => 'Redirect',
579            Data         => \%Param
580            );
581    }
582
583    # set baselink
584    $Param{Redirect} = $Self->{Baselink};
585
586    if ( $Param{OP} ) {
587
588        # Filter out hazardous characters
589        if ( $Param{OP} =~ s{\x00}{}smxg ) {
590            $Kernel::OM->Get('Kernel::System::Log')->Log(
591                Priority => 'error',
592                Message  => 'Someone tries to use a null bytes (\x00) character in redirect!',
593            );
594        }
595
596        if ( $Param{OP} =~ s{\r}{}smxg ) {
597            $Kernel::OM->Get('Kernel::System::Log')->Log(
598                Priority => 'error',
599                Message  => 'Someone tries to use a carriage return character in redirect!',
600            );
601        }
602
603        if ( $Param{OP} =~ s{\n}{}smxg ) {
604            $Kernel::OM->Get('Kernel::System::Log')->Log(
605                Priority => 'error',
606                Message  => 'Someone tries to use a newline character in redirect!',
607            );
608        }
609
610        # internal redirect
611        $Param{OP} =~ s/^.*\?(.+?)$/$1/;
612        $Param{Redirect} .= $Param{OP};
613    }
614
615    # check if IIS 6 is used, add absolute url for IIS workaround
616    # see also:
617    #  o http://bugs.otrs.org/show_bug.cgi?id=2230
618    #  o http://bugs.otrs.org/show_bug.cgi?id=9835
619    #  o http://support.microsoft.com/default.aspx?scid=kb;en-us;221154
620    if ( $ENV{SERVER_SOFTWARE} =~ /^microsoft\-iis\/6/i ) {
621        my $Host     = $ENV{HTTP_HOST} || $ConfigObject->Get('FQDN');
622        my $HttpType = $ConfigObject->Get('HttpType');
623        $Param{Redirect} = $HttpType . '://' . $Host . $Param{Redirect};
624    }
625    my $Output = $Cookies
626        . $Self->Output(
627        TemplateFile => 'Redirect',
628        Data         => \%Param
629        );
630
631    # add session id to redirect if no cookie is enabled
632    if ( !$Self->{SessionIDCookie} && !( $Self->{BrowserHasCookie} && $Param{Login} ) ) {
633
634        # rewrite location header
635        $Output =~ s{
636            (location:\s)(.*)
637        }
638        {
639            my $Start  = $1;
640            my $Target = $2;
641            my $End = '';
642            if ($Target =~ /^(.+?)#(|.+?)$/) {
643                $Target = $1;
644                $End = "#$2";
645            }
646            if ($Target =~ /http/i || !$Self->{SessionID}) {
647                "$Start$Target$End";
648            }
649            else {
650                if ($Target =~ /(\?|&)$/) {
651                    "$Start$Target$Self->{SessionName}=$Self->{SessionID}$End";
652                }
653                elsif ($Target !~ /\?/) {
654                    "$Start$Target?$Self->{SessionName}=$Self->{SessionID}$End";
655                }
656                elsif ($Target =~ /\?/) {
657                    "$Start$Target&$Self->{SessionName}=$Self->{SessionID}$End";
658                }
659                else {
660                    "$Start$Target?&$Self->{SessionName}=$Self->{SessionID}$End";
661                }
662            }
663        }iegx;
664    }
665    return $Output;
666}
667
668sub Login {
669    my ( $Self, %Param ) = @_;
670
671    # set Action parameter for the loader
672    $Self->{Action} = 'Login';
673    $Param{IsLoginPage} = 1;
674
675    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
676
677    my $Output = '';
678    if ( $ConfigObject->Get('SessionUseCookie') ) {
679
680        # always set a cookie, so that at the time the user submits
681        # the password, we know already if the browser supports cookies.
682        # ( the session cookie isn't available at that time ).
683        my $CookieSecureAttribute = 0;
684        if ( $ConfigObject->Get('HttpType') eq 'https' ) {
685
686            # Restrict Cookie to HTTPS if it is used.
687            $CookieSecureAttribute = 1;
688        }
689        $Self->{SetCookies}->{OTRSBrowserHasCookie} = $Kernel::OM->Get('Kernel::System::Web::Request')->SetCookie(
690            Key      => 'OTRSBrowserHasCookie',
691            Value    => 1,
692            Expires  => '+1y',
693            Path     => $ConfigObject->Get('ScriptAlias'),
694            Secure   => $CookieSecureAttribute,
695            HttpOnly => 1,
696        );
697    }
698
699    # add cookies if exists
700    if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
701        for ( sort keys %{ $Self->{SetCookies} } ) {
702            $Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
703        }
704    }
705
706    # get message of the day
707    if ( $ConfigObject->Get('ShowMotd') ) {
708        $Param{Motd} = $Self->Output(
709            TemplateFile => 'Motd',
710            Data         => \%Param
711        );
712    }
713
714    # Generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
715    $Self->LoaderCreateAgentCSSCalls();
716    $Self->LoaderCreateAgentJSCalls();
717    $Self->LoaderCreateJavaScriptTranslationData();
718    $Self->LoaderCreateJavaScriptTemplateData();
719
720    my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
721    $Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
722    $Param{OTRSSTORMIsInstalled}    = $OTRSBusinessObject->OTRSSTORMIsInstalled();
723    $Param{OTRSCONTROLIsInstalled}  = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
724
725    # we need the baselink for VerfifiedGet() of selenium tests
726    $Self->AddJSData(
727        Key   => 'Baselink',
728        Value => $Self->{Baselink},
729    );
730
731    # Add header logo, if configured
732    if ( defined $ConfigObject->Get('AgentLogo') ) {
733        my %AgentLogo = %{ $ConfigObject->Get('AgentLogo') };
734        my %Data;
735
736        for my $CSSStatement ( sort keys %AgentLogo ) {
737            if ( $CSSStatement eq 'URL' ) {
738                my $WebPath = '';
739                if ( $AgentLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
740                    $WebPath = $ConfigObject->Get('Frontend::WebPath');
741                }
742                $Data{'URL'} = 'url(' . $WebPath . $AgentLogo{$CSSStatement} . ')';
743            }
744            else {
745                $Data{$CSSStatement} = $AgentLogo{$CSSStatement};
746            }
747        }
748
749        $Self->Block(
750            Name => 'HeaderLogoCSS',
751            Data => \%Data,
752        );
753    }
754
755    # add login logo, if configured
756    if ( defined $ConfigObject->Get('AgentLoginLogo') ) {
757        my %AgentLoginLogo = %{ $ConfigObject->Get('AgentLoginLogo') };
758        my %Data;
759
760        for my $CSSStatement ( sort keys %AgentLoginLogo ) {
761            if ( $CSSStatement eq 'URL' ) {
762                my $WebPath = '';
763                if ( $AgentLoginLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
764                    $WebPath = $ConfigObject->Get('Frontend::WebPath');
765                }
766                $Data{'URL'} = 'url(' . $WebPath . $AgentLoginLogo{$CSSStatement} . ')';
767            }
768            else {
769                $Data{$CSSStatement} = $AgentLoginLogo{$CSSStatement};
770            }
771        }
772
773        $Self->Block(
774            Name => 'LoginLogoCSS',
775            Data => \%Data,
776        );
777
778        $Self->Block(
779            Name => 'LoginLogo'
780        );
781    }
782
783    # get system maintenance object
784    my $SystemMaintenanceObject = $Kernel::OM->Get('Kernel::System::SystemMaintenance');
785
786    my $ActiveMaintenance = $SystemMaintenanceObject->SystemMaintenanceIsActive();
787
788    # check if system maintenance is active
789    if ($ActiveMaintenance) {
790        my $SystemMaintenanceData = $SystemMaintenanceObject->SystemMaintenanceGet(
791            ID     => $ActiveMaintenance,
792            UserID => 1,
793        );
794
795        if ( $SystemMaintenanceData->{ShowLoginMessage} ) {
796
797            my $LoginMessage =
798                $SystemMaintenanceData->{LoginMessage}
799                || $ConfigObject->Get('SystemMaintenance::IsActiveDefaultLoginMessage')
800                || "System maintenance is active, not possible to perform a login!";
801
802            $Self->Block(
803                Name => 'SystemMaintenance',
804                Data => {
805                    LoginMessage => $LoginMessage,
806                },
807            );
808        }
809    }
810
811    # show prelogin block, if in prelogin mode (e.g. SSO login)
812    if ( defined $Param{'Mode'} && $Param{'Mode'} eq 'PreLogin' ) {
813        $Self->Block(
814            Name => 'PreLogin',
815            Data => \%Param,
816        );
817    }
818
819    # if not in PreLogin mode, show normal login form
820    else {
821
822        my $DisableLoginAutocomplete = $ConfigObject->Get('DisableLoginAutocomplete');
823        $Param{UserNameAutocomplete} = $DisableLoginAutocomplete ? 'off' : 'username';
824        $Param{PasswordAutocomplete} = $DisableLoginAutocomplete ? 'off' : 'current-password';
825
826        $Self->Block(
827            Name => 'LoginBox',
828            Data => \%Param,
829        );
830
831        # show 2 factor password input if we have at least one backend enabled
832        COUNT:
833        for my $Count ( '', 1 .. 10 ) {
834            next COUNT if !$ConfigObject->Get("AuthTwoFactorModule$Count");
835
836            # if no empty shared secrets are allowed, input is mandatory
837            my %MandatoryOptions;
838            if ( !$ConfigObject->Get("AuthTwoFactorModule${Count}::AllowEmptySecret") ) {
839                %MandatoryOptions = (
840                    MandatoryClass   => 'Mandatory',
841                    ValidateRequired => 'Validate_Required',
842                );
843            }
844
845            $Self->Block(
846                Name => 'AuthTwoFactor',
847                Data => {
848                    %Param,
849                    %MandatoryOptions,
850                },
851            );
852
853            if (%MandatoryOptions) {
854                $Self->Block(
855                    Name => 'AuthTwoFactorMandatory',
856                    Data => \%Param,
857                );
858            }
859
860            last COUNT;
861        }
862
863        # get lost password
864        if (
865            $ConfigObject->Get('LostPassword')
866            && $ConfigObject->Get('AuthModule') eq 'Kernel::System::Auth::DB'
867            )
868        {
869            $Self->Block(
870                Name => 'LostPasswordLink',
871                Data => \%Param,
872            );
873
874            $Self->Block(
875                Name => 'LostPassword',
876                Data => \%Param,
877            );
878        }
879    }
880
881    # send data to JS
882    $Self->AddJSData(
883        Key   => 'LoginFailed',
884        Value => $Param{LoginFailed},
885    );
886
887    # create & return output
888    $Output .= $Self->Output(
889        TemplateFile => 'Login',
890        Data         => \%Param,
891    );
892
893    # remove the version tag from the header if configured
894    $Self->_DisableBannerCheck( OutputRef => \$Output );
895
896    return $Output;
897}
898
899sub ChallengeTokenCheck {
900    my ( $Self, %Param ) = @_;
901
902    # return if feature is disabled
903    return 1 if !$Kernel::OM->Get('Kernel::Config')->Get('SessionCSRFProtection');
904
905    # get challenge token and check it
906    my $ChallengeToken = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'ChallengeToken' ) || '';
907
908    # check regular ChallengeToken
909    return 1 if $ChallengeToken eq $Self->{UserChallengeToken};
910
911    # check ChallengeToken of all own sessions
912    my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession');
913    my @Sessions      = $SessionObject->GetAllSessionIDs();
914
915    SESSION:
916    for my $SessionID (@Sessions) {
917        my %Data = $SessionObject->GetSessionIDData( SessionID => $SessionID );
918        next SESSION if !$Data{UserID};
919        next SESSION if $Data{UserID} ne $Self->{UserID};
920        next SESSION if !$Data{UserChallengeToken};
921
922        # check ChallengeToken
923        return 1 if $ChallengeToken eq $Data{UserChallengeToken};
924    }
925
926    # no valid token found
927    if ( $Param{Type} && lc $Param{Type} eq 'customer' ) {
928        $Self->CustomerFatalError(
929            Message => 'Invalid Challenge Token!',
930        );
931    }
932    else {
933        $Self->FatalError(
934            Message => 'Invalid Challenge Token!',
935        );
936    }
937
938    return;
939}
940
941sub FatalError {
942    my ( $Self, %Param ) = @_;
943
944    # Prevent endless recursion in case of problems with Template engine.
945    return if ( $Self->{InFatalError}++ );
946
947    if ( $Param{Message} ) {
948        $Kernel::OM->Get('Kernel::System::Log')->Log(
949            Caller   => 1,
950            Priority => 'error',
951            Message  => $Param{Message},
952        );
953    }
954    my $Output = $Self->Header(
955        Area  => 'Frontend',
956        Title => 'Fatal Error'
957    );
958    $Output .= $Self->Error(%Param);
959    $Output .= $Self->Footer();
960    $Self->Print( Output => \$Output );
961    exit;
962}
963
964sub SecureMode {
965    my ( $Self, %Param ) = @_;
966
967    my $Output = $Self->Header(
968        Area  => 'Frontend',
969        Title => 'Secure Mode'
970    );
971    $Output .= $Self->Output(
972        TemplateFile => 'AdminSecureMode',
973        Data         => \%Param
974    );
975    $Output .= $Self->Footer();
976    return $Output;
977}
978
979sub FatalDie {
980    my ( $Self, %Param ) = @_;
981
982    if ( $Param{Message} ) {
983        $Kernel::OM->Get('Kernel::System::Log')->Log(
984            Caller   => 1,
985            Priority => 'error',
986            Message  => $Param{Message},
987        );
988    }
989
990    # get backend error messages
991    for (qw(Message Traceback)) {
992        my $Backend = 'Backend' . $_;
993        $Param{$Backend} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
994            Type => 'Error',
995            What => $_
996        ) || '';
997        $Param{$Backend} = $Self->Ascii2Html(
998            Text           => $Param{$Backend},
999            HTMLResultMode => 1,
1000        );
1001    }
1002    if ( !$Param{Message} ) {
1003        $Param{Message} = $Param{BackendMessage};
1004    }
1005    die $Param{Message};
1006}
1007
1008sub ErrorScreen {
1009    my ( $Self, %Param ) = @_;
1010
1011    my $Output = $Self->Header( Title => 'Error' );
1012    $Output .= $Self->Error(%Param);
1013    $Output .= $Self->Footer();
1014    return $Output;
1015}
1016
1017sub Error {
1018    my ( $Self, %Param ) = @_;
1019
1020    # get backend error messages
1021    for (qw(Message Traceback)) {
1022        my $Backend = 'Backend' . $_;
1023        $Param{$Backend} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
1024            Type => 'Error',
1025            What => $_
1026        ) || '';
1027    }
1028    if ( !$Param{BackendMessage} && !$Param{BackendTraceback} ) {
1029        $Kernel::OM->Get('Kernel::System::Log')->Log(
1030            Priority => 'error',
1031            Message  => $Param{Message} || '?',
1032        );
1033        for (qw(Message Traceback)) {
1034            my $Backend = 'Backend' . $_;
1035            $Param{$Backend} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
1036                Type => 'Error',
1037                What => $_
1038            ) || '';
1039        }
1040    }
1041
1042    if ( !$Param{Message} ) {
1043        $Param{Message} = $Param{BackendMessage};
1044
1045        # Don't check for business package if the database was not yet configured (in the installer).
1046        if (
1047            $Kernel::OM->Get('Kernel::Config')->Get('SecureMode')
1048            && $Kernel::OM->Get('Kernel::Config')->Get('DatabaseDSN')
1049            && !$Kernel::OM->Get('Kernel::System::OTRSBusiness')->OTRSBusinessIsInstalled()
1050            )
1051        {
1052            $Param{ShowOTRSBusinessHint}++;
1053        }
1054    }
1055
1056    if ( $Param{BackendTraceback} ) {
1057        $Self->Block(
1058            Name => 'ShowBackendTraceback',
1059            Data => \%Param,
1060        );
1061    }
1062
1063    # create & return output
1064    return $Self->Output(
1065        TemplateFile => 'Error',
1066        Data         => \%Param
1067    );
1068}
1069
1070sub Warning {
1071    my ( $Self, %Param ) = @_;
1072
1073    # get backend error messages
1074    $Param{BackendMessage} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
1075        Type => 'Notice',
1076        What => 'Message',
1077        )
1078        || $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
1079        Type => 'Error',
1080        What => 'Message',
1081        ) || '';
1082
1083    if ( !$Param{Message} ) {
1084        $Param{Message} = $Param{BackendMessage};
1085    }
1086
1087    # create & return output
1088    return $Self->Output(
1089        TemplateFile => 'Warning',
1090        Data         => \%Param
1091    );
1092}
1093
1094=head2 Notify()
1095
1096create notify lines
1097
1098    infos, the text will be translated
1099
1100    my $Output = $LayoutObject->Notify(
1101        Priority => 'Warning',
1102        Info => 'Some Info Message',
1103    );
1104
1105    data with link, the text will be translated
1106
1107    my $Output = $LayoutObject->Notify(
1108        Priority  => 'Warning',
1109        Data      => 'Template content',
1110        Link      => 'http://example.com/',
1111        LinkClass => 'some_CSS_class',              # optional
1112    );
1113
1114    errors, the text will be translated
1115
1116    my $Output = $LayoutObject->Notify(
1117        Priority => 'Error',
1118        Info => 'Some Error Message',
1119    );
1120
1121    errors from log backend, if no error exists, a '' will be returned
1122
1123    my $Output = $LayoutObject->Notify(
1124        Priority => 'Error',
1125    );
1126
1127=cut
1128
1129sub Notify {
1130    my ( $Self, %Param ) = @_;
1131
1132    # create & return output
1133    if ( !$Param{Info} && !$Param{Data} ) {
1134        $Param{BackendMessage} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
1135            Type => 'Notice',
1136            What => 'Message',
1137            )
1138            || $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
1139            Type => 'Error',
1140            What => 'Message',
1141            ) || '';
1142
1143        $Param{Info} = $Param{BackendMessage};
1144
1145        # return if we have nothing to show
1146        return '' if !$Param{Info};
1147    }
1148
1149    my $BoxClass = 'Notice';
1150
1151    if ( $Param{Info} ) {
1152        $Param{Info} =~ s/\n//g;
1153    }
1154    if ( $Param{Priority} && $Param{Priority} eq 'Error' ) {
1155        $BoxClass = 'Error';
1156    }
1157    elsif ( $Param{Priority} && $Param{Priority} eq 'Success' ) {
1158        $BoxClass = 'Success';
1159    }
1160    elsif ( $Param{Priority} && $Param{Priority} eq 'Info' ) {
1161        $BoxClass = 'Info';
1162    }
1163
1164    if ( $Param{Link} ) {
1165        $Self->Block(
1166            Name => 'LinkStart',
1167            Data => {
1168                LinkStart => $Param{Link},
1169                LinkClass => $Param{LinkClass} || '',
1170            },
1171        );
1172    }
1173    if ( $Param{Data} ) {
1174        $Self->Block(
1175            Name => 'Data',
1176            Data => \%Param,
1177        );
1178    }
1179    else {
1180        $Self->Block(
1181            Name => 'Text',
1182            Data => \%Param,
1183        );
1184    }
1185    if ( $Param{Link} ) {
1186        $Self->Block(
1187            Name => 'LinkStop',
1188            Data => {
1189                LinkStop => '</a>',
1190            },
1191        );
1192    }
1193    return $Self->Output(
1194        TemplateFile => 'Notify',
1195        Data         => {
1196            %Param,
1197            BoxClass => $BoxClass,
1198        },
1199    );
1200}
1201
1202=head2 NotifyNonUpdatedTickets()
1203
1204Adds notification about tickets which are not updated.
1205
1206    my $Output = $LayoutObject->NotifyNonUpdatedTickets();
1207
1208=cut
1209
1210sub NotifyNonUpdatedTickets {
1211    my ( $Self, %Param ) = @_;
1212
1213    my $NonUpdatedTicketsString = $Kernel::OM->Get('Kernel::System::Cache')->Get(
1214        Type => 'Ticket',
1215        Key  => 'NonUpdatedTicketsString-' . $Self->{UserID},
1216    );
1217
1218    return if !$NonUpdatedTicketsString;
1219
1220    # Delete this value from the cache.
1221    $Kernel::OM->Get('Kernel::System::Cache')->Delete(
1222        Type => 'Ticket',
1223        Key  => 'NonUpdatedTicketsString-' . $Self->{UserID},
1224    );
1225
1226    return $Self->Notify(
1227        Info => $Self->{LanguageObject}
1228            ->Translate( "The following tickets are not updated: %s.", $NonUpdatedTicketsString ),
1229    );
1230
1231}
1232
1233=head2 Header()
1234
1235generates the HTML for the page begin in the Agent interface.
1236
1237    my $Output = $LayoutObject->Header(
1238        Type              => 'Small',                # (optional) '' (Default, full header) or 'Small' (blank header)
1239        ShowToolbarItems  => 0,                      # (optional) default 1 (0|1)
1240        ShowPrefLink      => 0,                      # (optional) default 1 (0|1)
1241        ShowLogoutButton  => 0,                      # (optional) default 1 (0|1)
1242
1243        DisableIFrameOriginRestricted => 1,          # (optional, default 0) - suppress X-Frame-Options header.
1244    );
1245
1246=cut
1247
1248sub Header {
1249    my ( $Self, %Param ) = @_;
1250
1251    my $Type = $Param{Type} || '';
1252
1253    # check params
1254    if ( !defined $Param{ShowToolbarItems} ) {
1255        $Param{ShowToolbarItems} = 1;
1256    }
1257
1258    if ( !defined $Param{ShowPrefLink} ) {
1259        $Param{ShowPrefLink} = 1;
1260    }
1261
1262    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1263
1264    # do not show preferences link if the preferences module is disabled
1265    my $Modules = $ConfigObject->Get('Frontend::Module');
1266    if ( !$Modules->{AgentPreferences} ) {
1267        $Param{ShowPrefLink} = 0;
1268    }
1269
1270    if ( !defined $Param{ShowLogoutButton} ) {
1271        $Param{ShowLogoutButton} = 1;
1272    }
1273
1274    # set rtl if needed
1275    if ( $Self->{TextDirection} && $Self->{TextDirection} eq 'rtl' ) {
1276        $Param{BodyClass} = 'RTL';
1277    }
1278    elsif ( $ConfigObject->Get('Frontend::DebugMode') ) {
1279        $Self->Block(
1280            Name => 'DebugRTLButton',
1281        );
1282    }
1283
1284    # Generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
1285    $Self->LoaderCreateAgentCSSCalls();
1286
1287    my %AgentLogo;
1288
1289    # check if we need to display a custom logo for the selected skin
1290    my $AgentLogoCustom = $ConfigObject->Get('AgentLogoCustom');
1291    if (
1292        $Self->{SkinSelected}
1293        && $AgentLogoCustom
1294        && IsHashRefWithData($AgentLogoCustom)
1295        && $AgentLogoCustom->{ $Self->{SkinSelected} }
1296        )
1297    {
1298        %AgentLogo = %{ $AgentLogoCustom->{ $Self->{SkinSelected} } };
1299    }
1300
1301    # Otherwise show default header logo, if configured
1302    elsif ( defined $ConfigObject->Get('AgentLogo') ) {
1303        %AgentLogo = %{ $ConfigObject->Get('AgentLogo') };
1304    }
1305
1306    if ( %AgentLogo && keys %AgentLogo ) {
1307
1308        my %Data;
1309        for my $CSSStatement ( sort keys %AgentLogo ) {
1310            if ( $CSSStatement eq 'URL' ) {
1311                my $WebPath = '';
1312                if ( $AgentLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
1313                    $WebPath = $ConfigObject->Get('Frontend::WebPath');
1314                }
1315                $Data{'URL'} = 'url(' . $WebPath . $AgentLogo{$CSSStatement} . ')';
1316            }
1317            else {
1318                $Data{$CSSStatement} = $AgentLogo{$CSSStatement};
1319            }
1320        }
1321
1322        $Self->Block(
1323            Name => 'HeaderLogoCSS',
1324            Data => \%Data,
1325        );
1326    }
1327
1328    # add cookies if exists
1329    my $Output = '';
1330    if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
1331        for ( sort keys %{ $Self->{SetCookies} } ) {
1332            $Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
1333        }
1334    }
1335
1336    my $File = $Param{Filename} || $Self->{Action} || 'unknown';
1337
1338    # set file name for "save page as"
1339    $Param{ContentDisposition} = "filename=\"$File.html\"";
1340
1341    # area and title
1342    if ( !$Param{Area} ) {
1343        $Param{Area} = (
1344            defined $Self->{Action}
1345            ? $ConfigObject->Get('Frontend::Module')->{ $Self->{Action} }->{NavBarName}
1346            : ''
1347        );
1348    }
1349    if ( !$Param{Title} ) {
1350        $Param{Title} = $ConfigObject->Get('Frontend::Module')->{ $Self->{Action} }->{Title}
1351            || '';
1352    }
1353    for my $Word (qw(Value Title Area)) {
1354        if ( $Param{$Word} ) {
1355            $Param{TitleArea} .= $Self->{LanguageObject}->Translate( $Param{$Word} ) . ' - ';
1356        }
1357    }
1358
1359    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1360
1361    # run header meta modules
1362    my $HeaderMetaModule = $ConfigObject->Get('Frontend::HeaderMetaModule');
1363    if ( ref $HeaderMetaModule eq 'HASH' ) {
1364        my %Jobs = %{$HeaderMetaModule};
1365
1366        MODULE:
1367        for my $Job ( sort keys %Jobs ) {
1368
1369            # load and run module
1370            next MODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
1371            my $Object = $Jobs{$Job}->{Module}->new(
1372                %{$Self},
1373                LayoutObject => $Self,
1374            );
1375            next MODULE if !$Object;
1376            $Object->Run( %Param, Config => $Jobs{$Job} );
1377        }
1378    }
1379
1380    # run tool bar item modules
1381    if ( $Self->{UserID} && $Self->{UserType} eq 'User' ) {
1382        my $ToolBarModule = $ConfigObject->Get('Frontend::ToolBarModule');
1383        if ( $Param{ShowToolbarItems} && ref $ToolBarModule eq 'HASH' ) {
1384
1385            $Self->Block(
1386                Name => 'ToolBar',
1387                Data => \%Param,
1388            );
1389
1390            my %Modules;
1391            my %Jobs = %{$ToolBarModule};
1392
1393            # get group object
1394            my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
1395
1396            MODULE:
1397            for my $Job ( sort keys %Jobs ) {
1398
1399                # load and run module
1400                next MODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
1401                my $Object = $Jobs{$Job}->{Module}->new(
1402                    %{$Self},    # UserID etc.
1403                );
1404                next MODULE if !$Object;
1405
1406                my $ToolBarAccessOk;
1407
1408                # if group restriction for tool-bar is set, check user permission
1409                if ( $Jobs{$Job}->{Group} ) {
1410
1411                    # remove white-spaces
1412                    $Jobs{$Job}->{Group} =~ s{\s}{}xmsg;
1413
1414                    # get group configurations
1415                    my @Items = split( ';', $Jobs{$Job}->{Group} );
1416
1417                    ITEM:
1418                    for my $Item (@Items) {
1419
1420                        # split values into permission and group
1421                        my ( $Permission, $GroupName ) = split( ':', $Item );
1422
1423                        # log an error if not valid setting
1424                        if ( !$Permission || !$GroupName ) {
1425                            $Kernel::OM->Get('Kernel::System::Log')->Log(
1426                                Priority => 'error',
1427                                Message  => "Invalid config for ToolBarModule $Job - Key Group: '$Item'! "
1428                                    . "Need something like 'Permission:Group;'",
1429                            );
1430                        }
1431
1432                        # get groups for current user
1433                        my %Groups = $GroupObject->PermissionUserGet(
1434                            UserID => $Self->{UserID},
1435                            Type   => $Permission,
1436                        );
1437
1438                        # next job if user have not groups
1439                        next ITEM if !%Groups;
1440
1441                        # check user belongs to the correct group
1442                        my %GroupsReverse = reverse %Groups;
1443                        next ITEM if !$GroupsReverse{$GroupName};
1444
1445                        $ToolBarAccessOk = 1;
1446
1447                        last ITEM;
1448                    }
1449
1450                    # go to the next module if not permissions
1451                    # for the current one
1452                    next MODULE if !$ToolBarAccessOk;
1453                }
1454
1455                %Modules = ( $Object->Run( %Param, Config => $Jobs{$Job} ), %Modules );
1456            }
1457
1458            # show tool bar items
1459            MODULE:
1460            for my $Key ( sort keys %Modules ) {
1461                next MODULE if !%{ $Modules{$Key} };
1462
1463                # For ToolBarSearchFulltext module take into consideration SearchInArchive settings.
1464                # See bug#13790 (https://bugs.otrs.org/show_bug.cgi?id=13790).
1465                if ( $ConfigObject->Get('Ticket::ArchiveSystem') && $Modules{$Key}->{Block} eq 'ToolBarSearchFulltext' )
1466                {
1467                    $Modules{$Key}->{SearchInArchive}
1468                        = $ConfigObject->Get('Ticket::Frontend::AgentTicketSearch')->{Defaults}->{SearchInArchive};
1469                }
1470
1471                $Self->Block(
1472                    Name => $Modules{$Key}->{Block},
1473                    Data => {
1474                        %{ $Modules{$Key} },
1475                        AccessKeyReference => $Modules{$Key}->{AccessKey}
1476                        ? " ($Modules{$Key}->{AccessKey})"
1477                        : '',
1478                    },
1479                );
1480            }
1481        }
1482
1483        if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Chat', Silent => 1 ) ) {
1484            if ( $ConfigObject->Get('ChatEngine::Active') ) {
1485                $Self->AddJSData(
1486                    Key   => 'ChatEngine::Active',
1487                    Value => $ConfigObject->Get('ChatEngine::Active')
1488                );
1489            }
1490        }
1491
1492        # generate avatar
1493        if ( $ConfigObject->Get('Frontend::AvatarEngine') eq 'Gravatar' && $Self->{UserEmail} ) {
1494            my $DefaultIcon = $ConfigObject->Get('Frontend::Gravatar::DefaultImage') || 'mp';
1495            $Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( \$Self->{UserEmail} );
1496            $Param{Avatar}
1497                = '//www.gravatar.com/avatar/' . md5_hex( lc $Self->{UserEmail} ) . '?s=100&d=' . $DefaultIcon;
1498        }
1499        else {
1500            my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
1501                User          => $Self->{UserLogin},
1502                NoOutOfOffice => 1,
1503            );
1504
1505            $Param{UserInitials} = $Self->UserInitialsGet( Fullname => $User{UserFullname} );
1506        }
1507
1508        # show logged in notice
1509        if ( $Param{ShowPrefLink} ) {
1510            $Self->Block(
1511                Name => 'Login',
1512                Data => \%Param,
1513            );
1514        }
1515        else {
1516            $Self->Block(
1517                Name => 'LoginWithoutLink',
1518                Data => \%Param,
1519            );
1520        }
1521
1522        # show logout button (if registered)
1523        if (
1524            $Param{ShowLogoutButton}
1525            && $ConfigObject->Get('Frontend::Module')->{Logout}
1526            )
1527        {
1528            $Self->Block(
1529                Name => 'Logout',
1530                Data => \%Param,
1531            );
1532        }
1533    }
1534
1535    if ( $ConfigObject->Get('SecureMode') ) {
1536        $Param{OTRSBusinessIsInstalled} = $Kernel::OM->Get('Kernel::System::OTRSBusiness')->OTRSBusinessIsInstalled();
1537    }
1538
1539    # create & return output
1540    $Output .= $Self->Output(
1541        TemplateFile => "Header$Type",
1542        Data         => \%Param
1543    );
1544
1545    # remove the version tag from the header if configured
1546    $Self->_DisableBannerCheck( OutputRef => \$Output );
1547
1548    return $Output;
1549}
1550
1551sub Footer {
1552    my ( $Self, %Param ) = @_;
1553
1554    my $Type          = $Param{Type}           || '';
1555    my $HasDatepicker = $Self->{HasDatepicker} || 0;
1556
1557    # generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
1558    $Self->LoaderCreateAgentJSCalls();
1559    $Self->LoaderCreateJavaScriptTranslationData();
1560    $Self->LoaderCreateJavaScriptTemplateData();
1561
1562    # get datepicker data, if needed in module
1563    if ($HasDatepicker) {
1564        my $VacationDays  = $Self->DatepickerGetVacationDays();
1565        my $TextDirection = $Self->{LanguageObject}->{TextDirection} || '';
1566
1567        # send data to JS
1568        $Self->AddJSData(
1569            Key   => 'Datepicker',
1570            Value => {
1571                VacationDays => $VacationDays,
1572                IsRTL        => ( $TextDirection eq 'rtl' ) ? 1 : 0,
1573            },
1574        );
1575    }
1576
1577    # get config object
1578    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
1579
1580    # send data to JS if NewTicketInNewWindow is enabled
1581    if ( $ConfigObject->Get('NewTicketInNewWindow::Enabled') ) {
1582        $Self->AddJSData(
1583            Key   => 'NewTicketInNewWindow',
1584            Value => 1,
1585        );
1586    }
1587
1588    # AutoComplete-Config
1589    my $AutocompleteConfig = $ConfigObject->Get('AutoComplete::Agent');
1590
1591    for my $ConfigElement ( sort keys %{$AutocompleteConfig} ) {
1592        $AutocompleteConfig->{$ConfigElement}->{ButtonText}
1593            = $Self->{LanguageObject}->Translate( $AutocompleteConfig->{$ConfigElement}->{ButtonText} );
1594    }
1595
1596    # Search frontend (JavaScript)
1597    my $SearchFrontendConfig = $ConfigObject->Get('Frontend::Search::JavaScript');
1598
1599    # get target javascript function
1600    my $JSCall = '';
1601
1602    if ( $SearchFrontendConfig && $Self->{Action} ) {
1603        for my $Group ( sort keys %{$SearchFrontendConfig} ) {
1604            REGEXP:
1605            for my $RegExp ( sort keys %{ $SearchFrontendConfig->{$Group} } ) {
1606                if ( $Self->{Action} =~ /$RegExp/ ) {
1607                    $JSCall = $SearchFrontendConfig->{$Group}->{$RegExp};
1608                    last REGEXP;
1609                }
1610            }
1611        }
1612    }
1613
1614    # get OTRS business object
1615    my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
1616
1617    # don't check for business package if the database was not yet configured (in the installer)
1618    if ( $ConfigObject->Get('SecureMode') ) {
1619        $Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
1620        $Param{OTRSSTORMIsInstalled}    = $OTRSBusinessObject->OTRSSTORMIsInstalled();
1621        $Param{OTRSCONTROLIsInstalled}  = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
1622    }
1623
1624    # Check if video chat is enabled.
1625    if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::VideoChat', Silent => 1 ) ) {
1626        $Param{VideoChatEnabled} = $Kernel::OM->Get('Kernel::System::VideoChat')->IsEnabled()
1627            || $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'UnitTestMode' ) // 0;
1628    }
1629
1630    # Set an array with pending states.
1631    my @PendingStateIDs = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
1632        StateType => [ 'pending reminder', 'pending auto' ],
1633        Result    => 'ID',
1634    );
1635
1636    # add JS data
1637    my %JSConfig = (
1638        Baselink                       => $Self->{Baselink},
1639        CGIHandle                      => $Self->{CGIHandle},
1640        WebPath                        => $ConfigObject->Get('Frontend::WebPath'),
1641        Action                         => $Self->{Action},
1642        Subaction                      => $Self->{Subaction},
1643        SessionIDCookie                => $Self->{SessionIDCookie},
1644        SessionName                    => $Self->{SessionName},
1645        SessionID                      => $Self->{SessionID},
1646        SessionUseCookie               => $ConfigObject->Get('SessionUseCookie'),
1647        ChallengeToken                 => $Self->{UserChallengeToken},
1648        CustomerPanelSessionName       => $ConfigObject->Get('CustomerPanelSessionName'),
1649        UserLanguage                   => $Self->{UserLanguage},
1650        WebMaxFileUpload               => $ConfigObject->Get('WebMaxFileUpload'),
1651        RichTextSet                    => $ConfigObject->Get('Frontend::RichText'),
1652        CheckEmailAddresses            => $ConfigObject->Get('CheckEmailAddresses'),
1653        MenuDragDropEnabled            => $ConfigObject->Get('Frontend::MenuDragDropEnabled'),
1654        OpenMainMenuOnHover            => $ConfigObject->Get('OpenMainMenuOnHover'),
1655        CustomerInfoSet                => $ConfigObject->Get('Ticket::Frontend::CustomerInfoCompose'),
1656        IncludeUnknownTicketCustomers  => $ConfigObject->Get('Ticket::IncludeUnknownTicketCustomers'),
1657        InputFieldsActivated           => $ConfigObject->Get('ModernizeFormFields'),
1658        OTRSBusinessIsInstalled        => $Param{OTRSBusinessIsInstalled},
1659        VideoChatEnabled               => $Param{VideoChatEnabled},
1660        PendingStateIDs                => \@PendingStateIDs,
1661        CheckSearchStringsForStopWords => (
1662            $ConfigObject->Get('Ticket::SearchIndex::WarnOnStopWordUsage')
1663                &&
1664                (
1665                $ConfigObject->Get('Ticket::SearchIndexModule')
1666                eq 'Kernel::System::Ticket::ArticleSearchIndex::DB'
1667                )
1668        ) ? 1 : 0,
1669        SearchFrontend => $JSCall,
1670        Autocomplete   => $AutocompleteConfig,
1671    );
1672
1673    for my $Config ( sort keys %JSConfig ) {
1674        $Self->AddJSData(
1675            Key   => $Config,
1676            Value => $JSConfig{$Config},
1677        );
1678    }
1679
1680    # create & return output
1681    return $Self->Output(
1682        TemplateFile => "Footer$Type",
1683        Data         => \%Param
1684    );
1685}
1686
1687sub Print {
1688    my ( $Self, %Param ) = @_;
1689
1690    # run output content filters
1691    if ( $Self->{FilterContent} && ref $Self->{FilterContent} eq 'HASH' ) {
1692
1693        # extract filter list
1694        my %FilterList = %{ $Self->{FilterContent} };
1695
1696        my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1697
1698        FILTER:
1699        for my $Filter ( sort keys %FilterList ) {
1700
1701            # extract filter config
1702            my $FilterConfig = $FilterList{$Filter};
1703
1704            next FILTER if !$FilterConfig;
1705            next FILTER if ref $FilterConfig ne 'HASH';
1706
1707            # extract template list
1708            my $TemplateList = $FilterConfig->{Templates};
1709
1710            # check template list
1711            if ( !$TemplateList || ref $TemplateList ne 'HASH' || !%{$TemplateList} ) {
1712
1713                $Kernel::OM->Get('Kernel::System::Log')->Log(
1714                    Priority => 'error',
1715                    Message =>
1716                        "Please add a template list to output filter $FilterConfig->{Module} "
1717                        . "to improve performance. Use ALL if OutputFilter should modify all "
1718                        . "templates of the system (deprecated).",
1719                );
1720            }
1721
1722            # check template list
1723            if ( $Param{TemplateFile} && ref $TemplateList eq 'HASH' && !$TemplateList->{ALL} ) {
1724                next FILTER if !$TemplateList->{ $Param{TemplateFile} };
1725            }
1726
1727            next FILTER if !$MainObject->Require( $FilterConfig->{Module} );
1728
1729            # create new instance
1730            my $Object = $FilterConfig->{Module}->new(
1731                %{$Self},
1732                LayoutObject => $Self,
1733            );
1734
1735            next FILTER if !$Object;
1736
1737            # run output filter
1738            $Object->Run(
1739                %{$FilterConfig},
1740                Data         => $Param{Output},
1741                TemplateFile => $Param{TemplateFile} || '',
1742            );
1743        }
1744    }
1745
1746    # There seems to be a bug in FastCGI that it cannot handle unicode output properly.
1747    #   Work around this by converting to an utf8 byte stream instead.
1748    #   See also http://bugs.otrs.org/show_bug.cgi?id=6284 and
1749    #   http://bugs.otrs.org/show_bug.cgi?id=9802.
1750    if ( $INC{'CGI/Fast.pm'} || $ENV{FCGI_ROLE} || $ENV{FCGI_SOCKET_PATH} ) {    # are we on FCGI?
1751        $Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( $Param{Output} );
1752        binmode STDOUT, ':bytes';
1753    }
1754
1755    # Disable perl warnings in case of printing unicode private chars,
1756    #   see https://rt.perl.org/Public/Bug/Display.html?id=121226.
1757    no warnings 'nonchar';    ## no critic
1758
1759    print ${ $Param{Output} };
1760
1761    return 1;
1762}
1763
1764=head2 Ascii2Html()
1765
1766convert ASCII to html string
1767
1768    my $HTML = $LayoutObject->Ascii2Html(
1769        Text            => 'Some <> Test <font color="red">Test</font>',
1770        Max             => 20,       # max 20 chars flowed by [..]
1771        VMax            => 15,       # first 15 lines
1772        NewLine         => 0,        # move \r to \n
1773        HTMLResultMode  => 0,        # replace " " with C<&nbsp;>
1774        StripEmptyLines => 0,
1775        Type            => 'Normal', # JSText or Normal text
1776        LinkFeature     => 0,        # do some URL detections
1777    );
1778
1779also string ref is possible
1780
1781    my $HTMLStringRef = $LayoutObject->Ascii2Html(
1782        Text => \$String,
1783    );
1784
1785=cut
1786
1787sub Ascii2Html {
1788    my ( $Self, %Param ) = @_;
1789
1790    # check needed param
1791    return '' if !defined $Param{Text};
1792
1793    # check text
1794    my $TextScalar;
1795    my $Text;
1796    if ( !ref $Param{Text} ) {
1797        $TextScalar = 1;
1798        $Text       = \$Param{Text};
1799    }
1800    elsif ( ref $Param{Text} eq 'SCALAR' ) {
1801        $Text = $Param{Text};
1802    }
1803    else {
1804        $Kernel::OM->Get('Kernel::System::Log')->Log(
1805            Priority => 'error',
1806            Message  => 'Invalid ref "' . ref( $Param{Text} ) . '" of Text param!',
1807        );
1808        return '';
1809    }
1810
1811    # run output filter text
1812    my @Filters;
1813    if ( $Param{LinkFeature} && $Self->{FilterText} && ref $Self->{FilterText} eq 'HASH' ) {
1814
1815        # extract filter list
1816        my %FilterList = %{ $Self->{FilterText} };
1817
1818        my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1819
1820        FILTER:
1821        for my $Filter ( sort keys %FilterList ) {
1822
1823            # extract filter config
1824            my $FilterConfig = $FilterList{$Filter};
1825
1826            next FILTER if !$FilterConfig;
1827            next FILTER if ref $FilterConfig ne 'HASH';
1828
1829            # extract template list
1830            my $TemplateList = $FilterConfig->{Templates};
1831
1832            # check template list
1833            if ( !$TemplateList || ref $TemplateList ne 'HASH' || !%{$TemplateList} ) {
1834
1835                $Kernel::OM->Get('Kernel::System::Log')->Log(
1836                    Priority => 'error',
1837                    Message =>
1838                        "Please add a template list to output filter $FilterConfig->{Module} "
1839                        . "to improve performance. Use ALL if OutputFilter should modify all "
1840                        . "templates of the system (deprecated).",
1841                );
1842            }
1843
1844            # check template list
1845            if ( $Param{TemplateFile} && ref $TemplateList eq 'HASH' && !$TemplateList->{ALL} ) {
1846                next FILTER if !$TemplateList->{ $Param{TemplateFile} };
1847            }
1848
1849            $Self->FatalDie() if !$MainObject->Require( $FilterConfig->{Module} );
1850
1851            # create new instance
1852            my $Object = $FilterConfig->{Module}->new(
1853                %{$Self},
1854                LayoutObject => $Self,
1855            );
1856
1857            next FILTER if !$Object;
1858
1859            push(
1860                @Filters,
1861                {
1862                    Object => $Object,
1863                    Filter => $FilterConfig,
1864                },
1865            );
1866        }
1867
1868        # pre run
1869        for my $Filter (@Filters) {
1870
1871            $Text = $Filter->{Object}->Pre(
1872                Filter => $Filter->{Filter},
1873                Data   => $Text,
1874            );
1875        }
1876    }
1877
1878    # max width
1879    if ( $Param{Max} && length ${$Text} > $Param{Max} ) {
1880        ${$Text} = substr( ${$Text}, 0, $Param{Max} - 5 ) . '[...]';
1881    }
1882
1883    # newline
1884    if ( $Param{NewLine} && length( ${$Text} ) < 140_000 ) {
1885        ${$Text} =~ s/(\n\r|\r\r\n|\r\n)/\n/g;
1886        ${$Text} =~ s/\r/\n/g;
1887        ${$Text} =~ s/(.{4,$Param{NewLine}})(?:\s|\z)/$1\n/gm;
1888    }
1889
1890    # remove tabs
1891    ${$Text} =~ s/\t/ /g;
1892
1893    # strip empty lines
1894    if ( $Param{StripEmptyLines} ) {
1895        ${$Text} =~ s/^\s*\n//mg;
1896    }
1897
1898    # max lines
1899    if ( $Param{VMax} ) {
1900        my @TextList = split( "\n", ${$Text} );
1901        ${$Text} = '';
1902        my $Counter = 1;
1903        for (@TextList) {
1904            if ( $Counter <= $Param{VMax} ) {
1905                ${$Text} .= $_ . "\n";
1906            }
1907            $Counter++;
1908        }
1909        if ( $Counter >= $Param{VMax} ) {
1910            ${$Text} .= "[...]\n";
1911        }
1912    }
1913
1914    # html quoting
1915    ${$Text} =~ s/&/&amp;/g;
1916    ${$Text} =~ s/</&lt;/g;
1917    ${$Text} =~ s/>/&gt;/g;
1918    ${$Text} =~ s/"/&quot;/g;
1919
1920    # text -> html format quoting
1921    if ( $Param{LinkFeature} ) {
1922        for my $Filter (@Filters) {
1923            $Text = $Filter->{Object}->Post(
1924                Filter => $Filter->{Filter},
1925                Data   => $Text,
1926            );
1927        }
1928    }
1929
1930    if ( $Param{HTMLResultMode} ) {
1931        ${$Text} =~ s/\n/<br\/>\n/g;
1932        ${$Text} =~ s/  /&nbsp;&nbsp;/g;
1933
1934        # Convert the space at the beginning of the line (see bug#14346 - https://bugs.otrs.org/show_bug.cgi?id=14346).
1935        ${$Text} =~ s/\n /\n&nbsp;/g;
1936    }
1937
1938    if ( $Param{Type} && $Param{Type} eq 'JSText' ) {
1939        ${$Text} =~ s/'/\\'/g;
1940    }
1941
1942    return $Text if ref $Param{Text};
1943    return ${$Text};
1944}
1945
1946=head2 LinkQuote()
1947
1948detect links in text
1949
1950    my $HTMLWithLinks = $LayoutObject->LinkQuote(
1951        Text => $HTMLWithOutLinks,
1952    );
1953
1954also string ref is possible
1955
1956    my $HTMLWithLinksRef = $LayoutObject->LinkQuote(
1957        Text => \$HTMLWithOutLinksRef,
1958    );
1959
1960=cut
1961
1962sub LinkQuote {
1963    my ( $Self, %Param ) = @_;
1964
1965    my $Text   = $Param{Text}   || '';
1966    my $Target = $Param{Target} || 'NewPage' . int( rand(199) );
1967
1968    # check ref
1969    my $TextScalar;
1970    if ( !ref $Text ) {
1971        $TextScalar = $Text;
1972        $Text       = \$TextScalar;
1973    }
1974
1975    # run output filter text
1976    my @Filters;
1977    if ( $Self->{FilterText} && ref $Self->{FilterText} eq 'HASH' ) {
1978
1979        # extract filter list
1980        my %FilterList = %{ $Self->{FilterText} };
1981
1982        my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
1983
1984        FILTER:
1985        for my $Filter ( sort keys %FilterList ) {
1986
1987            # extract filter config
1988            my $FilterConfig = $FilterList{$Filter};
1989
1990            next FILTER if !$FilterConfig;
1991            next FILTER if ref $FilterConfig ne 'HASH';
1992
1993            # extract template list
1994            my $TemplateList = $FilterConfig->{Templates};
1995
1996            # check template list
1997            if ( !$TemplateList || ref $TemplateList ne 'HASH' || !%{$TemplateList} ) {
1998
1999                $Kernel::OM->Get('Kernel::System::Log')->Log(
2000                    Priority => 'error',
2001                    Message =>
2002                        "Please add a template list to output filter $FilterConfig->{Module} "
2003                        . "to improve performance. Use ALL if OutputFilter should modify all "
2004                        . "templates of the system (deprecated).",
2005                );
2006            }
2007
2008            # check template list
2009            if ( $Param{TemplateFile} && ref $TemplateList eq 'HASH' && !$TemplateList->{ALL} ) {
2010                next FILTER if !$TemplateList->{ $Param{TemplateFile} };
2011            }
2012
2013            $Self->FatalDie() if !$MainObject->Require( $FilterConfig->{Module} );
2014
2015            # create new instance
2016            my $Object = $FilterConfig->{Module}->new(
2017                %{$Self},
2018                LayoutObject => $Self,
2019            );
2020
2021            next FILTER if !$Object;
2022
2023            push @Filters, {
2024                Object => $Object,
2025                Filter => $FilterConfig,
2026            };
2027        }
2028    }
2029
2030    for my $Filter (@Filters) {
2031        $Text = $Filter->{Object}->Pre(
2032            Filter => $Filter->{Filter},
2033            Data   => $Text
2034        );
2035    }
2036    for my $Filter (@Filters) {
2037        $Text = $Filter->{Object}->Post(
2038            Filter => $Filter->{Filter},
2039            Data   => $Text
2040        );
2041    }
2042
2043    # do mail to quote
2044    ${$Text} =~ s/(mailto:.+?)(\.\s|\s|\)|\"|]|')/<a href=\"$1\">$1<\/a>$2/gi;
2045
2046    # check ref && return result like called
2047    if ($TextScalar) {
2048        return ${$Text};
2049    }
2050    else {
2051        return $Text;
2052    }
2053}
2054
2055=head2 HTMLLinkQuote()
2056
2057detect links in HTML code
2058
2059    my $HTMLWithLinks = $LayoutObject->HTMLLinkQuote(
2060        String => $HTMLString,
2061    );
2062
2063also string ref is possible
2064
2065    my $HTMLWithLinksRef = $LayoutObject->HTMLLinkQuote(
2066        String => \$HTMLString,
2067    );
2068
2069=cut
2070
2071sub HTMLLinkQuote {
2072    my ( $Self, %Param ) = @_;
2073
2074    return $Kernel::OM->Get('Kernel::System::HTMLUtils')->LinkQuote(
2075        String    => $Param{String},
2076        TargetAdd => 1,
2077        Target    => '_blank',
2078    );
2079}
2080
2081=head2 LinkEncode()
2082
2083perform URL encoding on query string parameter names or values.
2084
2085    my $ParamValueEncoded = $LayoutObject->LinkEncode($ParamValue);
2086
2087Don't encode entire URLs, because this will make them invalid
2088(?, & and ; will be encoded as well). Only pass one parameter name
2089or value at a time.
2090
2091=cut
2092
2093sub LinkEncode {
2094    my ( $Self, $Link ) = @_;
2095
2096    return if !defined $Link;
2097
2098    return URI::Escape::uri_escape_utf8($Link);
2099}
2100
2101sub CustomerAgeInHours {
2102    my ( $Self, %Param ) = @_;
2103
2104    my $Age       = defined( $Param{Age} ) ? $Param{Age} : return;
2105    my $Space     = $Param{Space} || '<br/>';
2106    my $AgeStrg   = '';
2107    my $HourDsc   = Translatable('h');
2108    my $MinuteDsc = Translatable('m');
2109    if ( $Kernel::OM->Get('Kernel::Config')->Get('TimeShowCompleteDescription') ) {
2110        $HourDsc   = Translatable('hour(s)');
2111        $MinuteDsc = Translatable('minute(s)');
2112    }
2113    if ( $Age =~ /^-(.*)/ ) {
2114        $Age     = $1;
2115        $AgeStrg = '-';
2116    }
2117
2118    # get hours
2119    if ( $Age >= 3600 ) {
2120        $AgeStrg .= int( ( $Age / 3600 ) ) . ' ';
2121        $AgeStrg .= $Self->{LanguageObject}->Translate($HourDsc);
2122        $AgeStrg .= $Space;
2123    }
2124
2125    # get minutes (just if age < 1 day)
2126    if ( $Age <= 3600 || int( ( $Age / 60 ) % 60 ) ) {
2127        $AgeStrg .= int( ( $Age / 60 ) % 60 ) . ' ';
2128        $AgeStrg .= $Self->{LanguageObject}->Translate($MinuteDsc);
2129    }
2130    return $AgeStrg;
2131}
2132
2133sub CustomerAge {
2134    my ( $Self, %Param ) = @_;
2135
2136    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
2137
2138    my $Age       = defined( $Param{Age} ) ? $Param{Age} : return;
2139    my $Space     = $Param{Space} || '<br/>';
2140    my $AgeStrg   = '';
2141    my $DayDsc    = Translatable('d');
2142    my $HourDsc   = Translatable('h');
2143    my $MinuteDsc = Translatable('m');
2144    if ( $ConfigObject->Get('TimeShowCompleteDescription') ) {
2145        $DayDsc    = Translatable('day(s)');
2146        $HourDsc   = Translatable('hour(s)');
2147        $MinuteDsc = Translatable('minute(s)');
2148    }
2149    if ( $Age =~ /^-(.*)/ ) {
2150        $Age     = $1;
2151        $AgeStrg = '-';
2152    }
2153
2154    # get days
2155    if ( $Age >= 86400 ) {
2156        $AgeStrg .= int( ( $Age / 3600 ) / 24 ) . ' ';
2157        $AgeStrg .= $Self->{LanguageObject}->Translate($DayDsc);
2158        $AgeStrg .= $Space;
2159    }
2160
2161    # get hours
2162    if ( $Age >= 3600 ) {
2163        $AgeStrg .= int( ( $Age / 3600 ) % 24 ) . ' ';
2164        $AgeStrg .= $Self->{LanguageObject}->Translate($HourDsc);
2165        $AgeStrg .= $Space;
2166    }
2167
2168    # get minutes (just if age < 1 day)
2169    if ( ( $Param{TimeShowAlwaysLong} || $ConfigObject->Get('TimeShowAlwaysLong') || $Age < 86400 ) && $Age != 0 ) {
2170        $AgeStrg .= int( ( $Age / 60 ) % 60 ) . ' ';
2171        $AgeStrg .= $Self->{LanguageObject}->Translate($MinuteDsc);
2172    }
2173    return $AgeStrg;
2174}
2175
2176=head2 BuildSelection()
2177
2178build a HTML option element based on given data
2179
2180    my $HTML = $LayoutObject->BuildSelection(
2181        Data            => $ArrayRef,        # use $HashRef, $ArrayRef or $ArrayHashRef (see below)
2182        Name            => 'TheName',        # name of element
2183        ID              => 'HTMLID',         # (optional) the HTML ID for this element, if not provided, the name will be used as ID as well
2184        Multiple        => 0,                # (optional) default 0 (0|1)
2185        Size            => 1,                # (optional) default 1 element size
2186        Class           => 'class',          # (optional) a css class, include 'Modernize' to activate InputFields
2187        Disabled        => 0,                # (optional) default 0 (0|1) disable the element
2188        AutoComplete    => 'off',            # (optional)
2189        OnChange        => 'javascript',     # (optional)
2190        OnClick         => 'javascript',     # (optional)
2191
2192        SelectedID     => 1,                 # (optional) use integer or arrayref (unable to use with ArrayHashRef)
2193        SelectedID     => [1, 5, 3],         # (optional) use integer or arrayref (unable to use with ArrayHashRef)
2194        SelectedValue  => 'test',            # (optional) use string or arrayref (unable to use with ArrayHashRef)
2195        SelectedValue  => ['test', 'test1'], # (optional) use string or arrayref (unable to use with ArrayHashRef)
2196
2197        Sort           => 'NumericValue',    # (optional) (AlphanumericValue|NumericValue|AlphanumericKey|NumericKey|TreeView|IndividualKey|IndividualValue) unable to use with ArrayHashRef
2198        SortIndividual => ['sec', 'min']     # (optional) only sort is set to IndividualKey or IndividualValue
2199        SortReverse    => 0,                 # (optional) reverse the list
2200
2201        Translation    => 1,                 # (optional) default 1 (0|1) translate value
2202        PossibleNone   => 0,                 # (optional) default 0 (0|1) add a leading empty selection
2203        TreeView       => 0,                 # (optional) default 0 (0|1)
2204        DisabledBranch => 'Branch',          # (optional) disable all elements of this branch (use string or arrayref)
2205        Max            => 100,               # (optional) default 100 max size of the shown value
2206        HTMLQuote      => 0,                 # (optional) default 1 (0|1) disable html quote
2207        Title          => 'C<Tooltip> Text',    # (optional) string will be shown as c<Tooltip> on c<mouseover>
2208        OptionTitle    => 1,                 # (optional) default 0 (0|1) show title attribute (the option value) on every option element
2209
2210        Filters => {                         # (optional) filter data, used by InputFields
2211            LastOwners => {                  # filter id
2212                Name   => 'Last owners',     # name of the filter
2213                Values => {                  # filtered data structure
2214                    Key1 => 'Value1',
2215                    Key2 => 'Value2',
2216                    Key3 => 'Value3',
2217                },
2218                Active => 1,                 # (optional) default 0 (0|1) make this filter immediately active
2219            },
2220            InvolvedAgents => {
2221                Name   => 'Involved in this ticket',
2222                Values => \%HashWithData,
2223            },
2224        },
2225        ExpandFilters  => 1,                 # (optional) default 0 (0|1) expand filters list by default
2226
2227        ValidateDateAfter  => '2016-01-01',  # (optional) validate that date is after supplied value
2228        ValidateDateBefore => '2016-01-01',  # (optional) validate that date is before supplied value
2229    );
2230
2231    my $HashRef = {
2232        Key1 => 'Value1',
2233        Key2 => 'Value2',
2234        Key3 => 'Value3',
2235    };
2236
2237    my $ArrayRef = [
2238        'KeyValue1',
2239        'KeyValue2',
2240        'KeyValue3',
2241        'KeyValue4',
2242    ];
2243
2244    my $ArrayHashRef = [
2245        {
2246            Key   => '1',
2247            Value => 'Value1',
2248        },
2249        {
2250            Key      => '2',
2251            Value    => 'Value1::Subvalue1',
2252            Selected => 1,
2253        },
2254        {
2255            Key   => '3',
2256            Value => 'Value1::Subvalue2',
2257        },
2258        {
2259            Key      => '4',
2260            Value    => 'Value2',
2261            Disabled => 1,
2262        }
2263    ];
2264
2265=cut
2266
2267sub BuildSelection {
2268    my ( $Self, %Param ) = @_;
2269
2270    # check needed stuff
2271    for (qw(Name Data)) {
2272        if ( !$Param{$_} ) {
2273            $Kernel::OM->Get('Kernel::System::Log')->Log(
2274                Priority => 'error',
2275                Message  => "Need $_!"
2276            );
2277            return;
2278        }
2279    }
2280
2281    # The parameters 'Ajax' and 'OnChange' are exclusive
2282    if ( $Param{Ajax} && $Param{OnChange} ) {
2283        $Kernel::OM->Get('Kernel::System::Log')->Log(
2284            Priority => 'error',
2285            Message  => "The parameters 'OnChange' and 'Ajax' exclude each other!"
2286        );
2287        return;
2288    }
2289
2290    # set OnChange if AJAX is used
2291    if ( $Param{Ajax} ) {
2292        if ( !$Param{Ajax}->{Depend} ) {
2293            $Kernel::OM->Get('Kernel::System::Log')->Log(
2294                Priority => 'error',
2295                Message  => 'Need Depend Param Ajax option!',
2296            );
2297            $Self->FatalError();
2298        }
2299        if ( !$Param{Ajax}->{Update} ) {
2300            $Kernel::OM->Get('Kernel::System::Log')->Log(
2301                Priority => 'error',
2302                Message  => 'Need Update Param Ajax option()!',
2303            );
2304            $Self->FatalError();
2305        }
2306        my $Selector = $Param{ID} || $Param{Name};
2307        $Param{OnChange} = "Core.AJAX.FormUpdate(\$('#"
2308            . $Selector . "'), '" . $Param{Ajax}->{Subaction} . "',"
2309            . " '$Param{Name}',"
2310            . " ['"
2311            . join( "', '", @{ $Param{Ajax}->{Update} } ) . "']);";
2312    }
2313
2314    # create OptionRef
2315    my $OptionRef = $Self->_BuildSelectionOptionRefCreate(%Param);
2316
2317    # create AttributeRef
2318    my $AttributeRef = $Self->_BuildSelectionAttributeRefCreate(%Param);
2319
2320    # create DataRef
2321    my $DataRef = $Self->_BuildSelectionDataRefCreate(
2322        Data         => $Param{Data},
2323        AttributeRef => $AttributeRef,
2324        OptionRef    => $OptionRef,
2325    );
2326
2327    # create FiltersRef
2328    my @Filters;
2329    my $FilterActive;
2330    if ( $Param{Filters} ) {
2331        my $Index = 1;
2332        for my $Filter ( sort keys %{ $Param{Filters} } ) {
2333            if (
2334                $Param{Filters}->{$Filter}->{Name}
2335                && $Param{Filters}->{$Filter}->{Values}
2336                )
2337            {
2338                my $FilterData = $Self->_BuildSelectionDataRefCreate(
2339                    Data         => $Param{Filters}->{$Filter}->{Values},
2340                    AttributeRef => $AttributeRef,
2341                    OptionRef    => $OptionRef,
2342                );
2343                push @Filters, {
2344                    Name => $Param{Filters}->{$Filter}->{Name},
2345                    Data => $FilterData,
2346                };
2347                if ( $Param{Filters}->{$Filter}->{Active} ) {
2348                    $FilterActive = $Index;
2349                }
2350            }
2351            else {
2352                $Kernel::OM->Get('Kernel::System::Log')->Log(
2353                    Priority => 'error',
2354                    Message  => 'Each Filter must provide Name and Values!',
2355                );
2356                $Self->FatalError();
2357            }
2358            $Index++;
2359        }
2360        @Filters = sort { $a->{Name} cmp $b->{Name} } @Filters;
2361    }
2362
2363    # generate output
2364    my $String = $Self->_BuildSelectionOutput(
2365        AttributeRef       => $AttributeRef,
2366        DataRef            => $DataRef,
2367        OptionTitle        => $Param{OptionTitle},
2368        TreeView           => $Param{TreeView},
2369        FiltersRef         => \@Filters,
2370        FilterActive       => $FilterActive,
2371        ExpandFilters      => $Param{ExpandFilters},
2372        ValidateDateAfter  => $Param{ValidateDateAfter},
2373        ValidateDateBefore => $Param{ValidateDateBefore},
2374    );
2375    return $String;
2376}
2377
2378sub NoPermission {
2379    my ( $Self, %Param ) = @_;
2380
2381    my $WithHeader = $Param{WithHeader} || 'yes';
2382
2383    if ( !$Param{Message} ) {
2384        $Param{Message} = $Self->{LanguageObject}->Translate(
2385            "This ticket does not exist, or you don't have permissions to access it in its current state. You can take one of the following actions:"
2386        );
2387    }
2388
2389    # get config option for possible next actions
2390    my $PossibleNextActions = $Kernel::OM->Get('Kernel::Config')->Get('PossibleNextActions');
2391
2392    POSSIBLE:
2393    if ( IsHashRefWithData($PossibleNextActions) ) {
2394        $Self->Block(
2395            Name => 'PossibleNextActionContainer',
2396        );
2397        for my $Key ( sort keys %{$PossibleNextActions} ) {
2398            next POSSIBLE if !$Key;
2399            next POSSIBLE if !$PossibleNextActions->{$Key};
2400
2401            $Self->Block(
2402                Name => 'PossibleNextActionRow',
2403                Data => {
2404                    Link        => $Key,
2405                    Description => $PossibleNextActions->{$Key},
2406                },
2407            );
2408        }
2409    }
2410
2411    # create output
2412    my $Output;
2413    $Output = $Self->Header( Title => 'Insufficient Rights' ) if ( $WithHeader eq 'yes' );
2414    $Output .= $Self->Output(
2415        TemplateFile => 'NoPermission',
2416        Data         => \%Param
2417    );
2418    $Output .= $Self->Footer() if ( $WithHeader eq 'yes' );
2419
2420    # return output
2421    return $Output;
2422}
2423
2424=head2 Permission()
2425
2426check if access to a frontend module exists
2427
2428    my $Access = $LayoutObject->Permission(
2429        Action => 'AdminCustomerUser',
2430        Type   => 'rw', # ro|rw possible
2431    );
2432
2433=cut
2434
2435sub Permission {
2436    my ( $Self, %Param ) = @_;
2437
2438    for my $Needed (qw(Action Type)) {
2439        if ( !defined $Param{$Needed} ) {
2440            $Kernel::OM->Get('Kernel::System::Log')->Log(
2441                Priority => 'error',
2442                Message  => "Got no $Needed!",
2443            );
2444            $Self->FatalError();
2445        }
2446    }
2447
2448    # Get config option for frontend module.
2449    my $Config = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::Module')->{ $Param{Action} };
2450    return if !$Config;
2451
2452    my $Item = $Config->{ $Param{Type} eq 'ro' ? 'GroupRo' : 'Group' };
2453
2454    my $GroupObject = $Kernel::OM->Get(
2455        $Self->{UserType} eq 'Customer' ? 'Kernel::System::CustomerGroup' : 'Kernel::System::Group'
2456    );
2457
2458    # No access restriction?
2459    if (
2460        ref $Config->{GroupRo} eq 'ARRAY'
2461        && !scalar @{ $Config->{GroupRo} }
2462        && ref $Config->{Group} eq 'ARRAY'
2463        && !scalar @{ $Config->{Group} }
2464        )
2465    {
2466        return 1;
2467    }
2468
2469    # Array access restriction.
2470    elsif ( IsArrayRefWithData($Item) ) {
2471        for my $GroupName ( @{$Item} ) {
2472            return 1 if $GroupObject->PermissionCheck(
2473                UserID    => $Self->{UserID},
2474                GroupName => $GroupName,
2475                Type      => $Param{Type},
2476            );
2477        }
2478    }
2479
2480    # Allow access if there is no configuration for module group permission.
2481    elsif ( !IsArrayRefWithData( $Config->{GroupRo} ) && !IsArrayRefWithData( $Config->{Group} ) ) {
2482        return 1;
2483    }
2484
2485    return 0;
2486}
2487
2488sub CheckMimeType {
2489    my ( $Self, %Param ) = @_;
2490
2491    my $Output = '';
2492    if ( !$Param{Action} ) {
2493        $Param{Action} = '[% Env("Action") %]';
2494    }
2495
2496    # check if it is a text/plain email
2497    if ( $Param{MimeType} && $Param{MimeType} !~ /text\/plain/i ) {
2498        $Output = '<p><i class="small">'
2499            . $Self->{LanguageObject}->Translate("This is a")
2500            . " $Param{MimeType} "
2501            . $Self->{LanguageObject}->Translate("email")
2502            . ', <a href="'
2503            . $Self->{Baselink}
2504            . "Action=$Param{Action};TicketID="
2505            . "$Param{TicketID};ArticleID=$Param{ArticleID};Subaction=ShowHTMLeMail\" "
2506            . 'target="HTMLeMail">'
2507            . $Self->{LanguageObject}->Translate("click here")
2508            . '</a> '
2509            . $Self->{LanguageObject}->Translate("to open it in a new window.")
2510            . '</i></p>';
2511    }
2512
2513    # just to be compat
2514    elsif ( $Param{Body} =~ /^<.DOCTYPE\s+html|^<HTML>/i ) {
2515        $Output = '<p><i class="small">'
2516            . $Self->{LanguageObject}->Translate("This is a")
2517            . " $Param{MimeType} "
2518            . $Self->{LanguageObject}->Translate("email")
2519            . ', <a href="'
2520            . $Self->{Baselink}
2521            . 'Action=$Param{Action};TicketID='
2522            . "$Param{TicketID};ArticleID=$Param{ArticleID};Subaction=ShowHTMLeMail\" "
2523            . 'target="HTMLeMail">'
2524            . $Self->{LanguageObject}->Translate("click here")
2525            . '</a> '
2526            . $Self->{LanguageObject}->Translate("to open it in a new window.")
2527            . '</i></p>';
2528    }
2529
2530    # return note string
2531    return $Output;
2532}
2533
2534sub ReturnValue {
2535    my ( $Self, $What ) = @_;
2536
2537    return $Self->{$What};
2538}
2539
2540=head2 Attachment()
2541
2542returns browser output to display/download a attachment
2543
2544    $HTML = $LayoutObject->Attachment(
2545        Type             => 'inline',          # optional, default: attachment, possible: inline|attachment
2546        Filename         => 'FileName.png',    # optional
2547        AdditionalHeader => $AdditionalHeader, # optional
2548        ContentType      => 'image/png',
2549        Content          => $Content,
2550        Sandbox          => 1,                 # optional, default 0; use content security policy to prohibit external
2551                                               #   scripts, flash etc.
2552    );
2553
2554    or for AJAX html snippets
2555
2556    $HTML = $LayoutObject->Attachment(
2557        Type        => 'inline',        # optional, default: attachment, possible: inline|attachment
2558        Filename    => 'FileName.html', # optional
2559        ContentType => 'text/html',
2560        Charset     => 'utf-8',         # optional
2561        Content     => $Content,
2562        NoCache     => 1,               # optional
2563    );
2564
2565=cut
2566
2567sub Attachment {
2568    my ( $Self, %Param ) = @_;
2569
2570    # check needed params
2571    for (qw(Content ContentType)) {
2572        if ( !defined $Param{$_} ) {
2573            $Kernel::OM->Get('Kernel::System::Log')->Log(
2574                Priority => 'error',
2575                Message  => "Got no $_!",
2576            );
2577            $Self->FatalError();
2578        }
2579    }
2580
2581    # get config object
2582    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
2583
2584    # return attachment
2585    my $Output = 'Content-Disposition: ';
2586    if ( $Param{Type} ) {
2587        $Output .= $Param{Type};
2588        $Output .= '; ';
2589    }
2590    else {
2591        $Output .= $ConfigObject->Get('AttachmentDownloadType') || 'attachment';
2592        $Output .= '; ';
2593    }
2594
2595    if ( $Param{Filename} ) {
2596
2597        # IE 10+ supports this
2598        my $URLEncodedFilename = URI::Escape::uri_escape_utf8( $Param{Filename} );
2599        $Output .= " filename=\"$Param{Filename}\"; filename*=utf-8''$URLEncodedFilename";
2600    }
2601    $Output .= "\n";
2602
2603    # get attachment size
2604    $Param{Size} = bytes::length( $Param{Content} );
2605
2606    # add no cache headers
2607    if ( $Param{NoCache} ) {
2608        $Output .= "Expires: Tue, 1 Jan 1980 12:00:00 GMT\n";
2609        $Output .= "Cache-Control: no-cache\n";
2610        $Output .= "Pragma: no-cache\n";
2611    }
2612    $Output .= "Content-Length: $Param{Size}\n";
2613    $Output .= "X-UA-Compatible: IE=edge,chrome=1\n";
2614
2615    if ( !$ConfigObject->Get('DisableIFrameOriginRestricted') ) {
2616        $Output .= "X-Frame-Options: SAMEORIGIN\n";
2617    }
2618
2619    if ( $Param{Sandbox} && !$Kernel::OM->Get('Kernel::Config')->Get('DisableContentSecurityPolicy') ) {
2620
2621        # Disallow external and inline scripts, active content, frames, but keep allowing inline styles
2622        #   as this is a common use case in emails.
2623        # Also disallow referrer headers to prevent referrer leaks via old-style policy directive. Please note this has
2624        #   been deprecated and will be removed in future OTRS versions in favor of a separate header (see below).
2625        # img-src:    allow external and inline (data:) images
2626        # script-src: block all scripts
2627        # object-src: allow 'self' so that the browser can load plugins for PDF display
2628        # frame-src:  block all frames
2629        # style-src:  allow inline styles for nice email display
2630        # referrer:   don't send referrers to prevent referrer-leak attacks
2631        $Output
2632            .= "Content-Security-Policy: default-src *; img-src * data:; script-src 'none'; object-src 'self'; frame-src 'none'; style-src 'unsafe-inline'; referrer no-referrer;\n";
2633
2634        # Use Referrer-Policy header to suppress referrer information in modern browsers
2635        #   (to prevent referrer-leak attacks).
2636        $Output .= "Referrer-Policy: no-referrer\n";
2637    }
2638
2639    if ( $Param{AdditionalHeader} ) {
2640        $Output .= $Param{AdditionalHeader} . "\n";
2641    }
2642
2643    if ( $Param{Charset} ) {
2644        $Output .= "Content-Type: $Param{ContentType}; charset=$Param{Charset};\n\n";
2645    }
2646    else {
2647        $Output .= "Content-Type: $Param{ContentType}\n\n";
2648    }
2649
2650    # disable utf8 flag, to write binary to output
2651    my $EncodeObject = $Kernel::OM->Get('Kernel::System::Encode');
2652    $EncodeObject->EncodeOutput( \$Output );
2653    $EncodeObject->EncodeOutput( \$Param{Content} );
2654
2655    # fix for firefox HEAD problem
2656    if ( !$ENV{REQUEST_METHOD} || $ENV{REQUEST_METHOD} ne 'HEAD' ) {
2657        $Output .= $Param{Content};
2658    }
2659
2660    # reset binmode, don't use utf8
2661    binmode STDOUT, ':bytes';
2662
2663    return $Output;
2664}
2665
2666=head2 PageNavBar()
2667
2668generates a page navigation bar
2669
2670    my %PageNavBar = $LayoutObject->PageNavBar(
2671        Limit       => 100,         # marks result of TotalHits red if Limit is gerater then AllHits
2672        WindowSize  => 15,          # max shown pages to click
2673        StartHit    => 1,           # start to show items
2674        PageShown   => 15,          # number of shown items a page
2675        AllHits     => 56,          # number of total hits
2676        Action      => 'AgentXXX',  # e. g. 'Action=' . $Self->{LayoutObject}->{Action}
2677        Link        => $Link,       # e. g. 'Subaction=View;'
2678        AJAXReplace => 'IDElement', # IDElement which should be replaced
2679        IDPrefix    => 'Tickets',   # Prefix for the id parameter
2680    );
2681
2682    return values of hash
2683
2684        TotalHits  # total hits
2685        Result     # shown items e. g. "1-5" or "16-30"
2686        SiteNavBar # html for page nav bar e. g. "1 2 3 4"
2687
2688        ResultLong     # shown items e. g. "1-5 of 32" or "16-30 of 64"
2689        SiteNavBarLong # html for page nav bar e. g. "Page: 1 2 3 4"
2690
2691=cut
2692
2693sub PageNavBar {
2694    my ( $Self, %Param ) = @_;
2695
2696    my $Limit = $Param{Limit} || 0;
2697    $Param{AllHits}  = 0 if ( !$Param{AllHits} );
2698    $Param{StartHit} = 0 if ( !$Param{AllHits} );
2699    my $Pages      = int( ( $Param{AllHits} / $Param{PageShown} ) + 0.99999 );
2700    my $Page       = int( ( $Param{StartHit} / $Param{PageShown} ) + 0.99999 );
2701    my $WindowSize = $Param{WindowSize} || 5;
2702    my $IDPrefix   = $Param{IDPrefix} || 'Generic';
2703
2704    # build Results (1-5 or 16-30)
2705    if ( $Param{AllHits} >= ( $Param{StartHit} + $Param{PageShown} ) ) {
2706        $Param{Results} = $Param{StartHit} . "-" . ( $Param{StartHit} + $Param{PageShown} - 1 );
2707    }
2708    else {
2709        $Param{Results} = "$Param{StartHit}-$Param{AllHits}";
2710    }
2711
2712    # check total hits
2713    if ( $Limit == $Param{AllHits} ) {
2714        $Param{TotalHits} = "<span class=\"PaginationLimit\">$Param{AllHits}</span>";
2715    }
2716    else {
2717        $Param{TotalHits} = $Param{AllHits};
2718    }
2719
2720    # build page nav bar
2721    my $WindowStart = sprintf( "%.0f", ( $Param{StartHit} / $Param{PageShown} ) );
2722    $WindowStart = int( ( $WindowStart / $WindowSize ) ) + 1;
2723    $WindowStart = ( $WindowStart * $WindowSize ) - ($WindowSize);
2724    my $Action = $Param{Action} || '';
2725    my $Link   = $Param{Link}   || '';
2726    my $Baselink = "$Self->{Baselink}$Action;$Link";
2727    my $i        = 0;
2728    my %PaginationData;
2729    my $WidgetName;
2730    my $ClassWidgetName;
2731
2732    if ( $Param{AJAXReplace} ) {
2733        $WidgetName = $Param{AJAXReplace};
2734        $WidgetName =~ s{-}{}xmsg;
2735
2736        $ClassWidgetName = $WidgetName;
2737        $ClassWidgetName =~ s/^Dashboard//;
2738    }
2739
2740    while ( $i <= ( $Pages - 1 ) ) {
2741        $i++;
2742
2743        # show normal page 1,2,3,...
2744        if ( $i <= ( $WindowStart + $WindowSize ) && $i > $WindowStart ) {
2745            my $BaselinkAll = $Baselink
2746                . "StartWindow=$WindowStart;StartHit="
2747                . ( ( ( $i - 1 ) * $Param{PageShown} ) + 1 );
2748            my $SelectedPage = '';
2749            my $PageNumber   = $i;
2750
2751            if ( $Page == $i ) {
2752                $SelectedPage = 'Selected';
2753            }
2754            if ( $Param{AJAXReplace} ) {
2755
2756                $PaginationData{$PageNumber} = {
2757                    Baselink    => $BaselinkAll,
2758                    AjaxReplace => $Param{AJAXReplace},
2759                    WidgetName  => $ClassWidgetName
2760                };
2761
2762                $Self->Block(
2763                    Name => 'PageAjax',
2764                    Data => {
2765                        BaselinkAll  => $BaselinkAll,
2766                        AjaxReplace  => $Param{AJAXReplace},
2767                        PageNumber   => $PageNumber,
2768                        IDPrefix     => $IDPrefix,
2769                        SelectedPage => $SelectedPage,
2770                        WidgetName   => $ClassWidgetName
2771                    },
2772                );
2773            }
2774            else {
2775                $Self->Block(
2776                    Name => 'Page',
2777                    Data => {
2778                        BaselinkAll  => $BaselinkAll,
2779                        PageNumber   => $PageNumber,
2780                        IDPrefix     => $IDPrefix,
2781                        SelectedPage => $SelectedPage
2782                    },
2783                );
2784            }
2785        }
2786
2787        # over window ">>" and ">|"
2788        elsif ( $i > ( $WindowStart + $WindowSize ) ) {
2789            my $StartWindow        = $WindowStart + $WindowSize + 1;
2790            my $LastStartWindow    = int( $Pages / $WindowSize );
2791            my $BaselinkOneForward = $Baselink . "StartHit=" . ( ( $i - 1 ) * $Param{PageShown} + 1 );
2792            my $BaselinkAllForward = $Baselink . "StartHit=" . ( ( $Param{PageShown} * ( $Pages - 1 ) ) + 1 );
2793
2794            if ( $Param{AJAXReplace} ) {
2795                $PaginationData{$BaselinkOneForward} = {
2796                    Baselink    => $BaselinkOneForward,
2797                    AjaxReplace => $Param{AJAXReplace},
2798                    WidgetName  => $ClassWidgetName
2799                };
2800                $PaginationData{$BaselinkAllForward} = {
2801                    Baselink    => $BaselinkAllForward,
2802                    AjaxReplace => $Param{AJAXReplace},
2803                    WidgetName  => $ClassWidgetName
2804                };
2805
2806                $Self->Block(
2807                    Name => 'PageForwardAjax',
2808                    Data => {
2809                        BaselinkOneForward => $BaselinkOneForward,
2810                        BaselinkAllForward => $BaselinkAllForward,
2811                        AjaxReplace        => $Param{AJAXReplace},
2812                        IDPrefix           => $IDPrefix,
2813                        WidgetName         => $ClassWidgetName
2814                    },
2815                );
2816            }
2817            else {
2818                $Self->Block(
2819                    Name => 'PageForward',
2820                    Data => {
2821                        BaselinkOneForward => $BaselinkOneForward,
2822                        BaselinkAllForward => $BaselinkAllForward,
2823                        IDPrefix           => $IDPrefix,
2824                    },
2825                );
2826            }
2827
2828            $i = 99999999;
2829        }
2830
2831        # over window "<<" and "|<"
2832        elsif ( $i < $WindowStart && ( $i - 1 ) < $Pages ) {
2833            my $StartWindow     = $WindowStart - $WindowSize - 1;
2834            my $BaselinkAllBack = $Baselink . 'StartHit=1;StartWindow=1';
2835            my $BaselinkOneBack = $Baselink . 'StartHit=' . ( ( $WindowStart - 1 ) * ( $Param{PageShown} ) + 1 );
2836
2837            if ( $Param{AJAXReplace} ) {
2838
2839                $PaginationData{$BaselinkOneBack} = {
2840                    Baselink    => $BaselinkOneBack,
2841                    AjaxReplace => $Param{AJAXReplace},
2842                    WidgetName  => $ClassWidgetName
2843                };
2844                $PaginationData{$BaselinkAllBack} = {
2845                    Baselink    => $BaselinkAllBack,
2846                    AjaxReplace => $Param{AJAXReplace},
2847                    WidgetName  => $ClassWidgetName
2848                };
2849
2850                $Self->Block(
2851                    Name => 'PageBackAjax',
2852                    Data => {
2853                        BaselinkOneBack => $BaselinkOneBack,
2854                        BaselinkAllBack => $BaselinkAllBack,
2855                        AjaxReplace     => $Param{AJAXReplace},
2856                        IDPrefix        => $IDPrefix,
2857                        WidgetName      => $ClassWidgetName
2858                    },
2859                );
2860            }
2861            else {
2862                $Self->Block(
2863                    Name => 'PageBack',
2864                    Data => {
2865                        BaselinkOneBack => $BaselinkOneBack,
2866                        BaselinkAllBack => $BaselinkAllBack,
2867                        IDPrefix        => $IDPrefix,
2868                    },
2869                );
2870            }
2871
2872            $i = $WindowStart - 1;
2873        }
2874    }
2875
2876    # send data to JS
2877    if ( $Param{AJAXReplace} ) {
2878        $Self->AddJSData(
2879            Key   => 'PaginationData' . $ClassWidgetName,
2880            Value => \%PaginationData
2881        );
2882    }
2883
2884    $Param{SearchNavBar} = $Self->Output(
2885        TemplateFile => 'Pagination',
2886        AJAX         => $Param{AJAX},
2887    );
2888
2889    # only show total amount of pages if there is more than one
2890    if ( $Pages > 1 ) {
2891        $Param{NavBarLong} = "- " . $Self->{LanguageObject}->Translate("Page") . ": $Param{SearchNavBar}";
2892    }
2893    else {
2894        $Param{SearchNavBar} = '';
2895    }
2896
2897    # return data
2898    return (
2899        TotalHits  => $Param{TotalHits},
2900        Result     => $Param{Results},
2901        ResultLong => "$Param{Results} "
2902            . $Self->{LanguageObject}->Translate("of")
2903            . " $Param{TotalHits}",
2904        SiteNavBar     => $Param{SearchNavBar},
2905        SiteNavBarLong => $Param{NavBarLong},
2906        Link           => $Param{Link},
2907    );
2908}
2909
2910sub NavigationBar {
2911    my ( $Self, %Param ) = @_;
2912
2913    if ( !$Param{Type} ) {
2914        $Param{Type} = $Self->{ModuleReg}->{NavBarName} || 'Ticket';
2915    }
2916
2917    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
2918
2919    # Create menu items.
2920    my %NavBar;
2921
2922    my $FrontendRegistration = $ConfigObject->Get('Frontend::Module');
2923    my $FrontendNavigation   = $ConfigObject->Get('Frontend::Navigation');
2924
2925    my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
2926
2927    MODULE:
2928    for my $Module ( sort keys %{$FrontendNavigation} ) {
2929
2930        # Skip if module is disabled in frontend registration.
2931        next MODULE if !IsHashRefWithData( $FrontendRegistration->{$Module} );
2932
2933        # Top-level frontend navigation configuration should always be a hash.
2934        next MODULE if !IsHashRefWithData( $FrontendNavigation->{$Module} );
2935
2936        my @ModuleNavigationConfigs;
2937
2938        # Go through all defined navigation configurations for the module and sort them by the key (00#-Module).
2939        NAVIGATION_CONFIG:
2940        for my $Key ( sort keys %{ $FrontendNavigation->{$Module} || {} } ) {
2941            next NAVIGATION_CONFIG if $Key !~ m{^\d+};
2942
2943            # FIXME: Support both old (HASH) and new (ARRAY of HASH) navigation configurations, for reasons of backwards
2944            #   compatibility. Once we are sure everything has been migrated correctly, support for HASH-only
2945            #   configuration can be dropped in future major release.
2946            if ( IsHashRefWithData( $FrontendNavigation->{$Module}->{$Key} ) ) {
2947                push @ModuleNavigationConfigs, $FrontendNavigation->{$Module}->{$Key};
2948            }
2949            elsif ( IsArrayRefWithData( $FrontendNavigation->{$Module}->{$Key} ) ) {
2950                push @ModuleNavigationConfigs, @{ $FrontendNavigation->{$Module}->{$Key} };
2951            }
2952
2953            # Skip incompatible configuration.
2954            else {
2955                next NAVIGATION_CONFIG;
2956            }
2957        }
2958
2959        ITEM:
2960        for my $Item (@ModuleNavigationConfigs) {
2961            next ITEM if !$Item->{NavBar};
2962
2963            $Item->{CSS} = '';
2964
2965            # Highlight active area link.
2966            if (
2967                ( $Item->{Type} && $Item->{Type} eq 'Menu' )
2968                && ( $Item->{NavBar} && $Item->{NavBar} eq $Param{Type} )
2969                )
2970            {
2971                $Item->{CSS} .= ' Selected';
2972            }
2973
2974            my $InheritPermissions = 0;
2975
2976            # Inherit permissions from frontend registration if no permissions were defined for the navigation entry.
2977            if ( !$Item->{GroupRo} && !$Item->{Group} ) {
2978                if ( $FrontendRegistration->{GroupRo} ) {
2979                    $Item->{GroupRo} = $FrontendRegistration->{GroupRo};
2980                }
2981                if ( $FrontendRegistration->{Group} ) {
2982                    $Item->{Group} = $FrontendRegistration->{Group};
2983                }
2984                $InheritPermissions = 1;
2985            }
2986
2987            my $Shown = 0;
2988
2989            PERMISSION:
2990            for my $Permission (qw(GroupRo Group)) {
2991
2992                # No access restriction.
2993                if (
2994                    ref $Item->{GroupRo} eq 'ARRAY'
2995                    && !scalar @{ $Item->{GroupRo} }
2996                    && ref $Item->{Group} eq 'ARRAY'
2997                    && !scalar @{ $Item->{Group} }
2998                    )
2999                {
3000                    $Shown = 1;
3001                    last PERMISSION;
3002                }
3003
3004                # Array access restriction.
3005                elsif ( $Item->{$Permission} && ref $Item->{$Permission} eq 'ARRAY' ) {
3006                    GROUP:
3007                    for my $Group ( @{ $Item->{$Permission} } ) {
3008                        next GROUP if !$Group;
3009                        my $HasPermission = $GroupObject->PermissionCheck(
3010                            UserID    => $Self->{UserID},
3011                            GroupName => $Group,
3012                            Type      => $Permission eq 'GroupRo' ? 'ro' : 'rw',
3013
3014                        );
3015                        if ($HasPermission) {
3016                            $Shown = 1;
3017                            last PERMISSION;
3018                        }
3019                    }
3020                }
3021            }
3022
3023            # If we passed the initial permission check and didn't inherit permissions from the module registration,
3024            #   make sure to also check access to the module, since navigation item might be out of sync.
3025            if ( $Shown && !$InheritPermissions ) {
3026                my $ModulePermission;
3027
3028                PERMISSION:
3029                for my $Permission (qw(GroupRo Group)) {
3030
3031                    # No access restriction.
3032                    if (
3033                        ref $FrontendRegistration->{$Module}->{GroupRo} eq 'ARRAY'
3034                        && !scalar @{ $FrontendRegistration->{$Module}->{GroupRo} }
3035                        && ref $FrontendRegistration->{$Module}->{Group} eq 'ARRAY'
3036                        && !scalar @{ $FrontendRegistration->{$Module}->{Group} }
3037                        )
3038                    {
3039
3040                        $ModulePermission = 1;
3041                        last PERMISSION;
3042                    }
3043
3044                    # Array access restriction.
3045                    elsif (
3046                        $FrontendRegistration->{$Module}->{$Permission}
3047                        && ref $FrontendRegistration->{$Module}->{$Permission} eq 'ARRAY'
3048                        )
3049                    {
3050                        GROUP:
3051                        for my $Group ( @{ $FrontendRegistration->{$Module}->{$Permission} } ) {
3052                            next GROUP if !$Group;
3053                            my $HasPermission = $GroupObject->PermissionCheck(
3054                                UserID    => $Self->{UserID},
3055                                GroupName => $Group,
3056                                Type      => $Permission eq 'GroupRo' ? 'ro' : 'rw',
3057
3058                            );
3059                            if ($HasPermission) {
3060                                $ModulePermission = 1;
3061                                last PERMISSION;
3062                            }
3063                        }
3064                    }
3065                }
3066
3067                # Hide item if no permission was granted to access the module.
3068                if ( !$ModulePermission ) {
3069                    $Shown = 0;
3070                }
3071            }
3072
3073            next ITEM if !$Shown;
3074
3075            # set prio of item
3076            my $Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
3077            COUNT:
3078            for ( 1 .. 51 ) {
3079                last COUNT if !$NavBar{$Key};
3080
3081                $Item->{Prio}++;
3082                $Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
3083            }
3084
3085            # show as main menu
3086            if ( $Item->{Type} eq 'Menu' ) {
3087                $NavBar{$Key} = $Item;
3088            }
3089
3090            # show as sub of main menu
3091            else {
3092                $NavBar{Sub}->{ $Item->{NavBar} }->{$Key} = $Item;
3093            }
3094        }
3095    }
3096
3097    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
3098
3099    # run menu item modules
3100    if ( ref $ConfigObject->Get('Frontend::NavBarModule') eq 'HASH' ) {
3101        my %Jobs = %{ $ConfigObject->Get('Frontend::NavBarModule') };
3102
3103        MENUMODULE:
3104        for my $Job ( sort keys %Jobs ) {
3105
3106            # load module
3107            next MENUMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
3108            my $Object = $Jobs{$Job}->{Module}->new(
3109                %{$Self},
3110                LayoutObject => $Self,
3111            );
3112            next MENUMODULE if !$Object;
3113
3114            # run module
3115            %NavBar = (
3116                %NavBar,
3117                $Object->Run(
3118                    %Param,
3119                    Config => $Jobs{$Job},
3120                    NavBar => \%NavBar || {}
3121                )
3122            );
3123        }
3124    }
3125
3126    # show nav bar
3127    ITEM:
3128    for my $Key ( sort keys %NavBar ) {
3129        next ITEM if $Key eq 'Sub';
3130        next ITEM if !%{ $NavBar{$Key} };
3131        my $Item = $NavBar{$Key};
3132        $Item->{NameForID} = $Item->{Name};
3133        $Item->{NameForID} =~ s/[ &;]//ig;
3134        my $Sub = $NavBar{Sub}->{ $Item->{NavBar} };
3135
3136        $Self->Block(
3137            Name => 'ItemArea',
3138            Data => {
3139                %$Item,
3140                AccessKeyReference => $Item->{AccessKey} ? " ($Item->{AccessKey})" : '',
3141            },
3142        );
3143
3144        # show sub menu (only if sub elements available)
3145        next ITEM if !$Sub;
3146        next ITEM if !keys %{$Sub};
3147
3148        $Self->Block(
3149            Name => 'ItemAreaSub',
3150            Data => $Item,
3151        );
3152
3153        # Sort Admin sub modules (favorites) correctly. See bug#13103 for more details.
3154        my @Subs = sort keys %{$Sub};
3155        if ( $Item->{NameForID} eq 'Admin' ) {
3156            @Subs = sort { $a <=> $b } keys %{$Sub};
3157        }
3158
3159        for my $Key (@Subs) {
3160            my $ItemSub = $Sub->{$Key};
3161            $ItemSub->{NameForID} = $ItemSub->{Name};
3162            $ItemSub->{NameForID} =~ s/[ &;]//ig;
3163            $ItemSub->{NameTop} = $Item->{NameForID};
3164            $ItemSub->{Description}
3165                ||= $ItemSub->{Name};    # use 'name' as fallback, this is shown as the link title
3166            $Self->Block(
3167                Name => 'ItemAreaSubItem',    #$Item->{Block} || 'Item',
3168                Data => {
3169                    %$ItemSub,
3170                    AccessKeyReference => $ItemSub->{AccessKey} ? " ($ItemSub->{AccessKey})" : '',
3171                },
3172            );
3173        }
3174    }
3175
3176    # get user preferences for custom nav bar item ordering
3177    my %UserPreferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences(
3178        UserID => $Self->{UserID},
3179    );
3180
3181    my $NavbarOrderItems = $UserPreferences{'UserNavBarItemsOrder'} || '';
3182    $Self->AddJSData(
3183        Key   => 'NavbarOrderItems',
3184        Value => $NavbarOrderItems,
3185    );
3186
3187    my $FrontendSearch = $ConfigObject->Get('Frontend::Search') || {};
3188
3189    my $SearchAdded;
3190
3191    # show search icon if any search router is configured
3192    if ( IsHashRefWithData($FrontendSearch) ) {
3193
3194        KEY:
3195        for my $Key ( sort keys %{$FrontendSearch} ) {
3196            next KEY if !IsHashRefWithData( $FrontendSearch->{$Key} );
3197
3198            for my $Regex ( sort keys %{ $FrontendSearch->{$Key} } ) {
3199                next KEY if !$Regex;
3200
3201                # Check if regex matches current action.
3202                if ( $Self->{Action} =~ m{$Regex}g ) {
3203
3204                    # Extract Action from the configuration.
3205                    my ($Action) = $FrontendSearch->{$Key}->{$Regex} =~ m{Action=(.*?)(;.*)?$};
3206
3207                    # Do not show Search icon if action is not registered.
3208                    next KEY if !$FrontendRegistration->{$Action};
3209
3210                    $Self->Block(
3211                        Name => 'SearchIcon',
3212                    );
3213
3214                    $SearchAdded = 1;
3215                    last KEY;
3216                }
3217            }
3218        }
3219    }
3220
3221    # If Search icon is not added, check if AgentTicketSearch is enabled and add it.
3222    if ( !$SearchAdded && $FrontendRegistration->{AgentTicketSearch} ) {
3223        $Self->Block(
3224            Name => 'SearchIcon',
3225        );
3226    }
3227
3228    # create & return output
3229    my $Output = $Self->Output(
3230        TemplateFile => 'AgentNavigationBar',
3231        Data         => \%Param,
3232    );
3233
3234    # run nav bar output modules
3235    my $NavBarOutputModuleConfig = $ConfigObject->Get('Frontend::NavBarOutputModule');
3236    if ( ref $NavBarOutputModuleConfig eq 'HASH' ) {
3237        my %Jobs = %{$NavBarOutputModuleConfig};
3238
3239        OUTPUTMODULE:
3240        for my $Job ( sort keys %Jobs ) {
3241
3242            # load module
3243            next OUTPUTMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
3244            my $Object = $Jobs{$Job}->{Module}->new(
3245                %{$Self},
3246                LayoutObject => $Self,
3247            );
3248            next OUTPUTMODULE if !$Object;
3249
3250            # run module
3251            $Output .= $Object->Run( %Param, Config => $Jobs{$Job} );
3252        }
3253    }
3254
3255    # run notification modules
3256    my $FrontendNotifyModuleConfig = $ConfigObject->Get('Frontend::NotifyModule');
3257    if ( ref $FrontendNotifyModuleConfig eq 'HASH' ) {
3258        my %Jobs = %{$FrontendNotifyModuleConfig};
3259
3260        NOTIFICATIONMODULE:
3261        for my $Job ( sort keys %Jobs ) {
3262
3263            # load module
3264            next NOTIFICATIONMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
3265            my $Object = $Jobs{$Job}->{Module}->new(
3266                %{$Self},
3267                LayoutObject => $Self,
3268            );
3269            next NOTIFICATIONMODULE if !$Object;
3270
3271            # run module
3272            $Output .= $Object->Run( %Param, Config => $Jobs{$Job} );
3273        }
3274    }
3275
3276    # run nav bar modules
3277    if ( $Self->{NavigationModule} ) {
3278
3279        # run navbar modules
3280        my %Jobs = %{ $Self->{NavigationModule} };
3281
3282        # load module
3283        if ( !$MainObject->Require( $Jobs{Module} ) ) {
3284            return $Output;
3285        }
3286
3287        my $Object = $Jobs{Module}->new(
3288            %{$Self},
3289            LayoutObject => $Self,
3290        );
3291
3292        if ( !$Object ) {
3293            return $Output;
3294        }
3295
3296        # run module
3297        $Output .= $Object->Run( %Param, Config => \%Jobs );
3298    }
3299    return $Output;
3300}
3301
3302sub TransformDateSelection {
3303    my ( $Self, %Param ) = @_;
3304
3305    # get key prefix
3306    my $Prefix = $Param{Prefix} || '';
3307
3308    # time zone translation if needed
3309    # from user time zone to OTRS time zone
3310    if ( $Self->{UserTimeZone} ) {
3311        my $DateTimeObject = $Kernel::OM->Create(
3312            'Kernel::System::DateTime',
3313            ObjectParams => {
3314                Year     => $Param{ $Prefix . 'Year' },
3315                Month    => $Param{ $Prefix . 'Month' },
3316                Day      => $Param{ $Prefix . 'Day' },
3317                Hour     => $Param{ $Prefix . 'Hour' } || 0,
3318                Minute   => $Param{ $Prefix . 'Minute' } || 0,
3319                Second   => $Param{ $Prefix . 'Second' } || 0,
3320                TimeZone => $Self->{UserTimeZone},
3321            },
3322        );
3323
3324        if ($DateTimeObject) {
3325            $DateTimeObject->ToOTRSTimeZone();
3326            my $DateTimeValues = $DateTimeObject->Get();
3327
3328            $Param{ $Prefix . 'Year' }   = $DateTimeValues->{Year};
3329            $Param{ $Prefix . 'Month' }  = $DateTimeValues->{Month};
3330            $Param{ $Prefix . 'Day' }    = $DateTimeValues->{Day};
3331            $Param{ $Prefix . 'Hour' }   = $DateTimeValues->{Hour};
3332            $Param{ $Prefix . 'Minute' } = $DateTimeValues->{Minute};
3333            $Param{ $Prefix . 'Second' } = $DateTimeValues->{Second};
3334        }
3335    }
3336
3337    # reset prefix
3338    $Param{Prefix} = '';
3339
3340    return %Param;
3341}
3342
3343=head2 BuildDateSelection()
3344
3345build the HTML code to represent a date selection based on the given data.
3346Depending on the SysConfig settings the controls to set the date could be multiple select or input fields
3347
3348    my $HTML = $LayoutObject->BuildDateSelection(
3349        Prefix           => 'some prefix',        # optional, (needed to specify other parameters)
3350        <Prefix>Year     => 2015,                 # optional, defaults to current year, used to set the initial value
3351        <Prefix>Month    => 6,                    # optional, defaults to current month, used to set the initial value
3352        <Prefix>Day      => 9,                    # optional, defaults to current day, used to set the initial value
3353        <Prefix>Hour     => 12,                   # optional, defaults to current hour, used to set the initial value
3354        <Prefix>Minute   => 26,                   # optional, defaults to current minute, used to set the initial value
3355        <Prefix>Second   => 59,                   # optional, defaults to current second, used to set the initial value
3356        <Prefix>Optional => 1,                    # optional, default 0, when active a checkbox is included to specify
3357                                                  #   if the values should be saved or not
3358        <Prefix>Used     => 1,                    # optional, default 0, used to set the initial state of the checkbox
3359                                                  #   mentioned above
3360        <Prefix>Required => 1,                    # optional, default 0 (Deprecated)
3361        <prefix>Class    => 'some class',         # optional, specify an additional class to the HTML elements
3362        Area     => 'some area',                  # optional, default 'Agent' (Deprecated)
3363        DiffTime => 123,                          # optional, default 0, used to set the initial time influencing the
3364                                                  #   current time (in seconds)
3365        OverrideTimeZone => 1,                    # optional (1 or 0), when active the time is not translated to the user
3366                                                  #   time zone
3367        YearPeriodFuture => 3,                    # optional, used to define the number of years in future to be display
3368                                                  #   in the year select
3369        YearPeriodPast   => 2,                    # optional, used to define the number of years in past to be display
3370                                                  #   in the year select
3371        YearDiff         => 0,                    # optional. used to define the number of years to be displayed
3372                                                  #   in the year select (alternatively to YearPeriodFuture and YearPeriodPast)
3373        ValidateDateInFuture     => 1,            # optional (1 or 0), when active sets an special class to validate
3374                                                  #   that the date set in the controls to be in the future
3375        ValidateDateNotInFuture  => 1,            # optional (1 or 0), when active sets an special class to validate
3376                                                  #   that the date set in the controls not to be in the future
3377        ValidateDateAfterPrefix  => 'Start',      # optional (Prefix), when defined sets a special class to validate
3378                                                  #   that the date set in the controls comes after the date with Prefix
3379        ValidateDateAfterValue   => '2016-01-01', # optional (Date), when defined sets a special data parameter to validate
3380                                                  #   that the date set in the controls comes after the supplied date
3381        ValidateDateBeforePrefix => 'End',        # optional (Prefix), when defined sets a special class to validate
3382                                                  #   that the date set in the controls comes before the date with Prefix
3383        ValidateDateBeforeValue  => '2016-01-01', # optional (Date), when defined sets a special data parameter to validate
3384                                                  #   that the date set in the controls comes before the supplied date
3385        Calendar => 2,                            # optional, used to define the SysConfig calendar on which the Datepicker
3386                                                  #   will be based on to show the vacation days and the start week day
3387        Format   => 'DateInputFormat',            # optional, or 'DateInputFormatLong', used to define if only date or
3388                                                  #   date/time components should be shown (DateInputFormatLong shows date/time)
3389        Validate => 1,                            # optional (1 or 0), defines if the date selection should be validated on
3390                                                  #   client side with JS
3391        Disabled => 1,                            # optional (1 or 0), when active select and checkbox controls gets the
3392                                                  #   disabled attribute and input fields gets the read only attribute
3393    );
3394
3395=cut
3396
3397sub BuildDateSelection {
3398    my ( $Self, %Param ) = @_;
3399
3400    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
3401
3402    my $DateInputStyle = $ConfigObject->Get('TimeInputFormat');
3403    my $MinuteStep     = $ConfigObject->Get('TimeInputMinutesStep');
3404    my $Prefix         = $Param{Prefix} || '';
3405    my $DiffTime       = $Param{DiffTime} || 0;
3406    my $Format         = defined( $Param{Format} ) ? $Param{Format} : 'DateInputFormatLong';
3407    my $Area           = $Param{Area} || 'Agent';
3408    my $Optional       = $Param{ $Prefix . 'Optional' } || 0;
3409    my $Required       = $Param{ $Prefix . 'Required' } || 0;
3410    my $Used           = $Param{ $Prefix . 'Used' } || 0;
3411    my $Class          = $Param{ $Prefix . 'Class' } || '';
3412
3413    # Defines, if the date selection should be validated on client side with JS
3414    my $Validate = $Param{Validate} || 0;
3415
3416    # Validate that the date is in the future (e. g. pending times)
3417    my $ValidateDateInFuture    = $Param{ValidateDateInFuture}    || 0;
3418    my $ValidateDateNotInFuture = $Param{ValidateDateNotInFuture} || 0;
3419
3420    # Validate that the date is set after/before supplied date
3421    my $ValidateDateAfterPrefix  = $Param{ValidateDateAfterPrefix}  || '';
3422    my $ValidateDateAfterValue   = $Param{ValidateDateAfterValue}   || '';
3423    my $ValidateDateBeforePrefix = $Param{ValidateDateBeforePrefix} || '';
3424    my $ValidateDateBeforeValue  = $Param{ValidateDateBeforeValue}  || '';
3425
3426    my $GetCurSysDTUnitFromLowest = sub {
3427        my %Param = @_;
3428
3429        my $DateTimeObject = $Kernel::OM->Create(
3430            'Kernel::System::DateTime',
3431            ObjectParams => {
3432                TimeZone => $Self->{UserTimeZone}
3433            }
3434        );
3435        if ( $Param{AddSeconds} ) {
3436            $DateTimeObject->Add( Seconds => $Param{AddSeconds} );
3437        }
3438
3439        my %Details = %{ $DateTimeObject->Get() };
3440
3441        return map { $Details{$_} } (qw(Second Minute Hour Day Month Year));
3442    };
3443
3444    my ( $s, $m, $h, $D, $M, $Y ) = $GetCurSysDTUnitFromLowest->(
3445        AddSeconds => $DiffTime,
3446    );
3447    my ( $Cs, $Cm, $Ch, $CD, $CM, $CY ) = $GetCurSysDTUnitFromLowest->();
3448
3449    # time zone translation
3450    if (
3451        $Self->{UserTimeZone}
3452        && $Param{ $Prefix . 'Year' }
3453        && $Param{ $Prefix . 'Month' }
3454        && $Param{ $Prefix . 'Day' }
3455        && !$Param{OverrideTimeZone}
3456        )
3457    {
3458        my $DateTimeObject = $Kernel::OM->Create(
3459            'Kernel::System::DateTime',
3460            ObjectParams => {
3461                Year   => $Param{ $Prefix . 'Year' },
3462                Month  => $Param{ $Prefix . 'Month' },
3463                Day    => $Param{ $Prefix . 'Day' },
3464                Hour   => $Param{ $Prefix . 'Hour' } || 0,
3465                Minute => $Param{ $Prefix . 'Minute' } || 0,
3466                Second => $Param{ $Prefix . 'Second' } || 0,
3467            },
3468        );
3469
3470        if ($DateTimeObject) {
3471            $DateTimeObject->ToTimeZone( TimeZone => $Self->{UserTimeZone} );
3472            my $DateTimeValues = $DateTimeObject->Get();
3473
3474            $Param{ $Prefix . 'Year' }   = $DateTimeValues->{Year};
3475            $Param{ $Prefix . 'Month' }  = $DateTimeValues->{Month};
3476            $Param{ $Prefix . 'Day' }    = $DateTimeValues->{Day};
3477            $Param{ $Prefix . 'Hour' }   = $DateTimeValues->{Hour};
3478            $Param{ $Prefix . 'Minute' } = $DateTimeValues->{Minute};
3479            $Param{ $Prefix . 'Second' } = $DateTimeValues->{Second};
3480        }
3481    }
3482
3483    # year
3484    if ( $DateInputStyle eq 'Option' ) {
3485        my %Year;
3486        if ( defined $Param{YearPeriodPast} && defined $Param{YearPeriodFuture} ) {
3487            for ( $Y - $Param{YearPeriodPast} .. $Y + $Param{YearPeriodFuture} ) {
3488                $Year{$_} = $_;
3489            }
3490        }
3491        else {
3492            for ( 2001 .. $Y + 1 + ( $Param{YearDiff} || 0 ) ) {
3493                $Year{$_} = $_;
3494            }
3495        }
3496
3497        # Check if the DiffTime is in a future year. In this case, we add the missing years between
3498        # $CY (current year) and $Y (year) to allow the user to manually set back the year if needed.
3499        if ( $Y > $CY ) {
3500            for ( $CY .. $Y ) {
3501                $Year{$_} = $_;
3502            }
3503        }
3504
3505        $Param{Year} = $Self->BuildSelection(
3506            Name        => $Prefix . 'Year',
3507            Data        => \%Year,
3508            SelectedID  => int( $Param{ $Prefix . 'Year' } || $Y ),
3509            Translation => 0,
3510            Class       => $Validate ? "Validate_DateYear $Class" : $Class,
3511            Title       => $Self->{LanguageObject}->Translate('Year'),
3512            Disabled    => $Param{Disabled},
3513        );
3514    }
3515    else {
3516        $Param{Year} = "<input type=\"text\" "
3517            . ( $Validate ? "class=\"Validate_DateYear $Class\" " : "class=\"$Class\" " )
3518            . "name=\"${Prefix}Year\" id=\"${Prefix}Year\" size=\"4\" maxlength=\"4\" "
3519            . "title=\""
3520            . $Self->{LanguageObject}->Translate('Year')
3521            . "\" value=\""
3522            . sprintf( "%02d", ( $Param{ $Prefix . 'Year' } || $Y ) ) . "\" "
3523            . ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
3524    }
3525
3526    # month
3527    if ( $DateInputStyle eq 'Option' ) {
3528        my %Month = map { $_ => sprintf( "%02d", $_ ); } ( 1 .. 12 );
3529        $Param{Month} = $Self->BuildSelection(
3530            Name        => $Prefix . 'Month',
3531            Data        => \%Month,
3532            SelectedID  => int( $Param{ $Prefix . 'Month' } || $M ),
3533            Translation => 0,
3534            Class       => $Validate ? "Validate_DateMonth $Class" : $Class,
3535            Title       => $Self->{LanguageObject}->Translate('Month'),
3536            Disabled    => $Param{Disabled},
3537        );
3538    }
3539    else {
3540        $Param{Month} = "<input type=\"text\" "
3541            . ( $Validate ? "class=\"Validate_DateMonth $Class\" " : "class=\"$Class\" " )
3542            . "name=\"${Prefix}Month\" id=\"${Prefix}Month\" size=\"2\" maxlength=\"2\" "
3543            . "title=\""
3544            . $Self->{LanguageObject}->Translate('Month')
3545            . "\" value=\""
3546            . sprintf( "%02d", ( $Param{ $Prefix . 'Month' } || $M ) ) . "\" "
3547            . ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
3548    }
3549
3550    my $DateValidateClasses = '';
3551    if ($Validate) {
3552        $DateValidateClasses
3553            .= "Validate_DateDay Validate_DateYear_${Prefix}Year Validate_DateMonth_${Prefix}Month";
3554
3555        if ( $Format eq 'DateInputFormatLong' ) {
3556            $DateValidateClasses
3557                .= " Validate_DateHour_${Prefix}Hour Validate_DateMinute_${Prefix}Minute";
3558        }
3559
3560        if ($ValidateDateInFuture) {
3561            $DateValidateClasses .= " Validate_DateInFuture";
3562        }
3563        if ($ValidateDateNotInFuture) {
3564            $DateValidateClasses .= " Validate_DateNotInFuture";
3565        }
3566        if ( $ValidateDateAfterPrefix || $ValidateDateAfterValue ) {
3567            $DateValidateClasses .= ' Validate_DateAfter';
3568        }
3569        if ( $ValidateDateBeforePrefix || $ValidateDateBeforeValue ) {
3570            $DateValidateClasses .= ' Validate_DateBefore';
3571        }
3572        if ($ValidateDateAfterPrefix) {
3573            $DateValidateClasses .= " Validate_DateAfter_$ValidateDateAfterPrefix";
3574        }
3575        if ($ValidateDateBeforePrefix) {
3576            $DateValidateClasses .= " Validate_DateBefore_$ValidateDateBeforePrefix";
3577        }
3578    }
3579
3580    # day
3581    if ( $DateInputStyle eq 'Option' ) {
3582        my %Day = map { $_ => sprintf( "%02d", $_ ); } ( 1 .. 31 );
3583        $Param{Day} = $Self->BuildSelection(
3584            Name               => $Prefix . 'Day',
3585            Data               => \%Day,
3586            SelectedID         => int( $Param{ $Prefix . 'Day' } || $D ),
3587            Translation        => 0,
3588            Class              => "$DateValidateClasses $Class",
3589            Title              => $Self->{LanguageObject}->Translate('Day'),
3590            Disabled           => $Param{Disabled},
3591            ValidateDateAfter  => $ValidateDateAfterValue,
3592            ValidateDateBefore => $ValidateDateBeforeValue,
3593        );
3594    }
3595    else {
3596        $Param{Day} = "<input type=\"text\" "
3597            . "class=\"$DateValidateClasses $Class\" "
3598            . "name=\"${Prefix}Day\" id=\"${Prefix}Day\" size=\"2\" maxlength=\"2\" "
3599            . "title=\""
3600            . $Self->{LanguageObject}->Translate('Day')
3601            . "\" value=\""
3602            . sprintf( "%02d", ( $Param{ $Prefix . 'Day' } || $D ) ) . "\" "
3603            . ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
3604
3605    }
3606    if ( $Format eq 'DateInputFormatLong' ) {
3607
3608        # hour
3609        if ( $DateInputStyle eq 'Option' ) {
3610            my %Hour = map { $_ => sprintf( "%02d", $_ ); } ( 0 .. 23 );
3611            $Param{Hour} = $Self->BuildSelection(
3612                Name       => $Prefix . 'Hour',
3613                Data       => \%Hour,
3614                SelectedID => defined( $Param{ $Prefix . 'Hour' } )
3615                ? int( $Param{ $Prefix . 'Hour' } )
3616                : int($h),
3617                Translation => 0,
3618                Class       => $Validate ? ( 'Validate_DateHour ' . $Class ) : $Class,
3619                Title       => $Self->{LanguageObject}->Translate('Hours'),
3620                Disabled    => $Param{Disabled},
3621            );
3622        }
3623        else {
3624            $Param{Hour} = "<input type=\"text\" "
3625                . ( $Validate ? "class=\"Validate_DateHour $Class\" " : "class=\"$Class\" " )
3626                . "name=\"${Prefix}Hour\" id=\"${Prefix}Hour\" size=\"2\" maxlength=\"2\" "
3627                . "title=\""
3628                . $Self->{LanguageObject}->Translate('Hours')
3629                . "\" value=\""
3630                . sprintf(
3631                "%02d",
3632                ( defined( $Param{ $Prefix . 'Hour' } ) ? int( $Param{ $Prefix . 'Hour' } ) : $h )
3633                )
3634                . "\" "
3635                . ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
3636
3637        }
3638
3639        # minute
3640        if ( $DateInputStyle eq 'Option' ) {
3641            my %Minute
3642                = map { $_ => sprintf( "%02d", $_ ); } map { $_ * $MinuteStep } ( 0 .. ( 60 / $MinuteStep - 1 ) );
3643            $Param{Minute} = $Self->BuildSelection(
3644                Name       => $Prefix . 'Minute',
3645                Data       => \%Minute,
3646                SelectedID => defined( $Param{ $Prefix . 'Minute' } )
3647                ? int( $Param{ $Prefix . 'Minute' } )
3648                : int( $m - $m % $MinuteStep ),
3649                Translation => 0,
3650                Class       => $Validate ? ( 'Validate_DateMinute ' . $Class ) : $Class,
3651                Title       => $Self->{LanguageObject}->Translate('Minutes'),
3652                Disabled    => $Param{Disabled},
3653            );
3654        }
3655        else {
3656            $Param{Minute} = "<input type=\"text\" "
3657                . ( $Validate ? "class=\"Validate_DateMinute $Class\" " : "class=\"$Class\" " )
3658                . "name=\"${Prefix}Minute\" id=\"${Prefix}Minute\" size=\"2\" maxlength=\"2\" "
3659                . "title=\""
3660                . $Self->{LanguageObject}->Translate('Minutes')
3661                . "\" value=\""
3662                . sprintf(
3663                "%02d",
3664                (
3665                    defined( $Param{ $Prefix . 'Minute' } )
3666                    ? int( $Param{ $Prefix . 'Minute' } )
3667                    : $m
3668                )
3669                ) . "\" "
3670                . ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
3671        }
3672    }
3673
3674    # Get first day of the week
3675    my $WeekDayStart = $ConfigObject->Get('CalendarWeekDayStart');
3676    if ( $Param{Calendar} ) {
3677        if ( $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} . "Name" ) ) {
3678            $WeekDayStart = $ConfigObject->Get( "CalendarWeekDayStart::Calendar" . $Param{Calendar} );
3679        }
3680    }
3681    if ( !defined $WeekDayStart ) {
3682        $WeekDayStart = 1;
3683    }
3684
3685    my $Output;
3686
3687    # optional checkbox
3688    if ($Optional) {
3689        my $Checked = '';
3690        if ($Used) {
3691            $Checked = ' checked="checked"';
3692        }
3693        $Output .= "<input type=\"checkbox\" name=\""
3694            . $Prefix
3695            . "Used\" id=\"" . $Prefix . "Used\" value=\"1\""
3696            . $Checked
3697            . " class=\"$Class\""
3698            . " title=\""
3699            . $Self->{LanguageObject}->Translate('Check to activate this date')
3700            . "\" "
3701            . ( $Param{Disabled} ? 'disabled="disabled"' : '' )
3702            . "/>&nbsp;";
3703    }
3704
3705    # remove 'Second' because it is never used and bug #9441
3706    delete $Param{ $Prefix . 'Second' };
3707
3708    # date format
3709    $Output .= $Self->{LanguageObject}->Time(
3710        Action => 'Return',
3711        Format => 'DateInputFormat',
3712        Mode   => 'NotNumeric',
3713        %Param,
3714    );
3715
3716    # prepare datepicker for specific calendar
3717    my $VacationDays = '';
3718    if ( $Param{Calendar} ) {
3719        $VacationDays = $Self->DatepickerGetVacationDays(
3720            Calendar => $Param{Calendar},
3721        );
3722    }
3723    my $VacationDaysJSON = $Self->JSONEncode(
3724        Data => $VacationDays,
3725    );
3726
3727    # Add Datepicker JS to output.
3728    my $DatepickerJS = '
3729    Core.UI.Datepicker.Init({
3730        Day: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Day"),
3731        Month: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Month"),
3732        Year: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Year"),
3733        Hour: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Hour"),
3734        Minute: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Minute"),
3735        VacationDays: ' . $VacationDaysJSON . ',
3736        DateInFuture: ' .    ( $ValidateDateInFuture    ? 'true' : 'false' ) . ',
3737        DateNotInFuture: ' . ( $ValidateDateNotInFuture ? 'true' : 'false' ) . ',
3738        WeekDayStart: ' . $WeekDayStart . '
3739    });';
3740
3741    $Self->AddJSOnDocumentComplete( Code => $DatepickerJS );
3742    $Self->{HasDatepicker} = 1;    # Call some Datepicker init code.
3743
3744    return $Output;
3745}
3746
3747=head2 HumanReadableDataSize()
3748
3749Produces human readable data size.
3750
3751    my $SizeStr = $LayoutObject->HumanReadableDataSize(
3752        Size => 123,  # size in bytes
3753    );
3754
3755Returns
3756
3757    $SizeStr = '123 B';         # example with decimal point: 123.4 MB
3758
3759=cut
3760
3761sub HumanReadableDataSize {
3762    my ( $Self, %Param ) = @_;
3763
3764    # Use simple string concatenation to format real number. "sprintf" uses dot (.) as decimal separator unless
3765    #   locale and POSIX (LC_NUMERIC) is used. Even in this case, you are not allowed to use custom separator
3766    #   (as defined in language files).
3767
3768    my $FormatSize = sub {
3769        my ($Number) = @_;
3770
3771        my $ReadableSize;
3772
3773        if ( IsInteger($Number) ) {
3774            $ReadableSize = $Number;
3775        }
3776        else {
3777
3778            # Get integer and decimal parts.
3779            my ( $Integer, $Float ) = split( m{\.}, sprintf( "%.1f", $Number ) );
3780
3781            my $Separator = $Self->{LanguageObject}->{DecimalSeparator} || '.';
3782
3783            # Format size with provided decimal separator.
3784            $ReadableSize = $Integer . $Separator . $Float;
3785        }
3786
3787        return $ReadableSize;
3788    };
3789
3790    if ( !defined( $Param{Size} ) ) {
3791        $Kernel::OM->Get('Kernel::System::Log')->Log(
3792            Priority => 'error',
3793            Message  => 'Need Size!',
3794        );
3795        return;
3796    }
3797
3798    if ( !IsInteger( $Param{Size} ) ) {
3799        $Kernel::OM->Get('Kernel::System::Log')->Log(
3800            Priority => 'error',
3801            Message  => 'Size must be integer!',
3802        );
3803        return;
3804    }
3805
3806    # Use convention described on https://en.wikipedia.org/wiki/File_size
3807    my ( $SizeStr, $ReadableSize );
3808
3809    if ( $Param{Size} >= ( 1024**4 ) ) {
3810
3811        $ReadableSize = $FormatSize->( $Param{Size} / ( 1024**4 ) );
3812        $SizeStr      = $Self->{LanguageObject}->Translate( '%s TB', $ReadableSize );
3813    }
3814    elsif ( $Param{Size} >= ( 1024**3 ) ) {
3815
3816        $ReadableSize = $FormatSize->( $Param{Size} / ( 1024**3 ) );
3817        $SizeStr      = $Self->{LanguageObject}->Translate( '%s GB', $ReadableSize );
3818    }
3819    elsif ( $Param{Size} >= ( 1024**2 ) ) {
3820
3821        $ReadableSize = $FormatSize->( $Param{Size} / ( 1024**2 ) );
3822        $SizeStr      = $Self->{LanguageObject}->Translate( '%s MB', $ReadableSize );
3823    }
3824    elsif ( $Param{Size} >= 1024 ) {
3825
3826        $ReadableSize = $FormatSize->( $Param{Size} / 1024 );
3827        $SizeStr      = $Self->{LanguageObject}->Translate( '%s KB', $ReadableSize );
3828    }
3829    else {
3830        $SizeStr = $Self->{LanguageObject}->Translate( '%s B', $Param{Size} );
3831    }
3832
3833    return $SizeStr;
3834}
3835
3836sub CustomerLogin {
3837    my ( $Self, %Param ) = @_;
3838
3839    my $Output = '';
3840    $Param{TitleArea} = $Self->{LanguageObject}->Translate('Login') . ' - ';
3841
3842    # set Action parameter for the loader
3843    $Self->{Action}        = 'CustomerLogin';
3844    $Param{IsLoginPage}    = 1;
3845    $Param{'XLoginHeader'} = 1;
3846
3847    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
3848
3849    if ( $ConfigObject->Get('SessionUseCookie') ) {
3850
3851        # always set a cookie, so that at the time the user submits
3852        # the password, we know already if the browser supports cookies.
3853        # ( the session cookie isn't available at that time ).
3854        my $CookieSecureAttribute = 0;
3855        if ( $ConfigObject->Get('HttpType') eq 'https' ) {
3856
3857            # Restrict Cookie to HTTPS if it is used.
3858            $CookieSecureAttribute = 1;
3859        }
3860        $Self->{SetCookies}->{OTRSBrowserHasCookie} = $Kernel::OM->Get('Kernel::System::Web::Request')->SetCookie(
3861            Key      => 'OTRSBrowserHasCookie',
3862            Value    => 1,
3863            Expires  => '+1y',
3864            Path     => $ConfigObject->Get('ScriptAlias'),
3865            Secure   => $CookieSecureAttribute,
3866            HttpOnly => 1,
3867        );
3868    }
3869
3870    # add cookies if exists
3871    if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
3872        for ( sort keys %{ $Self->{SetCookies} } ) {
3873            $Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
3874        }
3875    }
3876
3877    # check if message should be shown
3878    if ( $Param{Message} ) {
3879        $Self->Block(
3880            Name => 'Message',
3881            Data => \%Param,
3882        );
3883    }
3884
3885    # Generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
3886    $Self->LoaderCreateCustomerCSSCalls();
3887    $Self->LoaderCreateCustomerJSCalls();
3888    $Self->LoaderCreateJavaScriptTranslationData();
3889    $Self->LoaderCreateJavaScriptTemplateData();
3890
3891    my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
3892    $Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
3893    $Param{OTRSSTORMIsInstalled}    = $OTRSBusinessObject->OTRSSTORMIsInstalled();
3894    $Param{OTRSCONTROLIsInstalled}  = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
3895
3896    $Self->AddJSData(
3897        Key   => 'Baselink',
3898        Value => $Self->{Baselink},
3899    );
3900
3901    # Add header logo, if configured
3902    if ( defined $ConfigObject->Get('CustomerLogo') ) {
3903        my %CustomerLogo = %{ $ConfigObject->Get('CustomerLogo') };
3904        my %Data;
3905
3906        for my $CSSStatement ( sort keys %CustomerLogo ) {
3907            if ( $CSSStatement eq 'URL' ) {
3908                my $WebPath = '';
3909                if ( $CustomerLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
3910                    $WebPath = $ConfigObject->Get('Frontend::WebPath');
3911                }
3912                $Data{'URL'} = 'url(' . $WebPath . $CustomerLogo{$CSSStatement} . ')';
3913            }
3914            else {
3915                $Data{$CSSStatement} = $CustomerLogo{$CSSStatement};
3916            }
3917        }
3918
3919        $Self->Block(
3920            Name => 'HeaderLogoCSS',
3921            Data => \%Data,
3922        );
3923
3924        $Self->Block(
3925            Name => 'HeaderLogo',
3926        );
3927    }
3928
3929    # get system maintenance object
3930    my $SystemMaintenanceObject = $Kernel::OM->Get('Kernel::System::SystemMaintenance');
3931
3932    my $ActiveMaintenance = $SystemMaintenanceObject->SystemMaintenanceIsActive();
3933
3934    # check if system maintenance is active
3935    if ($ActiveMaintenance) {
3936        my $SystemMaintenanceData = $SystemMaintenanceObject->SystemMaintenanceGet(
3937            ID     => $ActiveMaintenance,
3938            UserID => 1,
3939        );
3940        if ( $SystemMaintenanceData->{ShowLoginMessage} ) {
3941            my $LoginMessage =
3942                $SystemMaintenanceData->{LoginMessage}
3943                || $ConfigObject->Get('SystemMaintenance::IsActiveDefaultLoginMessage')
3944                || "System maintenance is active, not possible to perform a login!";
3945
3946            $Self->Block(
3947                Name => 'SystemMaintenance',
3948                Data => {
3949                    LoginMessage => $LoginMessage,
3950                },
3951            );
3952        }
3953    }
3954
3955    # show prelogin block, if in prelogin mode (e.g. SSO login)
3956    if ( defined $Param{'Mode'} && $Param{'Mode'} eq 'PreLogin' ) {
3957        $Self->Block(
3958            Name => 'PreLogin',
3959            Data => \%Param,
3960        );
3961    }
3962
3963    # if not in PreLogin mode, show normal login form
3964    else {
3965
3966        my $DisableLoginAutocomplete = $ConfigObject->Get('DisableLoginAutocomplete');
3967        $Param{UserNameAutocomplete} = $DisableLoginAutocomplete ? 'off' : 'username';
3968        $Param{PasswordAutocomplete} = $DisableLoginAutocomplete ? 'off' : 'current-password';
3969
3970        $Self->Block(
3971            Name => 'LoginBox',
3972            Data => \%Param,
3973        );
3974
3975        # show 2 factor password input if we have at least one backend enabled
3976        COUNT:
3977        for my $Count ( '', 1 .. 10 ) {
3978            next COUNT if !$ConfigObject->Get("Customer::AuthTwoFactorModule$Count");
3979
3980            $Self->Block(
3981                Name => 'AuthTwoFactor',
3982                Data => \%Param,
3983            );
3984            last COUNT;
3985        }
3986
3987        # get lost password output
3988        if (
3989            $ConfigObject->Get('CustomerPanelLostPassword')
3990            && $ConfigObject->Get('Customer::AuthModule') eq
3991            'Kernel::System::CustomerAuth::DB'
3992            )
3993        {
3994            $Self->Block(
3995                Name => 'LostPasswordLink',
3996                Data => \%Param,
3997            );
3998            $Self->Block(
3999                Name => 'LostPassword',
4000                Data => \%Param,
4001            );
4002        }
4003
4004        # get create account output
4005        if (
4006            $ConfigObject->Get('CustomerPanelCreateAccount')
4007            && $ConfigObject->Get('Customer::AuthModule') eq
4008            'Kernel::System::CustomerAuth::DB'
4009            )
4010
4011        {
4012            $Self->Block(
4013                Name => 'CreateAccountLink',
4014                Data => \%Param,
4015            );
4016            $Self->Block(
4017                Name => 'CreateAccount',
4018                Data => \%Param,
4019            );
4020        }
4021    }
4022
4023    # send data to JS
4024    $Self->AddJSData(
4025        Key   => 'LoginFailed',
4026        Value => $Param{LoginFailed},
4027    );
4028
4029    # Display footer links.
4030    my $FooterLinks = $ConfigObject->Get('PublicFrontend::FooterLinks');
4031    if ( IsHashRefWithData($FooterLinks) ) {
4032
4033        my @FooterLinks;
4034
4035        for my $Link ( sort keys %{$FooterLinks} ) {
4036
4037            push @FooterLinks, {
4038                Description => $FooterLinks->{$Link},
4039                Target      => $Link,
4040            };
4041        }
4042
4043        $Param{FooterLinks} = \@FooterLinks;
4044    }
4045
4046    # create & return output
4047    $Output .= $Self->Output(
4048        TemplateFile => 'CustomerLogin',
4049        Data         => \%Param,
4050    );
4051
4052    # remove the version tag from the header if configured
4053    $Self->_DisableBannerCheck( OutputRef => \$Output );
4054
4055    return $Output;
4056}
4057
4058sub CustomerHeader {
4059    my ( $Self, %Param ) = @_;
4060
4061    my $Type = $Param{Type} || '';
4062
4063    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4064
4065    # add cookies if exists
4066    my $Output = '';
4067    if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
4068        for ( sort keys %{ $Self->{SetCookies} } ) {
4069            $Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
4070        }
4071    }
4072
4073    my $File = $Param{Filename} || $Self->{Action} || 'unknown';
4074
4075    # set file name for "save page as"
4076    $Param{ContentDisposition} = "filename=\"$File.html\"";
4077
4078    # area and title
4079    if (
4080        !$Param{Area}
4081        && $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }
4082        )
4083    {
4084        $Param{Area} = $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }
4085            ->{NavBarName} || '';
4086    }
4087    if (
4088        !$Param{Title}
4089        && $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }
4090        )
4091    {
4092        $Param{Title} = $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }->{Title}
4093            || '';
4094    }
4095    if (
4096        !$Param{Area}
4097        && $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }
4098        )
4099    {
4100        $Param{Area} = $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }
4101            ->{NavBarName} || '';
4102    }
4103    if (
4104        !$Param{Title}
4105        && $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }
4106        )
4107    {
4108        $Param{Title} = $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }->{Title}
4109            || '';
4110    }
4111    for my $Word (qw(Value Title Area)) {
4112        if ( $Param{$Word} ) {
4113            $Param{TitleArea} .= $Self->{LanguageObject}->Translate( $Param{$Word} ) . ' - ';
4114        }
4115    }
4116
4117    my $Frontend;
4118    if ( $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} } ) {
4119        $Frontend = 'Customer';
4120    }
4121    else {
4122        $Frontend = 'Public';
4123    }
4124
4125    # run header meta modules for customer and public frontends
4126    my $HeaderMetaModule = $ConfigObject->Get( $Frontend . 'Frontend::HeaderMetaModule' );
4127    if ( ref $HeaderMetaModule eq 'HASH' ) {
4128        my %Jobs = %{$HeaderMetaModule};
4129
4130        my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
4131
4132        MODULE:
4133        for my $Job ( sort keys %Jobs ) {
4134
4135            # load and run module
4136            next MODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
4137            my $Object = $Jobs{$Job}->{Module}->new( %{$Self}, LayoutObject => $Self );
4138            next MODULE if !$Object;
4139            $Object->Run( %Param, Config => $Jobs{$Job} );
4140        }
4141    }
4142
4143    # set rtl if needed
4144    if ( $Self->{TextDirection} && $Self->{TextDirection} eq 'rtl' ) {
4145        $Param{BodyClass} = 'RTL';
4146    }
4147    elsif ( $ConfigObject->Get('Frontend::DebugMode') ) {
4148        $Self->Block(
4149            Name => 'DebugRTLButton',
4150        );
4151    }
4152
4153    # Add header logo, if configured
4154    if ( defined $ConfigObject->Get('CustomerLogo') ) {
4155        my %CustomerLogo = %{ $ConfigObject->Get('CustomerLogo') };
4156        my %Data;
4157
4158        for my $CSSStatement ( sort keys %CustomerLogo ) {
4159            if ( $CSSStatement eq 'URL' ) {
4160                my $WebPath = '';
4161                if ( $CustomerLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
4162                    $WebPath = $ConfigObject->Get('Frontend::WebPath');
4163                }
4164                $Data{'URL'} = 'url(' . $WebPath . $CustomerLogo{$CSSStatement} . ')';
4165            }
4166            else {
4167                $Data{$CSSStatement} = $CustomerLogo{$CSSStatement};
4168            }
4169        }
4170
4171        $Self->Block(
4172            Name => 'HeaderLogoCSS',
4173            Data => \%Data,
4174        );
4175
4176        $Self->Block(
4177            Name => 'HeaderLogo',
4178        );
4179    }
4180
4181    # Generate the minified CSS and JavaScript files
4182    # and the tags referencing them (see LayoutLoader)
4183    $Self->LoaderCreateCustomerCSSCalls();
4184
4185    # create & return output
4186    $Output .= $Self->Output(
4187        TemplateFile => "CustomerHeader$Type",
4188        Data         => \%Param,
4189    );
4190
4191    # remove the version tag from the header if configured
4192    $Self->_DisableBannerCheck( OutputRef => \$Output );
4193
4194    return $Output;
4195}
4196
4197sub CustomerFooter {
4198    my ( $Self, %Param ) = @_;
4199
4200    my $Type          = $Param{Type}           || '';
4201    my $HasDatepicker = $Self->{HasDatepicker} || 0;
4202
4203    # Generate the minified CSS and JavaScript files
4204    # and the tags referencing them (see LayoutLoader)
4205    $Self->LoaderCreateCustomerJSCalls();
4206    $Self->LoaderCreateJavaScriptTranslationData();
4207    $Self->LoaderCreateJavaScriptTemplateData();
4208
4209    # get datepicker data, if needed in module
4210    if ($HasDatepicker) {
4211        my $VacationDays  = $Self->DatepickerGetVacationDays();
4212        my $TextDirection = $Self->{LanguageObject}->{TextDirection} || '';
4213
4214        # send data to JS
4215        $Self->AddJSData(
4216            Key   => 'Datepicker',
4217            Value => {
4218                VacationDays => $VacationDays,
4219                IsRTL        => ( $TextDirection eq 'rtl' ) ? 1 : 0,
4220            },
4221        );
4222    }
4223
4224    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4225
4226    # Check if video chat is enabled.
4227    if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::VideoChat', Silent => 1 ) ) {
4228        $Param{VideoChatEnabled} = $Kernel::OM->Get('Kernel::System::VideoChat')->IsEnabled()
4229            || $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'UnitTestMode' ) // 0;
4230    }
4231
4232    # Check if customer user has permission for chat.
4233    my $CustomerChatPermission;
4234    if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Chat', Silent => 1 ) ) {
4235
4236        my $CustomerChatConfig = $ConfigObject->Get('CustomerFrontend::Module')->{'CustomerChat'} || {};
4237
4238        if (
4239            $Kernel::OM->Get('Kernel::Config')->Get('CustomerGroupSupport')
4240            && (
4241                IsArrayRefWithData( $CustomerChatConfig->{GroupRo} )
4242                || IsArrayRefWithData( $CustomerChatConfig->{Group} )
4243            )
4244            )
4245        {
4246
4247            my $CustomerGroupObject = $Kernel::OM->Get('Kernel::System::CustomerGroup');
4248
4249            GROUP:
4250            for my $GroupName ( @{ $CustomerChatConfig->{GroupRo} }, @{ $CustomerChatConfig->{Group} } ) {
4251                $CustomerChatPermission = $CustomerGroupObject->PermissionCheck(
4252                    UserID    => $Self->{UserID},
4253                    GroupName => $GroupName,
4254                    Type      => 'ro',
4255                );
4256                last GROUP if $CustomerChatPermission;
4257            }
4258        }
4259        else {
4260            $CustomerChatPermission = 1;
4261        }
4262    }
4263
4264    # don't check for business package if the database was not yet configured (in the installer)
4265    if ( $ConfigObject->Get('SecureMode') ) {
4266        my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
4267        $Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
4268        $Param{OTRSSTORMIsInstalled}    = $OTRSBusinessObject->OTRSSTORMIsInstalled();
4269        $Param{OTRSCONTROLIsInstalled}  = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
4270    }
4271
4272    # AutoComplete-Config
4273    my $AutocompleteConfig = $ConfigObject->Get('AutoComplete::Customer');
4274
4275    for my $ConfigElement ( sort keys %{$AutocompleteConfig} ) {
4276        $AutocompleteConfig->{$ConfigElement}->{ButtonText}
4277            = $Self->{LanguageObject}->Translate( $AutocompleteConfig->{$ConfigElement}{ButtonText} );
4278    }
4279
4280    # add JS data
4281    my %JSConfig = (
4282        Baselink                 => $Self->{Baselink},
4283        CGIHandle                => $Self->{CGIHandle},
4284        WebPath                  => $ConfigObject->Get('Frontend::WebPath'),
4285        Action                   => $Self->{Action},
4286        Subaction                => $Self->{Subaction},
4287        SessionIDCookie          => $Self->{SessionIDCookie},
4288        SessionName              => $Self->{SessionName},
4289        SessionID                => $Self->{SessionID},
4290        SessionUseCookie         => $ConfigObject->Get('SessionUseCookie'),
4291        ChallengeToken           => $Self->{UserChallengeToken},
4292        CustomerPanelSessionName => $ConfigObject->Get('CustomerPanelSessionName'),
4293        UserLanguage             => $Self->{UserLanguage},
4294        CheckEmailAddresses      => $ConfigObject->Get('CheckEmailAddresses'),
4295        OTRSBusinessIsInstalled  => $Param{OTRSBusinessIsInstalled},
4296        OTRSSTORMIsInstalled     => $Param{OTRSSTORMIsInstalled},
4297        OTRSCONTROLIsInstalled   => $Param{OTRSCONTROLIsInstalled},
4298        InputFieldsActivated     => $ConfigObject->Get('ModernizeCustomerFormFields'),
4299        Autocomplete             => $AutocompleteConfig,
4300        VideoChatEnabled         => $Param{VideoChatEnabled},
4301        WebMaxFileUpload         => $ConfigObject->Get('WebMaxFileUpload'),
4302        CustomerChatPermission   => $CustomerChatPermission,
4303    );
4304
4305    for my $Config ( sort keys %JSConfig ) {
4306        $Self->AddJSData(
4307            Key   => $Config,
4308            Value => $JSConfig{$Config},
4309        );
4310    }
4311
4312    # Display footer links.
4313    my $FooterLinks = $ConfigObject->Get('PublicFrontend::FooterLinks');
4314    if ( IsHashRefWithData($FooterLinks) ) {
4315
4316        my @FooterLinks;
4317
4318        for my $Link ( sort keys %{$FooterLinks} ) {
4319
4320            push @FooterLinks, {
4321                Description => $FooterLinks->{$Link},
4322                Target      => $Link,
4323            };
4324        }
4325
4326        $Param{FooterLinks} = \@FooterLinks;
4327    }
4328
4329    # create & return output
4330    return $Self->Output(
4331        TemplateFile => "CustomerFooter$Type",
4332        Data         => \%Param,
4333    );
4334}
4335
4336sub CustomerFatalError {
4337    my ( $Self, %Param ) = @_;
4338
4339    # Prevent endless recursion in case of problems with Template engine.
4340    return if ( $Self->{InFatalError}++ );
4341
4342    if ( $Param{Message} ) {
4343        $Kernel::OM->Get('Kernel::System::Log')->Log(
4344            Caller   => 1,
4345            Priority => 'error',
4346            Message  => $Param{Message},
4347        );
4348    }
4349    my $Output = $Self->CustomerHeader(
4350        Area  => 'Frontend',
4351        Title => 'Fatal Error'
4352    );
4353    $Output .= $Self->CustomerError(%Param);
4354    $Output .= $Self->CustomerFooter();
4355    $Self->Print( Output => \$Output );
4356    exit;
4357}
4358
4359sub CustomerNavigationBar {
4360    my ( $Self, %Param ) = @_;
4361
4362    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
4363
4364    # create menu items
4365    my %NavBarModule;
4366    my $FrontendModule   = $ConfigObject->Get('CustomerFrontend::Module');
4367    my $NavigationConfig = $ConfigObject->Get('CustomerFrontend::Navigation');
4368
4369    my $GroupObject = $Kernel::OM->Get('Kernel::System::CustomerGroup');
4370
4371    MODULE:
4372    for my $Module ( sort keys %{$NavigationConfig} ) {
4373
4374        # Skip if module is disabled in frontend registration.
4375        next MODULE if !IsHashRefWithData( $FrontendModule->{$Module} );
4376
4377        # Top-level frontend navigation configuration should always be a hash.
4378        next MODULE if !IsHashRefWithData( $NavigationConfig->{$Module} );
4379
4380        my @ModuleNavigationConfigs;
4381
4382        # Go through all defined navigation configurations for the module and sort them by the key (00#-Module).
4383        NAVIGATION_CONFIG:
4384        for my $Key ( sort keys %{ $NavigationConfig->{$Module} || {} } ) {
4385            next NAVIGATION_CONFIG if $Key !~ m{^\d+};
4386
4387            # FIXME: Support both old (HASH) and new (ARRAY of HASH) navigation configurations, for reasons of backwards
4388            #   compatibility. Once we are sure everything has been migrated correctly, support for HASH-only
4389            #   configuration can be dropped in future major release.
4390            if ( IsHashRefWithData( $NavigationConfig->{$Module}->{$Key} ) ) {
4391                push @ModuleNavigationConfigs, $NavigationConfig->{$Module}->{$Key};
4392            }
4393            elsif ( IsArrayRefWithData( $NavigationConfig->{$Module}->{$Key} ) ) {
4394                push @ModuleNavigationConfigs, @{ $NavigationConfig->{$Module}->{$Key} };
4395            }
4396
4397            # Skip incompatible configuration.
4398            else {
4399                next NAVIGATION_CONFIG;
4400            }
4401        }
4402
4403        ITEM:
4404        for my $Item (@ModuleNavigationConfigs) {
4405            next ITEM if !$Item->{NavBar};
4406
4407            my $InheritPermissions = 0;
4408
4409            # Inherit permissions from frontend registration if no permissions were defined for the navigation entry.
4410            if ( !$Item->{GroupRo} && !$Item->{Group} ) {
4411                if ( $FrontendModule->{GroupRo} ) {
4412                    $Item->{GroupRo} = $FrontendModule->{GroupRo};
4413                }
4414                if ( $FrontendModule->{Group} ) {
4415                    $Item->{Group} = $FrontendModule->{Group};
4416                }
4417                $InheritPermissions = 1;
4418            }
4419
4420            my $Shown = 0;
4421
4422            PERMISSION:
4423            for my $Permission (qw(GroupRo Group)) {
4424
4425                # No access restriction.
4426                if (
4427                    ref $Item->{GroupRo} eq 'ARRAY'
4428                    && !scalar @{ $Item->{GroupRo} }
4429                    && ref $Item->{Group} eq 'ARRAY'
4430                    && !scalar @{ $Item->{Group} }
4431                    )
4432                {
4433                    $Shown = 1;
4434                    last PERMISSION;
4435                }
4436
4437                # Array access restriction.
4438                elsif ( $Item->{$Permission} && ref $Item->{$Permission} eq 'ARRAY' ) {
4439                    for my $Group ( @{ $Item->{$Permission} } ) {
4440                        my $HasPermission = $GroupObject->PermissionCheck(
4441                            UserID    => $Self->{UserID},
4442                            GroupName => $Group,
4443                            Type      => $Permission eq 'GroupRo' ? 'ro' : 'rw',
4444                        );
4445                        if ($HasPermission) {
4446                            $Shown = 1;
4447                            last PERMISSION;
4448                        }
4449                    }
4450                }
4451            }
4452
4453            # If we passed the initial permission check and didn't inherit permissions from the module registration,
4454            #   make sure to also check access to the module, since navigation item might be out of sync.
4455            if ( $Shown && !$InheritPermissions ) {
4456                my $ModulePermission;
4457
4458                PERMISSION:
4459                for my $Permission (qw(GroupRo Group)) {
4460
4461                    # No access restriction.
4462                    if (
4463                        ref $FrontendModule->{$Module}->{GroupRo} eq 'ARRAY'
4464                        && !scalar @{ $FrontendModule->{$Module}->{GroupRo} }
4465                        && ref $FrontendModule->{$Module}->{Group} eq 'ARRAY'
4466                        && !scalar @{ $FrontendModule->{$Module}->{Group} }
4467                        )
4468                    {
4469
4470                        $ModulePermission = 1;
4471                        last PERMISSION;
4472                    }
4473
4474                    # Array access restriction.
4475                    elsif (
4476                        $FrontendModule->{$Module}->{$Permission}
4477                        && ref $FrontendModule->{$Module}->{$Permission} eq 'ARRAY'
4478                        )
4479                    {
4480                        GROUP:
4481                        for my $Group ( @{ $FrontendModule->{$Module}->{$Permission} } ) {
4482                            next GROUP if !$Group;
4483                            my $HasPermission = $GroupObject->PermissionCheck(
4484                                UserID    => $Self->{UserID},
4485                                GroupName => $Group,
4486                                Type      => $Permission eq 'GroupRo' ? 'ro' : 'rw',
4487
4488                            );
4489                            if ($HasPermission) {
4490                                $ModulePermission = 1;
4491                                last PERMISSION;
4492                            }
4493                        }
4494                    }
4495                }
4496
4497                # Hide item if no permission was granted to access the module.
4498                if ( !$ModulePermission ) {
4499                    $Shown = 0;
4500                }
4501            }
4502
4503            next ITEM if !$Shown;
4504
4505            # set prio of item
4506            my $Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
4507            COUNT:
4508            for ( 1 .. 51 ) {
4509                last COUNT if !$NavBarModule{$Key};
4510
4511                $Item->{Prio}++;
4512                $Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
4513            }
4514
4515            # Show as main menu.
4516            if ( $Item->{Type} eq 'Menu' ) {
4517                $NavBarModule{$Key} = $Item;
4518            }
4519
4520            # show as sub of main menu
4521            elsif ( $Item->{Type} eq 'Submenu' ) {
4522                $NavBarModule{Sub}->{ $Item->{NavBar} }->{$Key} = $Item;
4523            }
4524        }
4525    }
4526
4527    my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
4528
4529    # run menu item modules
4530    if ( ref $ConfigObject->Get('CustomerFrontend::NavBarModule') eq 'HASH' ) {
4531        my %Jobs = %{ $ConfigObject->Get('CustomerFrontend::NavBarModule') };
4532        for my $Job ( sort keys %Jobs ) {
4533
4534            # load module
4535            if ( !$MainObject->Require( $Jobs{$Job}->{Module} ) ) {
4536                $Self->FatalError();
4537            }
4538            my $Object = $Jobs{$Job}->{Module}->new(
4539                %{$Self},
4540                LayoutObject => $Self,
4541                UserID       => $Self->{UserID},
4542                Debug        => $Self->{Debug},
4543            );
4544
4545            # run module
4546            %NavBarModule = (
4547                %NavBarModule,
4548                $Object->Run(
4549                    %Param,
4550                    Config       => $Jobs{$Job},
4551                    NavBarModule => \%NavBarModule || {},
4552                ),
4553            );
4554        }
4555    }
4556
4557    my $Total   = keys %NavBarModule;
4558    my $Counter = 0;
4559
4560    if ( $NavBarModule{Sub} ) {
4561        $Total = int($Total) - 1;
4562    }
4563
4564    # Only highlight the first matched navigation entry. If there are several entries
4565    #   with the same Action and Subaction, it cannot be determined which one was used.
4566    #   Therefore we just highlight the first one.
4567    my $SelectedFlag;
4568
4569    ITEM:
4570    for my $Item ( sort keys %NavBarModule ) {
4571        next ITEM if !%{ $NavBarModule{$Item} };
4572        next ITEM if $Item eq 'Sub';
4573        $Counter++;
4574        my $Sub;
4575        if ( $NavBarModule{$Item}->{NavBar} ) {
4576            $Sub = $NavBarModule{Sub}->{ $NavBarModule{$Item}->{NavBar} };
4577        }
4578
4579        # highlight active link
4580        $NavBarModule{$Item}->{Class} = '';
4581        if ( $NavBarModule{$Item}->{Link} ) {
4582            if (
4583                !$SelectedFlag
4584                && $NavBarModule{$Item}->{Link} =~ /Action=$Self->{Action}/
4585                && $NavBarModule{$Item}->{Link} =~ /$Self->{Subaction}/       # Subaction can be empty
4586                )
4587            {
4588                $NavBarModule{$Item}->{Class} .= ' Selected';
4589                $SelectedFlag = 1;
4590            }
4591        }
4592        if ( $Counter == $Total ) {
4593            $NavBarModule{$Item}->{Class} .= ' Last';
4594        }
4595        $Self->Block(
4596            Name => $NavBarModule{$Item}->{Block} || 'Item',
4597            Data => $NavBarModule{$Item},
4598        );
4599
4600        # show sub menu
4601        next ITEM if !$Sub;
4602        $Self->Block(
4603            Name => 'ItemAreaSub',
4604            Data => $Item,
4605        );
4606        for my $Key ( sort keys %{$Sub} ) {
4607            my $ItemSub = $Sub->{$Key};
4608            $ItemSub->{NameForID} = $ItemSub->{Name};
4609            $ItemSub->{NameForID} =~ s/[ &;]//ig;
4610            $ItemSub->{NameTop} = $NavBarModule{$Item}->{NameForID};
4611
4612            # check if we must mark the parent element as selected
4613            if ( $ItemSub->{Link} ) {
4614                if (
4615                    $ItemSub->{Link} =~ /Action=$Self->{Action}/
4616                    && $ItemSub->{Link} =~ /$Self->{Subaction}/    # Subaction can be empty
4617                    )
4618                {
4619                    $NavBarModule{$Item}->{Class} .= ' Selected';
4620                    $ItemSub->{Class} .= ' SubSelected';
4621                    $SelectedFlag = 1;
4622                }
4623            }
4624
4625            $Self->Block(
4626                Name => 'ItemAreaSubItem',
4627                Data => {
4628                    %$ItemSub,
4629                    AccessKeyReference => $ItemSub->{AccessKey} ? " ($ItemSub->{AccessKey})" : '',
4630                },
4631            );
4632        }
4633    }
4634
4635    # run notification modules
4636    my $FrontendNotifyModuleConfig = $ConfigObject->Get('CustomerFrontend::NotifyModule');
4637    if ( ref $FrontendNotifyModuleConfig eq 'HASH' ) {
4638        my %Jobs = %{$FrontendNotifyModuleConfig};
4639
4640        NOTIFICATIONMODULE:
4641        for my $Job ( sort keys %Jobs ) {
4642
4643            # load module
4644            next NOTIFICATIONMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
4645            my $Object = $Jobs{$Job}->{Module}->new(
4646                %{$Self},
4647                LayoutObject => $Self,
4648            );
4649            next NOTIFICATIONMODULE if !$Object;
4650
4651            # run module
4652            $Param{Notification} .= $Object->Run( %Param, Config => $Jobs{$Job} );
4653        }
4654    }
4655
4656    # create the customer user login info (usually at the right side of the navigation bar)
4657    if ( !$Self->{UserLoginIdentifier} ) {
4658        $Param{UserLoginIdentifier} = $Self->{UserEmail} ne $Self->{UserCustomerID}
4659            ?
4660            "( $Self->{UserEmail} / $Self->{UserCustomerID} )"
4661            : $Self->{UserEmail};
4662    }
4663    else {
4664        $Param{UserLoginIdentifier} = $Self->{UserLoginIdentifier};
4665    }
4666
4667    # only on valid session
4668    if ( $Self->{UserID} ) {
4669
4670        # show logout button (if registered)
4671        if ( $FrontendModule->{Logout} ) {
4672            $Self->Block(
4673                Name => 'Logout',
4674                Data => \%Param,
4675            );
4676        }
4677
4678        # show preferences button (if registered)
4679        if ( $FrontendModule->{CustomerPreferences} ) {
4680            if ( $Self->{Action} eq 'CustomerPreferences' ) {
4681                $Param{Class} = 'Selected';
4682            }
4683            $Self->Block(
4684                Name => 'Preferences',
4685                Data => \%Param,
4686            );
4687        }
4688
4689        # Show open chat requests (if chat engine is active).
4690        if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Chat', Silent => 1 ) ) {
4691            if ( $ConfigObject->Get('ChatEngine::Active') ) {
4692                my $ChatObject = $Kernel::OM->Get('Kernel::System::Chat');
4693                my $Chats      = $ChatObject->ChatList(
4694                    Status        => 'request',
4695                    TargetType    => 'Customer',
4696                    ChatterID     => $Self->{UserID},
4697                    ChatterType   => 'Customer',
4698                    ChatterActive => 0,
4699                );
4700
4701                my $Count = scalar $Chats;
4702
4703                $Self->Block(
4704                    Name => 'ChatRequests',
4705                    Data => {
4706                        Count => $Count,
4707                        Class => ($Count) ? '' : 'Hidden',
4708                    },
4709                );
4710
4711                $Self->AddJSData(
4712                    Key   => 'ChatEngine::Active',
4713                    Value => $ConfigObject->Get('ChatEngine::Active')
4714                );
4715            }
4716        }
4717    }
4718
4719    # create & return output
4720    return $Self->Output(
4721        TemplateFile => 'CustomerNavigationBar',
4722        Data         => \%Param
4723    );
4724}
4725
4726sub CustomerError {
4727    my ( $Self, %Param ) = @_;
4728
4729    # get backend error messages
4730    for (qw(Message Traceback)) {
4731        $Param{ 'Backend' . $_ } = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
4732            Type => 'Error',
4733            What => $_
4734        ) || '';
4735    }
4736    if ( !$Param{BackendMessage} && !$Param{BackendTraceback} ) {
4737        $Kernel::OM->Get('Kernel::System::Log')->Log(
4738            Priority => 'error',
4739            Message  => $Param{Message} || '?',
4740        );
4741        for (qw(Message Traceback)) {
4742            $Param{ 'Backend' . $_ } = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
4743                Type => 'Error',
4744                What => $_
4745            ) || '';
4746        }
4747    }
4748
4749    if ( !$Param{Message} ) {
4750        $Param{Message} = $Param{BackendMessage};
4751    }
4752
4753    # create & return output
4754    return $Self->Output(
4755        TemplateFile => 'CustomerError',
4756        Data         => \%Param
4757    );
4758}
4759
4760sub CustomerErrorScreen {
4761    my ( $Self, %Param ) = @_;
4762
4763    my $Output = $Self->CustomerHeader( Title => 'Error' );
4764    $Output .= $Self->CustomerError(%Param);
4765    $Output .= $Self->CustomerFooter();
4766    return $Output;
4767}
4768
4769sub CustomerWarning {
4770    my ( $Self, %Param ) = @_;
4771
4772    # get backend error messages
4773    $Param{BackendMessage} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
4774        Type => 'Notice',
4775        What => 'Message',
4776        )
4777        || $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
4778        Type => 'Error',
4779        What => 'Message',
4780        ) || '';
4781
4782    if ( !$Param{Message} ) {
4783        $Param{Message} = $Param{BackendMessage};
4784    }
4785
4786    # create & return output
4787    return $Self->Output(
4788        TemplateFile => 'CustomerWarning',
4789        Data         => \%Param
4790    );
4791}
4792
4793sub CustomerNoPermission {
4794    my ( $Self, %Param ) = @_;
4795
4796    my $WithHeader = $Param{WithHeader} || 'yes';
4797    $Param{Message} ||= Translatable('No Permission!');
4798
4799    # create output
4800    my $Output;
4801    $Output = $Self->CustomerHeader( Title => Translatable('No Permission') ) if ( $WithHeader eq 'yes' );
4802    $Output .= $Self->Output(
4803        TemplateFile => 'NoPermission',
4804        Data         => \%Param
4805    );
4806    $Output .= $Self->CustomerFooter() if ( $WithHeader eq 'yes' );
4807
4808    # return output
4809    return $Output;
4810}
4811
4812=head2 Ascii2RichText()
4813
4814converts text to rich text
4815
4816    my $HTMLString = $LayoutObject->Ascii2RichText(
4817        String => $TextString,
4818    );
4819
4820=cut
4821
4822sub Ascii2RichText {
4823    my ( $Self, %Param ) = @_;
4824
4825    # check needed stuff
4826    for (qw(String)) {
4827        if ( !defined $Param{$_} ) {
4828            $Kernel::OM->Get('Kernel::System::Log')->Log(
4829                Priority => 'error',
4830                Message  => "Need $_!"
4831            );
4832            return;
4833        }
4834    }
4835
4836    # ascii 2 html
4837    $Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML(
4838        String => $Param{String},
4839    );
4840
4841    return $Param{String};
4842}
4843
4844=head2 RichText2Ascii()
4845
4846converts text to rich text
4847
4848    my $TextString = $LayoutObject->RichText2Ascii(
4849        String => $HTMLString,
4850    );
4851
4852=cut
4853
4854sub RichText2Ascii {
4855    my ( $Self, %Param ) = @_;
4856
4857    # check needed stuff
4858    for (qw(String)) {
4859        if ( !defined $Param{$_} ) {
4860            $Kernel::OM->Get('Kernel::System::Log')->Log(
4861                Priority => 'error',
4862                Message  => "Need $_!"
4863            );
4864            return;
4865        }
4866    }
4867
4868    # ascii 2 html
4869    $Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii(
4870        String => $Param{String},
4871    );
4872
4873    return $Param{String};
4874}
4875
4876=head2 RichTextDocumentComplete()
4877
48781) add html, body, ... tags to be a valid html document
48792) replace links of inline content e. g. images to <img src="cid:xxxx" />
4880
4881    $HTMLBody = $LayoutObject->RichTextDocumentComplete(
4882        String => $HTMLBody,
4883    );
4884
4885=cut
4886
4887sub RichTextDocumentComplete {
4888    my ( $Self, %Param ) = @_;
4889
4890    # check needed stuff
4891    for (qw(String)) {
4892        if ( !defined $Param{$_} ) {
4893            $Kernel::OM->Get('Kernel::System::Log')->Log(
4894                Priority => 'error',
4895                Message  => "Need $_!"
4896            );
4897            return;
4898        }
4899    }
4900
4901    # replace image link with content id for uploaded images
4902    my $StringRef = $Self->_RichTextReplaceLinkOfInlineContent(
4903        String => \$Param{String},
4904    );
4905
4906    # verify html document
4907    $Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentComplete(
4908        String  => ${$StringRef},
4909        Charset => $Self->{UserCharset},
4910    );
4911
4912    # do correct direction
4913    if ( $Self->{TextDirection} ) {
4914        $Param{String} =~ s/<body/<body dir="$Self->{TextDirection}"/i;
4915    }
4916
4917    # filter links in response
4918    $Param{String} = $Self->HTMLLinkQuote( String => $Param{String} );
4919
4920    return $Param{String};
4921}
4922
4923=begin Internal:
4924
4925=cut
4926
4927=head2 _RichTextReplaceLinkOfInlineContent()
4928
4929replace links of inline content e. g. images
4930
4931    $HTMLBodyStringRef = $LayoutObject->_RichTextReplaceLinkOfInlineContent(
4932        String => $HTMLBodyStringRef,
4933    );
4934
4935=cut
4936
4937sub _RichTextReplaceLinkOfInlineContent {
4938    my ( $Self, %Param ) = @_;
4939
4940    # check needed stuff
4941    for (qw(String)) {
4942        if ( !$Param{$_} ) {
4943            $Kernel::OM->Get('Kernel::System::Log')->Log(
4944                Priority => 'error',
4945                Message  => "Need $_!"
4946            );
4947            return;
4948        }
4949    }
4950
4951    # replace image link with content id for uploaded images
4952    ${ $Param{String} } =~ s{
4953        (<img.+?src=("|'))[^"'>]+?ContentID=(.+?)("|')([^>]*>)
4954    }
4955    {
4956        my ($Start, $CID, $Close, $End) = ($1, $3, $4, $5);
4957        # Make sure we only get the CID and not extra stuff like session information
4958        $CID =~ s{^([^;&]+).*}{$1}smx;
4959        $Start . 'cid:' . $CID . $Close . $End;
4960    }esgxi;
4961
4962    return $Param{String};
4963}
4964
4965=end Internal:
4966
4967=head2 RichTextDocumentServe()
4968
4969Serve a rich text (HTML) document for local view inside of an C<iframe> in correct charset and with correct links for
4970inline documents.
4971
4972By default, all inline/active content (such as C<script>, C<object>, C<applet> or C<embed> tags) will be stripped. If
4973there are external images, they will be stripped too, but a message will be shown allowing the user to reload the page
4974showing the external images.
4975
4976    my %HTMLFile = $LayoutObject->RichTextDocumentServe(
4977        Data => {
4978            Content     => $HTMLBodyRef,
4979            ContentType => 'text/html; charset="iso-8859-1"',
4980        },
4981        URL               => 'AgentTicketAttachment;Subaction=HTMLView;TicketID=123;ArticleID=123;FileID=',
4982        Attachments       => \%AttachmentListOfInlineAttachments,
4983
4984        LoadInlineContent => 0,     # Serve the document including all inline content. WARNING: This might be dangerous.
4985
4986        LoadExternalImages => 0,    # Load external images? If this is 0, a message will be included if
4987                                    # external images were found and removed.
4988    );
4989
4990=cut
4991
4992sub RichTextDocumentServe {
4993    my ( $Self, %Param ) = @_;
4994
4995    # check needed stuff
4996    for (qw(Data URL Attachments)) {
4997        if ( !defined $Param{$_} ) {
4998            $Kernel::OM->Get('Kernel::System::Log')->Log(
4999                Priority => 'error',
5000                Message  => "Need $_!"
5001            );
5002            return;
5003        }
5004    }
5005
5006    # Get charset from passed content type parameter.
5007    my $Charset;
5008    if ( $Param{Data}->{ContentType} =~ m/.+?charset=("|'|)(.+)/ig ) {
5009        $Charset = $2;
5010        $Charset =~ s/"|'//g;
5011    }
5012    if ( !$Charset ) {
5013        $Charset = 'us-ascii';
5014        $Param{Data}->{ContentType} .= '; charset="us-ascii"';
5015    }
5016
5017    # Convert to internal charset.
5018    if ($Charset) {
5019        $Param{Data}->{Content} = $Kernel::OM->Get('Kernel::System::Encode')->Convert(
5020            Text  => $Param{Data}->{Content},
5021            From  => $Charset,
5022            To    => 'utf-8',
5023            Check => 1,
5024        );
5025
5026        # Replace charset in content type and content.
5027        $Param{Data}->{ContentType} =~ s/\Q$Charset\E/utf-8/gi;
5028        if ( !( $Param{Data}->{Content} =~ s/(<meta[^>]+charset=("|'|))\Q$Charset\E/$1utf-8/gi ) ) {
5029
5030            # Add explicit charset if missing.
5031            $Param{Data}->{Content}
5032                =~ s/(<meta [^>]+ http-equiv=("|')?Content-Type("|')? [^>]+ content=("|')?[^;"'>]+)/$1; charset=utf-8/ixms;
5033        }
5034    }
5035
5036    # add html links
5037    $Param{Data}->{Content} = $Self->HTMLLinkQuote(
5038        String => $Param{Data}->{Content},
5039    );
5040
5041    # cleanup some html tags to be cross browser compat.
5042    $Param{Data}->{Content} = $Self->RichTextDocumentCleanup(
5043        String => $Param{Data}->{Content},
5044    );
5045
5046    # safety check
5047    if ( !$Param{LoadInlineContent} ) {
5048
5049        # Strip out active content first, keeping external images.
5050        my %SafetyCheckResult = $Kernel::OM->Get('Kernel::System::HTMLUtils')->Safety(
5051            String       => $Param{Data}->{Content},
5052            NoApplet     => 1,
5053            NoObject     => 1,
5054            NoEmbed      => 1,
5055            NoSVG        => 1,
5056            NoIntSrcLoad => 0,
5057            NoExtSrcLoad => 0,
5058            NoJavaScript => 1,
5059            Debug        => $Self->{Debug},
5060        );
5061
5062        $Param{Data}->{Content} = $SafetyCheckResult{String};
5063
5064        if ( !$Param{LoadExternalImages} ) {
5065
5066            # Strip out external content.
5067            my %SafetyCheckResult = $Kernel::OM->Get('Kernel::System::HTMLUtils')->Safety(
5068                String       => $Param{Data}->{Content},
5069                NoApplet     => 1,
5070                NoObject     => 1,
5071                NoEmbed      => 1,
5072                NoSVG        => 1,
5073                NoIntSrcLoad => 0,
5074                NoExtSrcLoad => 1,
5075                NoJavaScript => 1,
5076                Debug        => $Self->{Debug},
5077            );
5078
5079            $Param{Data}->{Content} = $SafetyCheckResult{String};
5080
5081           # Show confirmation button to load external content explicitly only if BlockLoadingRemoteContent is disabled.
5082            if (
5083                $SafetyCheckResult{Replace}
5084                && !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::Frontend::BlockLoadingRemoteContent')
5085                )
5086            {
5087
5088                # Generate blocker message.
5089                my $Message = $Self->Output( TemplateFile => 'AttachmentBlocker' );
5090
5091                # Add it to the beginning of the body, if possible, otherwise prepend it.
5092                if ( $Param{Data}->{Content} =~ /<body.*?>/si ) {
5093                    $Param{Data}->{Content} =~ s/(<body.*?>)/$1\n$Message/si;
5094                }
5095                else {
5096                    $Param{Data}->{Content} = $Message . $Param{Data}->{Content};
5097                }
5098            }
5099
5100        }
5101    }
5102
5103    # build base url for inline images
5104    my $SessionID = '';
5105    if ( $Self->{SessionID} && !$Self->{SessionIDCookie} ) {
5106        $SessionID = ';' . $Self->{SessionName} . '=' . $Self->{SessionID};
5107    }
5108
5109    # replace inline images in content with runtime url to images
5110    my $AttachmentLink = $Self->{Baselink} . $Param{URL};
5111    $Param{Data}->{Content} =~ s{
5112        (=|"|')cid:(.*?)("|'|>|\/>|\s)
5113    }
5114    {
5115        my $Start= $1;
5116        my $ContentID = $2;
5117        my $End = $3;
5118
5119        # improve html quality
5120        if ( $Start ne '"' && $Start ne '\'' ) {
5121            $Start .= '"';
5122        }
5123        if ( $End ne '"' && $End ne '\'' ) {
5124            $End = '"' . $End;
5125        }
5126
5127        # find matching attachment and replace it with runtime url to image
5128        ATTACHMENT_ID:
5129        for my $AttachmentID (  sort keys %{ $Param{Attachments} }) {
5130            next ATTACHMENT_ID if lc $Param{Attachments}->{$AttachmentID}->{ContentID} ne lc "<$ContentID>";
5131            $ContentID = $AttachmentLink . $AttachmentID . $SessionID;
5132            last ATTACHMENT_ID;
5133        }
5134
5135        # return new runtime url
5136        $Start . $ContentID . $End;
5137    }egxi;
5138
5139    # bug #5053
5140    # inline images using Content-Location as identifier instead of Content-ID even RFC2557
5141    # http://www.ietf.org/rfc/rfc2557.txt
5142
5143    # find matching attachment and replace it with runtlime url to image
5144    ATTACHMENT:
5145    for my $AttachmentID ( sort keys %{ $Param{Attachments} } ) {
5146        next ATTACHMENT if !$Param{Attachments}->{$AttachmentID}->{ContentID};
5147
5148        # content id cleanup
5149        $Param{Attachments}->{$AttachmentID}->{ContentID} =~ s/^<//;
5150        $Param{Attachments}->{$AttachmentID}->{ContentID} =~ s/>$//;
5151
5152        next ATTACHMENT if !$Param{Attachments}->{$AttachmentID}->{ContentID};
5153
5154        $Param{Data}->{Content} =~ s{
5155        (=|"|')(\Q$Param{Attachments}->{$AttachmentID}->{ContentID}\E)("|'|>|\/>|\s)
5156    }
5157    {
5158        my $Start= $1;
5159        my $ContentID = $2;
5160        my $End = $3;
5161
5162        # improve html quality
5163        if ( $Start ne '"' && $Start ne '\'' ) {
5164            $Start .= '"';
5165        }
5166        if ( $End ne '"' && $End ne '\'' ) {
5167            $End = '"' . $End;
5168        }
5169
5170        # return new runtime url
5171        $ContentID = $AttachmentLink . $AttachmentID . $SessionID;
5172        $Start . $ContentID . $End;
5173    }egxi;
5174    }
5175
5176    return %{ $Param{Data} };
5177}
5178
5179=head2 RichTextDocumentCleanup()
5180
5181please see L<Kernel::System::HTML::Layout::DocumentCleanup()>
5182
5183=cut
5184
5185sub RichTextDocumentCleanup {
5186    my ( $Self, %Param ) = @_;
5187
5188    # check needed stuff
5189    for (qw(String)) {
5190        if ( !defined $Param{$_} ) {
5191            $Kernel::OM->Get('Kernel::System::Log')->Log(
5192                Priority => 'error',
5193                Message  => "Need $_!"
5194            );
5195            return;
5196        }
5197    }
5198
5199    $Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentCleanup(
5200        String => $Param{String},
5201    );
5202
5203    return $Param{String};
5204}
5205
5206=begin Internal:
5207
5208=cut
5209
5210=head2 _BuildSelectionOptionRefCreate()
5211
5212create the option hash
5213
5214    my $OptionRef = $LayoutObject->_BuildSelectionOptionRefCreate(
5215        %Param,
5216    );
5217
5218    my $OptionRef = {
5219        Sort         => 'numeric',
5220        PossibleNone => 0,
5221        Max          => 100,
5222    }
5223
5224=cut
5225
5226sub _BuildSelectionOptionRefCreate {
5227    my ( $Self, %Param ) = @_;
5228
5229    # set SelectedID option
5230    my $OptionRef = {};
5231    if ( defined $Param{SelectedID} ) {
5232        if ( ref $Param{SelectedID} eq 'ARRAY' ) {
5233            for my $Key ( @{ $Param{SelectedID} } ) {
5234                $OptionRef->{SelectedID}->{$Key} = 1;
5235            }
5236        }
5237        else {
5238            $OptionRef->{SelectedID}->{ $Param{SelectedID} } = 1;
5239        }
5240    }
5241
5242    # set SelectedValue option
5243    if ( defined $Param{SelectedValue} ) {
5244        if ( ref $Param{SelectedValue} eq 'ARRAY' ) {
5245            for my $Value ( @{ $Param{SelectedValue} } ) {
5246                $OptionRef->{SelectedValue}->{$Value} = 1;
5247            }
5248        }
5249        else {
5250            $OptionRef->{SelectedValue}->{ $Param{SelectedValue} } = 1;
5251        }
5252    }
5253
5254    # set Sort option
5255    $OptionRef->{Sort} = 0;
5256    if ( $Param{Sort} ) {
5257        $OptionRef->{Sort} = $Param{Sort};
5258    }
5259
5260    # look if a individual sort is available
5261    if ( $Param{SortIndividual} && ref $Param{SortIndividual} eq 'ARRAY' ) {
5262        $OptionRef->{SortIndividual} = $Param{SortIndividual};
5263    }
5264
5265    # set SortReverse option
5266    $OptionRef->{SortReverse} = 0;
5267    if ( $Param{SortReverse} ) {
5268        $OptionRef->{SortReverse} = 1;
5269    }
5270
5271    # set Translation option
5272    $OptionRef->{Translation} = 1;
5273    if ( defined $Param{Translation} && $Param{Translation} eq 0 ) {
5274        $OptionRef->{Translation} = 0;
5275    }
5276
5277    # correcting selected value hash if translation is on
5278    if (
5279        $OptionRef->{Translation}
5280        && $OptionRef->{SelectedValue}
5281        && ref $OptionRef->{SelectedValue} eq 'HASH'
5282        )
5283    {
5284        my %SelectedValueNew;
5285        for my $OriginalKey ( sort keys %{ $OptionRef->{SelectedValue} } ) {
5286            my $TranslatedKey = $Self->{LanguageObject}->Translate($OriginalKey);
5287            $SelectedValueNew{$TranslatedKey} = 1;
5288        }
5289        $OptionRef->{SelectedValue} = \%SelectedValueNew;
5290    }
5291
5292    # set PossibleNone option
5293    $OptionRef->{PossibleNone} = 0;
5294    if ( $Param{PossibleNone} ) {
5295        $OptionRef->{PossibleNone} = 1;
5296    }
5297
5298    # set TreeView option
5299    $OptionRef->{TreeView} = 0;
5300    if ( $Param{TreeView} ) {
5301        $OptionRef->{TreeView} = 1;
5302        $OptionRef->{Sort}     = 'TreeView';
5303    }
5304
5305    # set DisabledBranch option
5306    if ( $Param{DisabledBranch} ) {
5307        if ( ref $Param{DisabledBranch} eq 'ARRAY' ) {
5308            for my $Branch ( @{ $Param{DisabledBranch} } ) {
5309                $OptionRef->{DisabledBranch}->{$Branch} = 1;
5310            }
5311        }
5312        else {
5313            $OptionRef->{DisabledBranch}->{ $Param{DisabledBranch} } = 1;
5314        }
5315    }
5316
5317    # set Max option
5318    $OptionRef->{Max} = $Param{Max} || 100;
5319
5320    # set HTMLQuote option
5321    $OptionRef->{HTMLQuote} = 1;
5322    if ( defined $Param{HTMLQuote} ) {
5323        $OptionRef->{HTMLQuote} = $Param{HTMLQuote};
5324    }
5325
5326    return $OptionRef;
5327}
5328
5329=head2 _BuildSelectionAttributeRefCreate()
5330
5331create the attribute hash
5332
5333    my $AttributeRef = $LayoutObject->_BuildSelectionAttributeRefCreate(
5334        %Param,
5335    );
5336
5337    my $AttributeRef = {
5338        name     => 'TheName',
5339        multiple => undef,
5340        size     => 5,
5341    }
5342
5343=cut
5344
5345sub _BuildSelectionAttributeRefCreate {
5346    my ( $Self, %Param ) = @_;
5347
5348    my $AttributeRef = {};
5349
5350    # check params with key and value
5351    for (qw(Name ID Size Class OnChange OnClick AutoComplete)) {
5352        if ( $Param{$_} ) {
5353            $AttributeRef->{ lc($_) } = $Param{$_};
5354        }
5355    }
5356
5357    # add id attriubut
5358    if ( !$AttributeRef->{id} ) {
5359        $AttributeRef->{id} = $AttributeRef->{name};
5360    }
5361
5362    # check params with key and value that need to be HTML-Quoted
5363    for (qw(Title)) {
5364        if ( $Param{$_} ) {
5365            $AttributeRef->{ lc($_) } = $Self->Ascii2Html( Text => $Param{$_} );
5366        }
5367    }
5368
5369    # check HTML params
5370    for (qw(Multiple Disabled)) {
5371        if ( $Param{$_} ) {
5372            $AttributeRef->{ lc($_) } = lc($_);
5373        }
5374    }
5375
5376    return $AttributeRef;
5377}
5378
5379=head2 _BuildSelectionDataRefCreate()
5380
5381create the data hash
5382
5383    my $DataRef = $LayoutObject->_BuildSelectionDataRefCreate(
5384        Data => $ArrayRef,              # use $HashRef, $ArrayRef or $ArrayHashRef
5385        AttributeRef => $AttributeRef,
5386        OptionRef => $OptionRef,
5387    );
5388
5389    my $DataRef  = [
5390        {
5391            Key => 11,
5392            Value => 'Text',
5393        },
5394        {
5395            Key => 'abc',
5396            Value => '&nbsp;&nbsp;Text',
5397            Selected => 1,
5398        },
5399    ];
5400
5401=cut
5402
5403sub _BuildSelectionDataRefCreate {
5404    my ( $Self, %Param ) = @_;
5405
5406    my $AttributeRef = $Param{AttributeRef};
5407    my $OptionRef    = $Param{OptionRef};
5408    my $DataRef      = [];
5409
5410    my $Counter = 0;
5411
5412    # for HashRef and ArrayRef only
5413    my %DisabledElements;
5414
5415    # dclone $Param{Data} because the subroutine unfortunately modifies
5416    # the original data ref
5417    my $DataLocal = $Kernel::OM->Get('Kernel::System::Storable')->Clone( Data => $Param{Data} );
5418
5419    # if HashRef was given
5420    if ( ref $DataLocal eq 'HASH' ) {
5421
5422        # get missing parents and mark them for disable later
5423        if ( $OptionRef->{Sort} eq 'TreeView' ) {
5424
5425            # Delete entries in hash with value = undef,
5426            #   because otherwise the reverse statement will cause warnings.
5427            # Reverse hash, skipping undefined values.
5428            my %List = map { $DataLocal->{$_} => $_ } grep { defined $DataLocal->{$_} } keys %{$DataLocal};
5429
5430            # get each data value
5431            for my $Key ( sort keys %List ) {
5432                my $Parents = '';
5433
5434                # try to split its parents (e.g. Queue or Service) GrandParent::Parent::Son
5435                my @Elements = split /::/, $Key;
5436
5437                # get each element in the hierarchy
5438                for my $Element (@Elements) {
5439
5440                    # add its own parents for the complete name
5441                    my $ElementLongName = $Parents . $Element;
5442
5443                    # check if element exists in the original data or if it is already marked
5444                    if ( !$List{$ElementLongName} && !$DisabledElements{$ElementLongName} ) {
5445
5446                        # mark element as disabled
5447                        $DisabledElements{$ElementLongName} = 1;
5448
5449                        # add the element to the original data to be disabled later
5450                        $DataLocal->{ $ElementLongName . '_Disabled' } = $ElementLongName;
5451                    }
5452                    $Parents .= $Element . '::';
5453                }
5454            }
5455        }
5456
5457        # sort hash (before the translation)
5458        my @SortKeys;
5459        if ( $OptionRef->{Sort} eq 'IndividualValue' && $OptionRef->{SortIndividual} ) {
5460            my %List = reverse %{$DataLocal};
5461            for my $Key ( @{ $OptionRef->{SortIndividual} } ) {
5462                if ( $List{$Key} ) {
5463                    push @SortKeys, $List{$Key};
5464                    delete $List{$Key};
5465                }
5466            }
5467            push @SortKeys, sort { lc $a cmp lc $b } ( values %List );
5468        }
5469
5470        # translate value
5471        if ( $OptionRef->{Translation} ) {
5472            for my $Row ( sort keys %{$DataLocal} ) {
5473                $DataLocal->{$Row} = $Self->{LanguageObject}->Translate( $DataLocal->{$Row} );
5474            }
5475        }
5476
5477        # sort hash (after the translation)
5478        if ( $OptionRef->{Sort} eq 'NumericKey' ) {
5479            @SortKeys = sort { $a <=> $b } ( keys %{$DataLocal} );
5480        }
5481        elsif ( $OptionRef->{Sort} eq 'NumericValue' ) {
5482            @SortKeys = sort { $DataLocal->{$a} <=> $DataLocal->{$b} } ( keys %{$DataLocal} );
5483        }
5484        elsif ( $OptionRef->{Sort} eq 'AlphanumericKey' ) {
5485            @SortKeys = sort( keys %{$DataLocal} );
5486        }
5487        elsif ( $OptionRef->{Sort} eq 'TreeView' ) {
5488
5489            # add suffix for correct sorting
5490            my %SortHash;
5491            KEY:
5492            for my $Key ( sort keys %{$DataLocal} ) {
5493                next KEY if !defined $DataLocal->{$Key};
5494                $SortHash{$Key} = $DataLocal->{$Key} . '::';
5495            }
5496            @SortKeys = sort { lc $SortHash{$a} cmp lc $SortHash{$b} } ( keys %SortHash );
5497        }
5498        elsif ( $OptionRef->{Sort} eq 'IndividualKey' && $OptionRef->{SortIndividual} ) {
5499            my %List = %{$DataLocal};
5500            for my $Key ( @{ $OptionRef->{SortIndividual} } ) {
5501                if ( $List{$Key} ) {
5502                    push @SortKeys, $Key;
5503                    delete $List{$Key};
5504                }
5505            }
5506            push @SortKeys, sort { lc $List{$a} cmp lc $List{$b} } ( keys %List );
5507        }
5508        elsif ( $OptionRef->{Sort} eq 'IndividualValue' && $OptionRef->{SortIndividual} ) {
5509
5510            # already done before the translation
5511        }
5512        else {
5513            @SortKeys = sort {
5514                lc( $DataLocal->{$a} // '' )
5515                    cmp lc( $DataLocal->{$b} // '' )
5516            } ( keys %{$DataLocal} );
5517            $OptionRef->{Sort} = 'AlphanumericValue';
5518        }
5519
5520        # create DataRef
5521        for my $Row (@SortKeys) {
5522            $DataRef->[$Counter]->{Key}   = $Row;
5523            $DataRef->[$Counter]->{Value} = $DataLocal->{$Row};
5524            $Counter++;
5525        }
5526    }
5527
5528    # if ArrayHashRef was given
5529    elsif ( ref $DataLocal eq 'ARRAY' && ref $DataLocal->[0] eq 'HASH' ) {
5530
5531        # get missing parents and mark them for disable later
5532        if ( $OptionRef->{Sort} eq 'TreeView' ) {
5533
5534            # build a list of element longnames
5535            my @NewDataLocal;
5536
5537            my %List;
5538            for my $ValueHash ( @{$DataLocal} ) {
5539                $List{ $ValueHash->{Value} } = 1;
5540            }
5541
5542            # get each data value hash
5543            for my $ValueHash ( @{$DataLocal} ) {
5544
5545                my $Parents = '';
5546
5547                # try to split its parents (e.g. Queue or Service) GrandParent::Parent::Son
5548                my @Elements = split /::/, $ValueHash->{Value};
5549
5550                # get each element in the hierarchy
5551                for my $Element (@Elements) {
5552
5553                    # add its own parents for the complete name
5554                    my $ElementLongName = $Parents . $Element;
5555
5556                    # check if element exists in the original data or if it is already marked
5557                    if ( !$List{$ElementLongName} && !$DisabledElements{$ElementLongName} ) {
5558
5559                        # mark element as disabled
5560                        $DisabledElements{$ElementLongName} = 1;
5561
5562                        # push the missing element to the data local array
5563                        push @NewDataLocal, {
5564                            Key      => $ElementLongName . '_Disabled',
5565                            Value    => $ElementLongName,
5566                            Disabled => 1,
5567                        };
5568                    }
5569                    $Parents .= $Element . '::';
5570                }
5571
5572                # push the element to the data local array
5573                push @NewDataLocal, {
5574                    Key      => $ValueHash->{Key},
5575                    Value    => $ValueHash->{Value},
5576                    Selected => $ValueHash->{Selected} ? 1 : 0,
5577                    Disabled => $ValueHash->{Disabled} ? 1 : 0,
5578                };
5579            }
5580
5581            # override the data local with the new one
5582            @{$DataLocal} = @NewDataLocal;
5583        }
5584
5585        # create DataRef
5586        for my $Row ( @{$DataLocal} ) {
5587            if ( ref $Row eq 'HASH' && defined $Row->{Key} ) {
5588                $DataRef->[$Counter]->{Key}   = $Row->{Key};
5589                $DataRef->[$Counter]->{Value} = $Row->{Value};
5590
5591                # translate value
5592                if ( $OptionRef->{Translation} ) {
5593                    $DataRef->[$Counter]->{Value} = $Self->{LanguageObject}->Translate( $DataRef->[$Counter]->{Value} );
5594                }
5595
5596                # set Selected and Disabled options
5597                if ( $Row->{Selected} ) {
5598                    $DataRef->[$Counter]->{Selected} = 1;
5599                }
5600                elsif ( $Row->{Disabled} ) {
5601                    $DataRef->[$Counter]->{Disabled} = 1;
5602                }
5603                $Counter++;
5604            }
5605        }
5606    }
5607
5608    # if ArrayRef was given
5609    elsif ( ref $DataLocal eq 'ARRAY' ) {
5610
5611        # get missing parents and mark them for disable later
5612        if ( $OptionRef->{Sort} eq 'TreeView' ) {
5613            my %List = map { $_ => 1 } @{$DataLocal};
5614
5615            # get each data value
5616            for my $Key ( sort keys %List ) {
5617                my $Parents = '';
5618
5619                # try to split its parents (e.g. Queue or Service) GrandParent::Parent::Son
5620                my @Elements = split /::/, $Key;
5621
5622                # get each element in the hierarchy
5623                for my $Element (@Elements) {
5624
5625                    # add its own parents for the complete name
5626                    my $ElementLongName = $Parents . $Element;
5627
5628                    # check if element exists in the original data or if it is already marked
5629                    if ( !$List{$ElementLongName} && !$DisabledElements{$ElementLongName} ) {
5630
5631                        # mark element as disabled
5632                        $DisabledElements{$ElementLongName} = 1;
5633
5634                        # add the element to the original data to be disabled later
5635                        push @{$DataLocal}, $ElementLongName;
5636                    }
5637                    $Parents .= $Element . '::';
5638                }
5639            }
5640        }
5641
5642        if ( $OptionRef->{Sort} eq 'IndividualValue' && $OptionRef->{SortIndividual} ) {
5643            my %List = map { $_ => 1 } @{$DataLocal};
5644            $DataLocal = [];
5645            for my $Key ( @{ $OptionRef->{SortIndividual} } ) {
5646                if ( $List{$Key} ) {
5647                    push @{$DataLocal}, $Key;
5648                    delete $List{$Key};
5649                }
5650            }
5651            push @{$DataLocal}, sort { $a cmp $b } ( keys %List );
5652        }
5653
5654        my %ReverseHash;
5655
5656        # translate value
5657        if ( $OptionRef->{Translation} ) {
5658            my @TranslateArray;
5659            for my $Row ( @{$DataLocal} ) {
5660                my $TranslateString = $Self->{LanguageObject}->Translate($Row);
5661                push @TranslateArray, $TranslateString;
5662                $ReverseHash{$TranslateString} = $Row;
5663            }
5664            $DataLocal = \@TranslateArray;
5665        }
5666        else {
5667            for my $Row ( @{$DataLocal} ) {
5668                $ReverseHash{$Row} = $Row;
5669            }
5670        }
5671
5672        # sort array
5673        if ( $OptionRef->{Sort} eq 'AlphanumericKey' || $OptionRef->{Sort} eq 'AlphanumericValue' )
5674        {
5675            my @SortArray = sort( @{$DataLocal} );
5676            $DataLocal = \@SortArray;
5677        }
5678        elsif ( $OptionRef->{Sort} eq 'NumericKey' || $OptionRef->{Sort} eq 'NumericValue' ) {
5679            my @SortArray = sort { $a <=> $b } ( @{$DataLocal} );
5680            $DataLocal = \@SortArray;
5681        }
5682        elsif ( $OptionRef->{Sort} eq 'TreeView' ) {
5683
5684            # sort array, add '::' in the comparison, for proper sort of Items with Items::SubItems
5685            my @SortArray = sort { $a . '::' cmp $b . '::' } @{$DataLocal};
5686            $DataLocal = \@SortArray;
5687        }
5688
5689        # create DataRef
5690        for my $Row ( @{$DataLocal} ) {
5691            $DataRef->[$Counter]->{Key}   = $ReverseHash{$Row};
5692            $DataRef->[$Counter]->{Value} = $Row;
5693            $Counter++;
5694        }
5695    }
5696
5697    # check disabled items on ArrayRef or HashRef only
5698    if (
5699        ref $DataLocal eq 'HASH'
5700        || ( ref $DataLocal eq 'ARRAY' && ref $DataLocal->[0] ne 'HASH' )
5701        )
5702    {
5703        for my $Row ( @{$DataRef} ) {
5704            if ( defined $Row->{Value} && $DisabledElements{ $Row->{Value} } ) {
5705                $Row->{Key}      = '-';
5706                $Row->{Disabled} = 1;
5707            }
5708        }
5709    }
5710
5711    # DisabledBranch option
5712    if ( $OptionRef->{DisabledBranch} ) {
5713        for my $Row ( @{$DataRef} ) {
5714            for my $Branch ( sort keys %{ $OptionRef->{DisabledBranch} } ) {
5715                if ( $Row->{Value} =~ /^(\Q$Branch\E)$/ || $Row->{Value} =~ /^(\Q$Branch\E)::/ ) {
5716                    $Row->{Disabled} = 1;
5717                }
5718            }
5719        }
5720    }
5721
5722    # SelectedID and SelectedValue option
5723    if ( defined $OptionRef->{SelectedID} || $OptionRef->{SelectedValue} ) {
5724        for my $Row ( @{$DataRef} ) {
5725            if (
5726                (
5727                    (
5728                        defined $Row->{Key}
5729                        && $OptionRef->{SelectedID}->{ $Row->{Key} }
5730                    )
5731                    ||
5732                    (
5733                        defined $Row->{Value}
5734                        && $OptionRef->{SelectedValue}->{ $Row->{Value} }
5735                    )
5736                )
5737                &&
5738                (
5739                    defined $Row->{Value}
5740                    && !$DisabledElements{ $Row->{Value} }
5741                )
5742                )
5743            {
5744                $Row->{Selected} = 1;
5745            }
5746        }
5747    }
5748
5749    # SortReverse option
5750    if ( $OptionRef->{SortReverse} ) {
5751        @{$DataRef} = reverse( @{$DataRef} );
5752    }
5753
5754    # PossibleNone option
5755    if ( $OptionRef->{PossibleNone} ) {
5756        my %None;
5757        $None{Key}   = '';
5758        $None{Value} = '-';
5759
5760        unshift( @{$DataRef}, \%None );
5761    }
5762
5763    # TreeView option
5764    if ( $OptionRef->{TreeView} ) {
5765
5766        ROW:
5767        for my $Row ( @{$DataRef} ) {
5768
5769            next ROW if !$Row->{Value};
5770
5771            my @Fragment = split '::', $Row->{Value};
5772            $Row->{Value} = pop @Fragment;
5773
5774            # translate the individual tree options
5775            if ( $OptionRef->{Translation} ) {
5776                $Row->{Value} = $Self->{LanguageObject}->Translate( $Row->{Value} );
5777            }
5778
5779            # TODO: Here we are combining Max with HTMLQuote, check below for the REMARK:
5780            # Max and HTMLQuote needs to be done before spaces insert but after the split of the
5781            # parents, then it is not possible to do it outside
5782            if ( $OptionRef->{HTMLQuote} ) {
5783                $Row->{Value} = $Self->Ascii2Html(
5784                    Text => $Row->{Value},
5785                    Max  => $OptionRef->{Max},
5786                );
5787            }
5788            elsif ( $OptionRef->{Max} ) {
5789                if ( length $Row->{Value} > $OptionRef->{Max} ) {
5790                    $Row->{Value} = substr( $Row->{Value}, 0, $OptionRef->{Max} - 5 ) . '[...]';
5791                }
5792            }
5793
5794            # Use unicode 'NO-BREAK SPACE' since unicode characters doesn't need to be escaped.
5795            # Previously, we used '&nbsp;' and we had issue that Option needs to be html encoded
5796            # in AJAX, and it was causing issues.
5797            my $Space = "\xA0\xA0" x scalar @Fragment;
5798            $Space ||= '';
5799
5800            $Row->{Value} = $Space . $Row->{Value};
5801        }
5802    }
5803    else {
5804
5805        # HTMLQuote option
5806        if ( $OptionRef->{HTMLQuote} ) {
5807            for my $Row ( @{$DataRef} ) {
5808                $Row->{Key}   = $Self->Ascii2Html( Text => $Row->{Key} );
5809                $Row->{Value} = $Self->Ascii2Html( Text => $Row->{Value} );
5810            }
5811        }
5812
5813        # TODO: Check this comment!
5814        # Max option
5815        # REMARK: Don't merge the Max handling with Ascii2Html function call of
5816        # the HTMLQuote handling. In this case you lose the max handling if you
5817        # deactivate HTMLQuote
5818        if ( $OptionRef->{Max} ) {
5819
5820            # REMARK: This is the same solution as in Ascii2Html
5821            for my $Row ( @{$DataRef} ) {
5822
5823                if ( ref $Row eq 'HASH' ) {
5824                    if ( length $Row->{Value} > $OptionRef->{Max} ) {
5825                        $Row->{Value} = substr( $Row->{Value}, 0, $OptionRef->{Max} - 5 ) . '[...]';
5826                    }
5827                }
5828                else {
5829                    if ( length $Row > $OptionRef->{Max} ) {
5830                        $Row = substr( $Row, 0, $OptionRef->{Max} - 5 ) . '[...]';
5831                    }
5832                }
5833            }
5834        }
5835    }
5836
5837    return $DataRef;
5838}
5839
5840=head2 _BuildSelectionOutput()
5841
5842create the html string
5843
5844    my $HTMLString = $LayoutObject->_BuildSelectionOutput(
5845        AttributeRef       => $AttributeRef,
5846        DataRef            => $DataRef,
5847        TreeView           => 0,              # optional, see BuildSelection()
5848        FiltersRef         => \@Filters,      # optional, see BuildSelection()
5849        FilterActive       => $FilterActive,  # optional, see BuildSelection()
5850        ExpandFilters      => 1,              # optional, see BuildSelection()
5851        ValidateDateAfter  => '2016-01-01',   # optional, see BuildSelection()
5852        ValidateDateBefore => '2016-01-01',   # optional, see BuildSelection()
5853    );
5854
5855    my $AttributeRef = {
5856        name => 'TheName',
5857        multiple => undef,
5858        size => 5,
5859    }
5860
5861    my $DataRef  = [
5862        {
5863            Key => 11,
5864            Value => 'Text',
5865            Disabled => 1,
5866        },
5867        {
5868            Key => 'abc',
5869            Value => '&nbsp;&nbsp;Text',
5870            Selected => 1,
5871        },
5872    ];
5873
5874=cut
5875
5876sub _BuildSelectionOutput {
5877    my ( $Self, %Param ) = @_;
5878
5879    # start generation, if AttributeRef and DataRef was found
5880    my $String;
5881    if ( $Param{AttributeRef} && $Param{DataRef} ) {
5882
5883        # generate <select> row
5884        $String = '<select';
5885        for my $Key ( sort keys %{ $Param{AttributeRef} } ) {
5886            if ( $Key && defined $Param{AttributeRef}->{$Key} ) {
5887                $String .= " $Key=\"$Param{AttributeRef}->{$Key}\"";
5888            }
5889            elsif ($Key) {
5890                $String .= " $Key";
5891            }
5892        }
5893
5894        # add filters if defined
5895        if ( $Param{FiltersRef} && scalar @{ $Param{FiltersRef} } > 0 ) {
5896            my $JSON = $Self->JSONEncode(
5897                Data => {
5898                    Filters => $Param{FiltersRef},
5899                },
5900                NoQuotes => 1,
5901            );
5902            my $JSONEscaped = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML(
5903                String => $JSON,
5904            );
5905            $String .= " data-filters=\"$JSONEscaped\"";
5906            if ( $Param{FilterActive} ) {
5907                $String .= ' data-filtered="' . int( $Param{FilterActive} ) . '"';
5908            }
5909            if ( $Param{ExpandFilters} ) {
5910                $String .= ' data-expand-filters="' . int( $Param{ExpandFilters} ) . '"';
5911            }
5912        }
5913
5914        # tree flag for Input Fields
5915        if ( $Param{TreeView} ) {
5916            $String .= ' data-tree="true"';
5917        }
5918
5919        # date validation values
5920        if ( $Param{ValidateDateAfter} ) {
5921            $String .= ' data-validate-date-after="' . $Param{ValidateDateAfter} . '"';
5922        }
5923        if ( $Param{ValidateDateBefore} ) {
5924            $String .= ' data-validate-date-before="' . $Param{ValidateDateBefore} . '"';
5925        }
5926
5927        $String .= ">\n";
5928
5929        # generate <option> rows
5930        for my $Row ( @{ $Param{DataRef} } ) {
5931            my $Key = '';
5932            if ( defined $Row->{Key} ) {
5933                $Key = $Row->{Key};
5934            }
5935            my $Value = '';
5936            if ( defined $Row->{Value} ) {
5937                $Value = $Row->{Value};
5938            }
5939            my $SelectedDisabled = '';
5940            if ( $Row->{Selected} ) {
5941                $SelectedDisabled = ' selected="selected"';
5942            }
5943            elsif ( $Row->{Disabled} ) {
5944                $SelectedDisabled = ' disabled="disabled"';
5945            }
5946            my $OptionTitle = '';
5947            if ( $Param{OptionTitle} ) {
5948                $OptionTitle = ' title="' . $Value . '"';
5949            }
5950            $String .= "  <option value=\"$Key\"$SelectedDisabled$OptionTitle>$Value</option>\n";
5951        }
5952        $String .= '</select>';
5953
5954        if ( $Param{TreeView} ) {
5955            my $TreeSelectionMessage = $Self->{LanguageObject}->Translate("Show Tree Selection");
5956            $String
5957                .= ' <a href="#" title="'
5958                . $TreeSelectionMessage
5959                . '" class="ShowTreeSelection"><span>'
5960                . $TreeSelectionMessage . '</span><i class="fa fa-sitemap"></i></a>';
5961        }
5962
5963    }
5964    return $String;
5965}
5966
5967sub _DisableBannerCheck {
5968    my ( $Self, %Param ) = @_;
5969
5970    return 1 if !$Kernel::OM->Get('Kernel::Config')->Get('Secure::DisableBanner');
5971    return   if !$Param{OutputRef};
5972
5973    # remove the version tag from the header
5974    ${ $Param{OutputRef} } =~ s{
5975                ^ X-Powered-By: .+? Open \s Ticket \s Request \s System \s \(http .+? \)$ \n
5976            }{}smx;
5977
5978    return 1;
5979}
5980
5981=head2 _RemoveScriptTags()
5982
5983This function will remove the surrounding <script> tags of a
5984piece of JavaScript code, if they are present, and return the result.
5985
5986    my $CodeContent = $LayoutObject->_RemoveScriptTags(Code => $SomeCode);
5987
5988=cut
5989
5990sub _RemoveScriptTags {
5991    my ( $Self, %Param ) = @_;
5992
5993    my $Code = $Param{Code} || '';
5994
5995    if ( $Code =~ m/<script/ ) {
5996
5997        # cut out dtl block comments of already replaced dtl blocks
5998        $Code =~ s{
5999            ^
6000            <!--
6001            \/?
6002            \w+
6003            -->
6004            \r?\n
6005        }{}smxg;
6006
6007        # cut out opening script tags
6008        $Code =~ s{
6009            <script[^>]+>
6010            (?:\s*<!--)?
6011            (?:\s*//\s*<!\[CDATA\[)?
6012        }
6013        {}smxg;
6014
6015        # cut out closing script tags
6016        $Code =~ s{
6017            (?:-->\s*)?
6018            (?://\s*\]\]>\s*)?
6019            </script>
6020        }{}smxg;
6021
6022    }
6023    return $Code;
6024}
6025
6026=head2 WrapPlainText()
6027
6028This sub has two main functionalities:
60291. Check every line and make sure that "\n" is the ending of the line.
60302. If the line does _not_ start with ">" (e.g. not cited text)
6031wrap it after the number of "MaxCharacters" (e.g. if MaxCharacters is "80" wrap after 80 characters).
6032Do this _just_ if the line, that should be wrapped, contains space characters at which the line can be wrapped.
6033
6034If you need more info to understand what it does, take a look at the UnitTest WrapPlainText.t to see
6035use cases there.
6036
6037my $WrappedPlainText = $LayoutObject->WrapPlainText(
6038    PlainText     => "Some Plain text that is longer than the amount stored in MaxCharacters",
6039    MaxCharacters => 80,
6040);
6041
6042=cut
6043
6044sub WrapPlainText {
6045    my ( $Self, %Param ) = @_;
6046
6047    # Return if we did not get MaxCharacters
6048    # or MaxCharacters doesn't contain just an int
6049    if ( !IsPositiveInteger( $Param{MaxCharacters} ) ) {
6050        $Kernel::OM->Get('Kernel::System::Log')->Log(
6051            Priority => 'error',
6052            Message  => "Got no or invalid MaxCharacters!",
6053        );
6054        return;
6055    }
6056
6057    # Return if we didn't get PlainText
6058    if ( !defined $Param{PlainText} ) {
6059        return;
6060    }
6061
6062    # Return if we got no Scalar
6063    if ( ref $Param{PlainText} ) {
6064        $Kernel::OM->Get('Kernel::System::Log')->Log(
6065            Priority => 'error',
6066            Message  => "Had no string in PlainText!",
6067        );
6068        return;
6069    }
6070
6071    # Return PlainText if we have less than MaxCharacters
6072    if ( length $Param{PlainText} < $Param{MaxCharacters} ) {
6073        return $Param{PlainText};
6074    }
6075
6076    my $WorkString = $Param{PlainText};
6077
6078    # Normalize line endings to avoid problems with \r\n (bug#11078).
6079    $WorkString =~ s/\r\n?/\n/g;
6080    $WorkString =~ s/(^>.+|.{4,$Param{MaxCharacters}})(?:\s|\z)/$1\n/gm;
6081    return $WorkString;
6082}
6083
6084=head2 SetRichTextParameters()
6085
6086set properties for rich text editor and send them to JS via AddJSData()
6087
6088$LayoutObject->SetRichTextParameters(
6089    Data => \%Param,
6090);
6091
6092=cut
6093
6094sub SetRichTextParameters {
6095    my ( $Self, %Param ) = @_;
6096
6097    $Param{Data} ||= {};
6098
6099    # get and check param Data
6100    if ( ref $Param{Data} ne 'HASH' ) {
6101        $Kernel::OM->Get('Kernel::System::Log')->Log(
6102            Priority => 'error',
6103            Message  => "Need HashRef in Param Data! Got: '" . ref( $Param{Data} ) . "'!",
6104        );
6105        $Self->FatalError();
6106    }
6107
6108    # get needed objects
6109    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
6110    my $ConfigObject   = $Kernel::OM->Get('Kernel::Config');
6111
6112    # get needed variables
6113    my $ScreenRichTextHeight = $Param{Data}->{RichTextHeight} || $ConfigObject->Get("Frontend::RichTextHeight");
6114    my $ScreenRichTextWidth  = $Param{Data}->{RichTextWidth}  || $ConfigObject->Get("Frontend::RichTextWidth");
6115    my $RichTextType         = $Param{Data}->{RichTextType}   || '';
6116    my $PictureUploadAction = $Param{Data}->{RichTextPictureUploadAction} || '';
6117    my $TextDir             = $Self->{TextDirection}                      || '';
6118    my $EditingAreaCSS      = 'body.cke_editable { ' . $ConfigObject->Get("Frontend::RichText::DefaultCSS") . ' }';
6119
6120    # decide if we need to use the enhanced mode (with tables)
6121    my @Toolbar;
6122    my @ToolbarWithoutImage;
6123
6124    if ( $RichTextType eq 'CodeMirror' ) {
6125        @Toolbar = @ToolbarWithoutImage = [
6126            [ 'autoFormat', 'CommentSelectedRange', 'UncommentSelectedRange', 'AutoComplete' ],
6127            [ 'Find',       'Replace',              '-',                      'SelectAll' ],
6128            ['Maximize'],
6129        ];
6130    }
6131    elsif ( $ConfigObject->Get("Frontend::RichText::EnhancedMode") == '1' ) {
6132        @Toolbar = [
6133            [
6134                'Bold',   'Italic',       'Underline',    'Strike',        'Subscript',    'Superscript',
6135                '-',      'NumberedList', 'BulletedList', 'Table',         '-',            'Outdent',
6136                'Indent', '-',            'JustifyLeft',  'JustifyCenter', 'JustifyRight', 'JustifyBlock',
6137                '-',      'Link',         'Unlink',       'Undo',          'Redo',         'SelectAll'
6138            ],
6139            '/',
6140            [
6141                'Image',   'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote',
6142                '-',       '-',              'Find',      'Replace',       'TextColor',
6143                'BGColor', 'RemoveFormat',   '-',         'ShowBlocks',    'Source',     'SpecialChar',
6144                '-',       'Maximize'
6145            ],
6146            [ 'Format', 'Font', 'FontSize' ]
6147        ];
6148        @ToolbarWithoutImage = [
6149            [
6150                'Bold',   'Italic',       'Underline',    'Strike',        'Subscript',    'Superscript',
6151                '-',      'NumberedList', 'BulletedList', 'Table',         '-',            'Outdent',
6152                'Indent', '-',            'JustifyLeft',  'JustifyCenter', 'JustifyRight', 'JustifyBlock',
6153                '-',      'Link',         'Unlink',       'Undo',          'Redo',         'SelectAll'
6154            ],
6155            '/',
6156            [
6157                'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote', '-',
6158                '-',              'Find',      'Replace',       'TextColor',  'BGColor',
6159                'RemoveFormat',   '-',         'ShowBlocks',    'Source',     'SpecialChar', '-',
6160                'Maximize'
6161            ],
6162            [ 'Format', 'Font', 'FontSize' ]
6163        ];
6164    }
6165    else {
6166        @Toolbar = [
6167            [
6168                'Bold',          'Italic',       'Underline',      'Strike', '-',    'NumberedList',
6169                'BulletedList',  '-',            'Outdent',        'Indent', '-',    'JustifyLeft',
6170                'JustifyCenter', 'JustifyRight', 'JustifyBlock',   '-',      'Link', 'Unlink',
6171                '-',             'Image',        'HorizontalRule', '-',      'Undo', 'Redo',
6172                '-',             'Find'
6173            ],
6174            '/',
6175            [
6176                'Format',       'Font', 'FontSize', '-',           'TextColor',  'BGColor',
6177                'RemoveFormat', '-',    'Source',   'SpecialChar', 'SplitQuote', 'RemoveQuote',
6178                '-',            'Maximize'
6179            ]
6180        ];
6181        @ToolbarWithoutImage = [
6182            [
6183                'Bold',          'Italic',       'Underline',    'Strike',
6184                '-',             'NumberedList', 'BulletedList', '-',
6185                'Outdent',       'Indent',       '-',            'JustifyLeft',
6186                'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-',
6187                'Link',          'Unlink',       '-',            'HorizontalRule',
6188                '-',             'Undo',         'Redo',         '-',
6189                'Find'
6190            ],
6191            '/',
6192            [
6193                'Format',       'Font', 'FontSize', '-',           'TextColor',  'BGColor',
6194                'RemoveFormat', '-',    'Source',   'SpecialChar', 'SplitQuote', 'RemoveQuote',
6195                '-',            'Maximize'
6196            ]
6197        ];
6198    }
6199
6200    # set data with AddJSData()
6201    $Self->AddJSData(
6202        Key   => 'RichText',
6203        Value => {
6204            Height         => $ScreenRichTextHeight,
6205            Width          => $ScreenRichTextWidth,
6206            TextDir        => $TextDir,
6207            EditingAreaCSS => $EditingAreaCSS,
6208            Lang           => {
6209                SplitQuote  => $LanguageObject->Translate('Split Quote'),
6210                RemoveQuote => $LanguageObject->Translate('Remove Quote'),
6211            },
6212            Toolbar             => $Toolbar[0],
6213            ToolbarWithoutImage => $ToolbarWithoutImage[0],
6214            PictureUploadAction => $PictureUploadAction,
6215            Type                => $RichTextType,
6216        },
6217    );
6218
6219    return 1;
6220}
6221
6222=head2 CustomerSetRichTextParameters()
6223
6224set properties for customer rich text editor and send them to JS via AddJSData()
6225
6226$LayoutObject->CustomerSetRichTextParameters(
6227    Data => \%Param,
6228);
6229
6230=cut
6231
6232sub CustomerSetRichTextParameters {
6233    my ( $Self, %Param ) = @_;
6234
6235    $Param{Data} ||= {};
6236
6237    # get and check param Data
6238    if ( ref $Param{Data} ne 'HASH' ) {
6239        $Kernel::OM->Get('Kernel::System::Log')->Log(
6240            Priority => 'error',
6241            Message  => "Need HashRef in Param Data! Got: '" . ref( $Param{Data} ) . "'!",
6242        );
6243        $Self->FatalError();
6244    }
6245
6246    # get needed objects
6247    my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
6248    my $ConfigObject   = $Kernel::OM->Get('Kernel::Config');
6249
6250    my $ScreenRichTextHeight = $ConfigObject->Get("Frontend::RichTextHeight");
6251    my $ScreenRichTextWidth  = $ConfigObject->Get("Frontend::RichTextWidth");
6252    my $TextDir              = $Self->{TextDirection} || '';
6253    my $PictureUploadAction  = $Param{Data}->{RichTextPictureUploadAction} || '';
6254    my $EditingAreaCSS       = 'body { ' . $ConfigObject->Get("Frontend::RichText::DefaultCSS") . ' }';
6255
6256    # decide if we need to use the enhanced mode (with tables)
6257    my @Toolbar;
6258    my @ToolbarWithoutImage;
6259
6260    if ( $ConfigObject->Get("Frontend::RichText::EnhancedMode::Customer") == '1' ) {
6261        @Toolbar = [
6262            [
6263                'Bold',   'Italic',       'Underline',    'Strike',        'Subscript',    'Superscript',
6264                '-',      'NumberedList', 'BulletedList', 'Table',         '-',            'Outdent',
6265                'Indent', '-',            'JustifyLeft',  'JustifyCenter', 'JustifyRight', 'JustifyBlock',
6266                '-',      'Link',         'Unlink',       'Undo',          'Redo',         'SelectAll'
6267            ],
6268            '/',
6269            [
6270                'Image',   'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote',
6271                '-',       '-',              'Find',      'Replace',       'TextColor',
6272                'BGColor', 'RemoveFormat',   '-',         'ShowBlocks',    'Source',     'SpecialChar',
6273                '-',       'Maximize'
6274            ],
6275            [ 'Format', 'Font', 'FontSize' ]
6276        ];
6277        @ToolbarWithoutImage = [
6278            [
6279                'Bold',   'Italic',       'Underline',    'Strike',        'Subscript',    'Superscript',
6280                '-',      'NumberedList', 'BulletedList', 'Table',         '-',            'Outdent',
6281                'Indent', '-',            'JustifyLeft',  'JustifyCenter', 'JustifyRight', 'JustifyBlock',
6282                '-',      'Link',         'Unlink',       'Undo',          'Redo',         'SelectAll'
6283            ],
6284            '/',
6285            [
6286                'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote', '-',
6287                '-',              'Find',      'Replace',       'TextColor',  'BGColor',
6288                'RemoveFormat',   '-',         'ShowBlocks',    'Source',     'SpecialChar', '-',
6289                'Maximize'
6290            ],
6291            [ 'Format', 'Font', 'FontSize' ]
6292        ];
6293    }
6294    else {
6295        @Toolbar = [
6296            [
6297                'Bold',          'Italic',       'Underline',      'Strike', '-',    'NumberedList',
6298                'BulletedList',  '-',            'Outdent',        'Indent', '-',    'JustifyLeft',
6299                'JustifyCenter', 'JustifyRight', 'JustifyBlock',   '-',      'Link', 'Unlink',
6300                '-',             'Image',        'HorizontalRule', '-',      'Undo', 'Redo',
6301                '-',             'Find'
6302            ],
6303            '/',
6304            [
6305                'Format',       'Font', 'FontSize', '-',           'TextColor',  'BGColor',
6306                'RemoveFormat', '-',    'Source',   'SpecialChar', 'SplitQuote', 'RemoveQuote',
6307                '-',            'Maximize'
6308            ]
6309        ];
6310        @ToolbarWithoutImage = [
6311            [
6312                'Bold',          'Italic',       'Underline',    'Strike',
6313                '-',             'NumberedList', 'BulletedList', '-',
6314                'Outdent',       'Indent',       '-',            'JustifyLeft',
6315                'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-',
6316                'Link',          'Unlink',       '-',            'HorizontalRule',
6317                '-',             'Undo',         'Redo',         '-',
6318                'Find'
6319            ],
6320            '/',
6321            [
6322                'Format',       'Font', 'FontSize', '-',           'TextColor',  'BGColor',
6323                'RemoveFormat', '-',    'Source',   'SpecialChar', 'SplitQuote', 'RemoveQuote',
6324                '-',            'Maximize'
6325            ]
6326        ];
6327    }
6328
6329    # set data with AddJSData()
6330    $Self->AddJSData(
6331        Key   => 'RichText',
6332        Value => {
6333            Height         => $ScreenRichTextHeight,
6334            Width          => $ScreenRichTextWidth,
6335            TextDir        => $TextDir,
6336            EditingAreaCSS => $EditingAreaCSS,
6337            Lang           => {
6338                SplitQuote => $LanguageObject->Translate('Split Quote'),
6339            },
6340            Toolbar             => $Toolbar[0],
6341            ToolbarWithoutImage => $ToolbarWithoutImage[0],
6342            PictureUploadAction => $PictureUploadAction,
6343        },
6344    );
6345
6346    return 1;
6347}
6348
6349=head2 UserInitialsGet()
6350
6351Get initials from a full name of a user.
6352
6353    my $UserInitials = $LayoutObject->UserInitialsGet(
6354        Fullname => 'John Doe',
6355    );
6356
6357Returns string of exactly two uppercase characters that represent user initials:
6358
6359    $UserInitials = 'JD';
6360
6361Please note that this function will return 'O' if invalid name (without any word characters) was supplied.
6362
6363=cut
6364
6365sub UserInitialsGet {
6366    my ( $Self, %Param ) = @_;
6367
6368    # Fallback in case name is invalid.
6369    my $UserInitials = 'O';
6370    return $UserInitials if !$Param{Fullname};
6371
6372    # Remove anything found in brackets (email address, etc).
6373    my $Fullname = $Param{Fullname} =~ s/[<[{(].*[>\]})]//r;
6374
6375    # Trim whitespaces.
6376    $Fullname =~ s/^\s+|\s+$//g;
6377
6378    # Split full name by whitespace.
6379    my @UserNames = split /\s+/, $Fullname;
6380    if (@UserNames) {
6381
6382        # Cleanup unnecessary characters.
6383        my $FirstName = $UserNames[0] =~ s/\W//gr;
6384        return $UserInitials if !$FirstName;
6385
6386        # Get first character of first name.
6387        $UserInitials = uc substr $FirstName, 0, 1;
6388
6389        if ( @UserNames > 1 ) {
6390
6391            # Cleanup unnecessary characters.
6392            my $LastName = $UserNames[-1] =~ s/\W//gr;
6393            return $UserInitials if !$LastName;
6394
6395            # Get first character of last name.
6396            $UserInitials .= uc substr $LastName, 0, 1;
6397        }
6398    }
6399
6400    return $UserInitials;
6401}
6402
64031;
6404
6405=end Internal:
6406
6407=head1 TERMS AND CONDITIONS
6408
6409This software is part of the OTRS project (L<https://otrs.org/>).
6410
6411This software comes with ABSOLUTELY NO WARRANTY. For details, see
6412the enclosed file COPYING for license information (GPL). If you
6413did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
6414
6415=cut
6416