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< > 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/&/&/g; 1916 ${$Text} =~ s/</</g; 1917 ${$Text} =~ s/>/>/g; 1918 ${$Text} =~ s/"/"/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/ / /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 /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 . "/> "; 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 => ' 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 ' ' 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 => ' 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