6400 lines
208 KiB
Perl
6400 lines
208 KiB
Perl
# --
|
|
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
|
# --
|
|
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
|
# the enclosed file COPYING for license information (GPL). If you
|
|
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
|
# --
|
|
|
|
package Kernel::Output::HTML::Layout;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use URI::Escape qw();
|
|
use Digest::MD5 qw(md5_hex);
|
|
|
|
use Kernel::System::VariableCheck qw(:all);
|
|
use Kernel::Language qw(Translatable);
|
|
|
|
our @ObjectDependencies = (
|
|
'Kernel::Config',
|
|
'Kernel::Language',
|
|
'Kernel::System::AuthSession',
|
|
'Kernel::System::Cache',
|
|
'Kernel::System::Chat',
|
|
'Kernel::System::CustomerGroup',
|
|
'Kernel::System::DateTime',
|
|
'Kernel::System::Group',
|
|
'Kernel::System::Encode',
|
|
'Kernel::System::HTMLUtils',
|
|
'Kernel::System::JSON',
|
|
'Kernel::System::Log',
|
|
'Kernel::System::Main',
|
|
'Kernel::System::OTRSBusiness',
|
|
'Kernel::System::State',
|
|
'Kernel::System::Storable',
|
|
'Kernel::System::SystemMaintenance',
|
|
'Kernel::System::User',
|
|
'Kernel::System::VideoChat',
|
|
'Kernel::System::Web::Request',
|
|
);
|
|
|
|
=head1 NAME
|
|
|
|
Kernel::Output::HTML::Layout - all generic html functions
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
All generic html functions. E. g. to get options fields, template processing, ...
|
|
|
|
=head1 PUBLIC INTERFACE
|
|
|
|
=head2 new()
|
|
|
|
create a new object. Do not use it directly, instead use:
|
|
|
|
use Kernel::System::ObjectManager;
|
|
local $Kernel::OM = Kernel::System::ObjectManager->new(
|
|
'Kernel::Output::HTML::Layout' => {
|
|
Lang => 'de',
|
|
},
|
|
);
|
|
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
|
|
|
From the web installer, a special Option C<InstallerOnly> is passed
|
|
to indicate that a database connection is not yet available.
|
|
|
|
use Kernel::System::ObjectManager;
|
|
local $Kernel::OM = Kernel::System::ObjectManager->new(
|
|
'Kernel::Output::HTML::Layout' => {
|
|
InstallerOnly => 1,
|
|
},
|
|
);
|
|
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
|
|
|
=cut
|
|
|
|
sub new {
|
|
my ( $Type, %Param ) = @_;
|
|
|
|
# allocate new hash for object
|
|
my $Self = {%Param};
|
|
bless( $Self, $Type );
|
|
|
|
# set debug
|
|
$Self->{Debug} = 0;
|
|
|
|
# reset block data
|
|
delete $Self->{BlockData};
|
|
|
|
# empty action if not defined
|
|
$Self->{Action} = '' if !defined $Self->{Action};
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# get/set some common params
|
|
if ( !$Self->{UserTheme} ) {
|
|
$Self->{UserTheme} = $ConfigObject->Get('DefaultTheme');
|
|
}
|
|
|
|
$Self->{UserTimeZone} ||= Kernel::System::DateTime->UserDefaultTimeZoneGet();
|
|
|
|
# Determine the language to use based on the browser setting, if there
|
|
# is none yet.
|
|
if ( !$Self->{UserLanguage} ) {
|
|
my @BrowserLanguages = split /\s*,\s*/, $Self->{Lang} || $ENV{HTTP_ACCEPT_LANGUAGE} || '';
|
|
my %Data = %{ $ConfigObject->Get('DefaultUsedLanguages') };
|
|
LANGUAGE:
|
|
for my $BrowserLang (@BrowserLanguages) {
|
|
for my $Language ( reverse sort keys %Data ) {
|
|
|
|
# check xx_XX and xx-XX type
|
|
my $LanguageOtherType = $Language;
|
|
$LanguageOtherType =~ s/_/-/;
|
|
if ( $BrowserLang =~ /^($Language|$LanguageOtherType)/i ) {
|
|
$Self->{UserLanguage} = $Language;
|
|
last LANGUAGE;
|
|
}
|
|
}
|
|
if ( !$Self->{UserLanguage} ) {
|
|
for my $Language ( reverse sort keys %Data ) {
|
|
|
|
# If Browser requests 'vi', also offer 'vi_VI' even though we don't have 'vi'
|
|
if ( $Language =~ m/^$BrowserLang/smxi ) {
|
|
$Self->{UserLanguage} = $Language;
|
|
last LANGUAGE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$Self->{UserLanguage} ||= $ConfigObject->Get('DefaultLanguage') || 'en';
|
|
}
|
|
|
|
# create language object
|
|
if ( !$Self->{LanguageObject} ) {
|
|
$Kernel::OM->ObjectParamAdd(
|
|
'Kernel::Language' => {
|
|
UserTimeZone => $Self->{UserTimeZone},
|
|
UserLanguage => $Self->{UserLanguage},
|
|
Action => $Self->{Action},
|
|
},
|
|
);
|
|
$Self->{LanguageObject} = $Kernel::OM->Get('Kernel::Language');
|
|
}
|
|
|
|
# set charset if there is no charset given
|
|
$Self->{UserCharset} = 'utf-8';
|
|
$Self->{Charset} = $Self->{UserCharset}; # just for compat.
|
|
$Self->{SessionID} = $Param{SessionID} || '';
|
|
$Self->{SessionName} = $Param{SessionName} || 'SessionID';
|
|
$Self->{CGIHandle} = $ENV{SCRIPT_NAME} || 'No-$ENV{"SCRIPT_NAME"}';
|
|
|
|
# baselink
|
|
$Self->{Baselink} = $Self->{CGIHandle} . '?';
|
|
$Self->{Time} = $Self->{LanguageObject}->Time(
|
|
Action => 'GET',
|
|
Format => 'DateFormat',
|
|
);
|
|
$Self->{TimeLong} = $Self->{LanguageObject}->Time(
|
|
Action => 'GET',
|
|
Format => 'DateFormatLong',
|
|
);
|
|
|
|
# set text direction
|
|
$Self->{TextDirection} = $Self->{LanguageObject}->{TextDirection};
|
|
|
|
# check Frontend::Output::FilterElementPost
|
|
$Self->{FilterElementPost} = {};
|
|
|
|
my %FilterElementPost = %{ $ConfigObject->Get('Frontend::Output::FilterElementPost') // {} };
|
|
|
|
FILTER:
|
|
for my $Filter ( sort keys %FilterElementPost ) {
|
|
|
|
# extract filter config
|
|
my $FilterConfig = $FilterElementPost{$Filter};
|
|
|
|
next FILTER if !$FilterConfig || ref $FilterConfig ne 'HASH';
|
|
|
|
# extract template list
|
|
my %TemplateList = %{ $FilterConfig->{Templates} || {} };
|
|
|
|
if ( !%TemplateList || $TemplateList{ALL} ) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => <<EOF,
|
|
$FilterConfig->{Module} will be ignored because it wants to operate on all templates or does not specify a template list.
|
|
EOF
|
|
);
|
|
|
|
next FILTER;
|
|
}
|
|
|
|
$Self->{FilterElementPost}->{$Filter} = $FilterElementPost{$Filter};
|
|
}
|
|
|
|
# check Frontend::Output::FilterContent
|
|
$Self->{FilterContent} = $ConfigObject->Get('Frontend::Output::FilterContent');
|
|
|
|
# check Frontend::Output::FilterText
|
|
$Self->{FilterText} = $ConfigObject->Get('Frontend::Output::FilterText');
|
|
|
|
# check browser
|
|
$Self->{Browser} = 'Unknown';
|
|
$Self->{BrowserVersion} = 0;
|
|
$Self->{Platform} = '';
|
|
$Self->{IsMobile} = 0;
|
|
|
|
$Self->{BrowserJavaScriptSupport} = 1;
|
|
$Self->{BrowserRichText} = 1;
|
|
|
|
my $HttpUserAgent = ( defined $ENV{HTTP_USER_AGENT} ? lc $ENV{HTTP_USER_AGENT} : '' );
|
|
|
|
if ( !$HttpUserAgent ) {
|
|
$Self->{Browser} = 'Unknown - no $ENV{"HTTP_USER_AGENT"}';
|
|
}
|
|
elsif ($HttpUserAgent) {
|
|
|
|
# check, if we are on a mobile platform.
|
|
# tablets are handled like desktops
|
|
# only phones are "mobile"
|
|
if ( $HttpUserAgent =~ /mobile/ ) {
|
|
$Self->{IsMobile} = 1;
|
|
}
|
|
|
|
# android
|
|
if ( $HttpUserAgent =~ /android/ ) {
|
|
$Self->{Platform} = 'Android';
|
|
}
|
|
|
|
# edge / spartan
|
|
if ( $HttpUserAgent =~ /edge/ ) {
|
|
$Self->{Browser} = 'Edge';
|
|
}
|
|
|
|
# msie
|
|
elsif (
|
|
$HttpUserAgent =~ /msie\s([0-9.]+)/
|
|
|| $HttpUserAgent =~ /internet\sexplorer\/([0-9.]+)/
|
|
)
|
|
{
|
|
$Self->{Browser} = 'MSIE';
|
|
|
|
if ( $1 =~ /(\d+)\.(\d+)/ ) {
|
|
$Self->{BrowserMajorVersion} = $1;
|
|
$Self->{BrowserMinorVersion} = $2;
|
|
}
|
|
|
|
# older windows mobile phones (until IE9), that still have 'MSIE' in the user agent string
|
|
if ( $Self->{IsMobile} ) {
|
|
$Self->{Platform} = 'Windows Phone';
|
|
}
|
|
}
|
|
|
|
# mobile ie
|
|
elsif ( $HttpUserAgent =~ /iemobile/ ) {
|
|
$Self->{Browser} = 'MSIE';
|
|
$Self->{Platform} = 'Windows Phone';
|
|
}
|
|
|
|
# mobile ie (second try)
|
|
elsif ( $HttpUserAgent =~ /trident/ ) {
|
|
$Self->{Browser} = 'MSIE';
|
|
|
|
if ( $HttpUserAgent =~ /rv:([0-9])+\.([0-9])+/ ) {
|
|
$Self->{BrowserMajorVersion} = $2;
|
|
$Self->{BrowserMinorVersion} = $3;
|
|
}
|
|
}
|
|
|
|
# iOS
|
|
elsif ( $HttpUserAgent =~ /(ipad|iphone|ipod)/ ) {
|
|
$Self->{Platform} = 'iOS';
|
|
$Self->{Browser} = 'Safari';
|
|
|
|
if ( $HttpUserAgent =~ /(ipad|iphone|ipod);.*cpu.*os ([0-9]+)_/ ) {
|
|
$Self->{BrowserVersion} = $2;
|
|
}
|
|
|
|
if ( $HttpUserAgent =~ /crios/ ) {
|
|
$Self->{Browser} = 'Chrome';
|
|
}
|
|
|
|
# RichText is supported in iOS6+.
|
|
if ( $Self->{BrowserVersion} >= 6 ) {
|
|
$Self->{BrowserRichText} = 1;
|
|
}
|
|
else {
|
|
$Self->{BrowserRichText} = 0;
|
|
}
|
|
}
|
|
|
|
# safari
|
|
elsif ( $HttpUserAgent =~ /safari/ ) {
|
|
|
|
# chrome
|
|
if ( $HttpUserAgent =~ /chrome/ ) {
|
|
$Self->{Browser} = 'Chrome';
|
|
}
|
|
else {
|
|
$Self->{Browser} = 'Safari';
|
|
}
|
|
}
|
|
|
|
# konqueror
|
|
elsif ( $HttpUserAgent =~ /konqueror/ ) {
|
|
$Self->{Browser} = 'Konqueror';
|
|
|
|
# on konquerer disable rich text editor
|
|
$Self->{BrowserRichText} = 0;
|
|
}
|
|
|
|
# firefox
|
|
elsif ( $HttpUserAgent =~ /firefox/ ) {
|
|
$Self->{Browser} = 'Firefox';
|
|
}
|
|
|
|
# opera
|
|
elsif ( $HttpUserAgent =~ /^opera.*/ ) {
|
|
$Self->{Browser} = 'Opera';
|
|
}
|
|
|
|
# netscape
|
|
elsif ( $HttpUserAgent =~ /netscape/ ) {
|
|
$Self->{Browser} = 'Netscape';
|
|
}
|
|
|
|
# w3m
|
|
elsif ( $HttpUserAgent =~ /^w3m.*/ ) {
|
|
$Self->{Browser} = 'w3m';
|
|
$Self->{BrowserJavaScriptSupport} = 0;
|
|
}
|
|
|
|
# lynx
|
|
elsif ( $HttpUserAgent =~ /^lynx.*/ ) {
|
|
$Self->{Browser} = 'Lynx';
|
|
$Self->{BrowserJavaScriptSupport} = 0;
|
|
}
|
|
|
|
# links
|
|
elsif ( $HttpUserAgent =~ /^links.*/ ) {
|
|
$Self->{Browser} = 'Links';
|
|
}
|
|
else {
|
|
$Self->{Browser} = 'Unknown - ' . $HttpUserAgent;
|
|
}
|
|
}
|
|
|
|
# check mobile devices to disable richtext support
|
|
if (
|
|
$Self->{IsMobile}
|
|
&& $Self->{Platform} ne 'iOS'
|
|
&& $Self->{Platform} ne 'Android'
|
|
&& $Self->{Platform} ne 'Windows Phone'
|
|
)
|
|
{
|
|
$Self->{BrowserRichText} = 0;
|
|
}
|
|
|
|
# check if rich text can be active
|
|
if ( !$Self->{BrowserJavaScriptSupport} || !$Self->{BrowserRichText} ) {
|
|
$ConfigObject->Set(
|
|
Key => 'Frontend::RichText',
|
|
Value => 0,
|
|
);
|
|
}
|
|
|
|
# check if rich text is active
|
|
if ( !$ConfigObject->Get('Frontend::RichText') ) {
|
|
$Self->{BrowserRichText} = 0;
|
|
}
|
|
|
|
# load theme
|
|
my $Theme = $Self->{UserTheme} || $ConfigObject->Get('DefaultTheme') || Translatable('Standard');
|
|
|
|
# force a theme based on host name
|
|
my $DefaultThemeHostBased = $ConfigObject->Get('DefaultTheme::HostBased');
|
|
if ( $DefaultThemeHostBased && $ENV{HTTP_HOST} ) {
|
|
|
|
THEME:
|
|
for my $RegExp ( sort keys %{$DefaultThemeHostBased} ) {
|
|
|
|
# do not use empty regexp or theme directories
|
|
next THEME if !$RegExp;
|
|
next THEME if $RegExp eq '';
|
|
next THEME if !$DefaultThemeHostBased->{$RegExp};
|
|
|
|
# check if regexp is matching
|
|
if ( $ENV{HTTP_HOST} =~ /$RegExp/i ) {
|
|
$Theme = $DefaultThemeHostBased->{$RegExp};
|
|
}
|
|
}
|
|
}
|
|
|
|
# locate template files
|
|
$Self->{TemplateDir} = $ConfigObject->Get('TemplateDir') . '/HTML/Templates/' . $Theme;
|
|
$Self->{StandardTemplateDir} = $ConfigObject->Get('TemplateDir') . '/HTML/Templates/' . 'Standard';
|
|
|
|
# Check if 'Standard' fallback exists
|
|
if ( !-e $Self->{StandardTemplateDir} ) {
|
|
$Self->FatalDie(
|
|
Message =>
|
|
"No existing template directory found ('$Self->{TemplateDir}')! Check your Home in Kernel/Config.pm."
|
|
);
|
|
}
|
|
|
|
if ( !-e $Self->{TemplateDir} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message =>
|
|
"No existing template directory found ('$Self->{TemplateDir}')!.
|
|
Default theme used instead.",
|
|
);
|
|
|
|
# Set TemplateDir to 'Standard' as a fallback.
|
|
$Theme = 'Standard';
|
|
$Self->{TemplateDir} = $Self->{StandardTemplateDir};
|
|
}
|
|
|
|
$Self->{CustomTemplateDir} = $ConfigObject->Get('CustomTemplateDir') . '/HTML/Templates/' . $Theme;
|
|
$Self->{CustomStandardTemplateDir} = $ConfigObject->Get('CustomTemplateDir') . '/HTML/Templates/' . 'Standard';
|
|
|
|
# get main object
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
# load sub layout files
|
|
my $NewDir = $ConfigObject->Get('TemplateDir') . '/HTML/Layout';
|
|
if ( -e $NewDir ) {
|
|
my @NewFiles = $MainObject->DirectoryRead(
|
|
Directory => $NewDir,
|
|
Filter => '*.pm',
|
|
);
|
|
for my $NewFile (@NewFiles) {
|
|
if ( $NewFile !~ /Layout.pm$/ ) {
|
|
$NewFile =~ s{\A.*\/(.+?).pm\z}{$1}xms;
|
|
my $NewClassName = "Kernel::Output::HTML::Layout::$NewFile";
|
|
if ( !$MainObject->RequireBaseClass($NewClassName) ) {
|
|
$Self->FatalDie(
|
|
Message => "Could not load class Kernel::Output::HTML::Layout::$NewFile.",
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $Self->{SessionID} && $Self->{UserChallengeToken} ) {
|
|
$Self->{ChallengeTokenParam} = "ChallengeToken=$Self->{UserChallengeToken};";
|
|
}
|
|
|
|
# load NavigationModule if defined
|
|
if ( $Self->{ModuleReg} ) {
|
|
my $NavigationModule = $Kernel::OM->Get('Kernel::Config')->Get("Frontend::NavigationModule");
|
|
if ( $NavigationModule->{ $Param{Action} } ) {
|
|
$Self->{NavigationModule} = $NavigationModule->{ $Param{Action} };
|
|
}
|
|
}
|
|
|
|
return $Self;
|
|
}
|
|
|
|
sub SetEnv {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
for (qw(Key Value)) {
|
|
if ( !defined $Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
}
|
|
$Self->{EnvNewRef}->{ $Param{Key} } = $Param{Value};
|
|
return 1;
|
|
}
|
|
|
|
=head2 Block()
|
|
|
|
call a block and pass data to it (optional) to generate the block's output.
|
|
|
|
$LayoutObject->Block(
|
|
Name => 'Row',
|
|
Data => {
|
|
Time => ...,
|
|
},
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Block {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
if ( !$Param{Name} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Name!'
|
|
);
|
|
return;
|
|
}
|
|
push @{ $Self->{BlockData} },
|
|
{
|
|
Name => $Param{Name},
|
|
Data => $Param{Data},
|
|
};
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 JSONEncode()
|
|
|
|
Encode perl data structure to JSON string
|
|
|
|
my $JSON = $LayoutObject->JSONEncode(
|
|
Data => $Data,
|
|
NoQuotes => 0|1, # optional: no double quotes at the start and the end of JSON string
|
|
);
|
|
|
|
=cut
|
|
|
|
sub JSONEncode {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check for needed data
|
|
return if !defined $Param{Data};
|
|
|
|
# get JSON encoded data
|
|
my $JSON = $Kernel::OM->Get('Kernel::System::JSON')->Encode(
|
|
Data => $Param{Data},
|
|
) || '""';
|
|
|
|
# remove trailing and trailing double quotes if requested
|
|
if ( $Param{NoQuotes} ) {
|
|
$JSON =~ s{ \A "(.*)" \z }{$1}smx;
|
|
}
|
|
|
|
return $JSON;
|
|
}
|
|
|
|
=head2 Redirect()
|
|
|
|
return html for browser to redirect
|
|
|
|
my $HTML = $LayoutObject->Redirect(
|
|
OP => "Action=AdminUserGroup;Subaction=User;ID=$UserID",
|
|
);
|
|
|
|
my $HTML = $LayoutObject->Redirect(
|
|
ExtURL => "http://some.example.com/",
|
|
);
|
|
|
|
During login action, C<Login => 1> should be passed to Redirect(),
|
|
which indicates that if the browser has cookie support, it is OK
|
|
for the session cookie to be not yet set.
|
|
|
|
=cut
|
|
|
|
sub Redirect {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# add cookies if exists
|
|
my $Cookies = '';
|
|
if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
|
|
for ( sort keys %{ $Self->{SetCookies} } ) {
|
|
$Cookies .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
|
|
}
|
|
}
|
|
|
|
# create & return output
|
|
if ( $Param{ExtURL} ) {
|
|
|
|
# external redirect
|
|
$Param{Redirect} = $Param{ExtURL};
|
|
return $Cookies
|
|
. $Self->Output(
|
|
TemplateFile => 'Redirect',
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
# set baselink
|
|
$Param{Redirect} = $Self->{Baselink};
|
|
|
|
if ( $Param{OP} ) {
|
|
|
|
# Filter out hazardous characters
|
|
if ( $Param{OP} =~ s{\x00}{}smxg ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Someone tries to use a null bytes (\x00) character in redirect!',
|
|
);
|
|
}
|
|
|
|
if ( $Param{OP} =~ s{\r}{}smxg ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Someone tries to use a carriage return character in redirect!',
|
|
);
|
|
}
|
|
|
|
if ( $Param{OP} =~ s{\n}{}smxg ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Someone tries to use a newline character in redirect!',
|
|
);
|
|
}
|
|
|
|
# internal redirect
|
|
$Param{OP} =~ s/^.*\?(.+?)$/$1/;
|
|
$Param{Redirect} .= $Param{OP};
|
|
}
|
|
|
|
# check if IIS 6 is used, add absolute url for IIS workaround
|
|
# see also:
|
|
# o http://bugs.otrs.org/show_bug.cgi?id=2230
|
|
# o http://bugs.otrs.org/show_bug.cgi?id=9835
|
|
# o http://support.microsoft.com/default.aspx?scid=kb;en-us;221154
|
|
if ( $ENV{SERVER_SOFTWARE} =~ /^microsoft\-iis\/6/i ) {
|
|
my $Host = $ENV{HTTP_HOST} || $ConfigObject->Get('FQDN');
|
|
my $HttpType = $ConfigObject->Get('HttpType');
|
|
$Param{Redirect} = $HttpType . '://' . $Host . $Param{Redirect};
|
|
}
|
|
my $Output = $Cookies
|
|
. $Self->Output(
|
|
TemplateFile => 'Redirect',
|
|
Data => \%Param
|
|
);
|
|
|
|
# add session id to redirect if no cookie is enabled
|
|
if ( !$Self->{SessionIDCookie} && !( $Self->{BrowserHasCookie} && $Param{Login} ) ) {
|
|
|
|
# rewrite location header
|
|
$Output =~ s{
|
|
(location:\s)(.*)
|
|
}
|
|
{
|
|
my $Start = $1;
|
|
my $Target = $2;
|
|
my $End = '';
|
|
if ($Target =~ /^(.+?)#(|.+?)$/) {
|
|
$Target = $1;
|
|
$End = "#$2";
|
|
}
|
|
if ($Target =~ /http/i || !$Self->{SessionID}) {
|
|
"$Start$Target$End";
|
|
}
|
|
else {
|
|
if ($Target =~ /(\?|&)$/) {
|
|
"$Start$Target$Self->{SessionName}=$Self->{SessionID}$End";
|
|
}
|
|
elsif ($Target !~ /\?/) {
|
|
"$Start$Target?$Self->{SessionName}=$Self->{SessionID}$End";
|
|
}
|
|
elsif ($Target =~ /\?/) {
|
|
"$Start$Target&$Self->{SessionName}=$Self->{SessionID}$End";
|
|
}
|
|
else {
|
|
"$Start$Target?&$Self->{SessionName}=$Self->{SessionID}$End";
|
|
}
|
|
}
|
|
}iegx;
|
|
}
|
|
return $Output;
|
|
}
|
|
|
|
sub Login {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# set Action parameter for the loader
|
|
$Self->{Action} = 'Login';
|
|
$Param{IsLoginPage} = 1;
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
my $Output = '';
|
|
if ( $ConfigObject->Get('SessionUseCookie') ) {
|
|
|
|
# always set a cookie, so that at the time the user submits
|
|
# the password, we know already if the browser supports cookies.
|
|
# ( the session cookie isn't available at that time ).
|
|
my $CookieSecureAttribute = 0;
|
|
if ( $ConfigObject->Get('HttpType') eq 'https' ) {
|
|
|
|
# Restrict Cookie to HTTPS if it is used.
|
|
$CookieSecureAttribute = 1;
|
|
}
|
|
$Self->{SetCookies}->{OTRSBrowserHasCookie} = $Kernel::OM->Get('Kernel::System::Web::Request')->SetCookie(
|
|
Key => 'OTRSBrowserHasCookie',
|
|
Value => 1,
|
|
Expires => '+1y',
|
|
Path => $ConfigObject->Get('ScriptAlias'),
|
|
Secure => $CookieSecureAttribute,
|
|
HttpOnly => 1,
|
|
);
|
|
}
|
|
|
|
# add cookies if exists
|
|
if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
|
|
for ( sort keys %{ $Self->{SetCookies} } ) {
|
|
$Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
|
|
}
|
|
}
|
|
|
|
# get message of the day
|
|
if ( $ConfigObject->Get('ShowMotd') ) {
|
|
$Param{Motd} = $Self->Output(
|
|
TemplateFile => 'Motd',
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
# Generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
|
|
$Self->LoaderCreateAgentCSSCalls();
|
|
$Self->LoaderCreateAgentJSCalls();
|
|
$Self->LoaderCreateJavaScriptTranslationData();
|
|
$Self->LoaderCreateJavaScriptTemplateData();
|
|
|
|
my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
|
|
$Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
|
|
$Param{OTRSSTORMIsInstalled} = $OTRSBusinessObject->OTRSSTORMIsInstalled();
|
|
$Param{OTRSCONTROLIsInstalled} = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
|
|
|
|
# we need the baselink for VerfifiedGet() of selenium tests
|
|
$Self->AddJSData(
|
|
Key => 'Baselink',
|
|
Value => $Self->{Baselink},
|
|
);
|
|
|
|
# Add header logo, if configured
|
|
if ( defined $ConfigObject->Get('AgentLogo') ) {
|
|
my %AgentLogo = %{ $ConfigObject->Get('AgentLogo') };
|
|
my %Data;
|
|
|
|
for my $CSSStatement ( sort keys %AgentLogo ) {
|
|
if ( $CSSStatement eq 'URL' ) {
|
|
my $WebPath = '';
|
|
if ( $AgentLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
|
|
$WebPath = $ConfigObject->Get('Frontend::WebPath');
|
|
}
|
|
$Data{'URL'} = 'url(' . $WebPath . $AgentLogo{$CSSStatement} . ')';
|
|
}
|
|
else {
|
|
$Data{$CSSStatement} = $AgentLogo{$CSSStatement};
|
|
}
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => 'HeaderLogoCSS',
|
|
Data => \%Data,
|
|
);
|
|
}
|
|
|
|
# add login logo, if configured
|
|
if ( defined $ConfigObject->Get('AgentLoginLogo') ) {
|
|
my %AgentLoginLogo = %{ $ConfigObject->Get('AgentLoginLogo') };
|
|
my %Data;
|
|
|
|
for my $CSSStatement ( sort keys %AgentLoginLogo ) {
|
|
if ( $CSSStatement eq 'URL' ) {
|
|
my $WebPath = '';
|
|
if ( $AgentLoginLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
|
|
$WebPath = $ConfigObject->Get('Frontend::WebPath');
|
|
}
|
|
$Data{'URL'} = 'url(' . $WebPath . $AgentLoginLogo{$CSSStatement} . ')';
|
|
}
|
|
else {
|
|
$Data{$CSSStatement} = $AgentLoginLogo{$CSSStatement};
|
|
}
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => 'LoginLogoCSS',
|
|
Data => \%Data,
|
|
);
|
|
|
|
$Self->Block(
|
|
Name => 'LoginLogo'
|
|
);
|
|
}
|
|
|
|
# get system maintenance object
|
|
my $SystemMaintenanceObject = $Kernel::OM->Get('Kernel::System::SystemMaintenance');
|
|
|
|
my $ActiveMaintenance = $SystemMaintenanceObject->SystemMaintenanceIsActive();
|
|
|
|
# check if system maintenance is active
|
|
if ($ActiveMaintenance) {
|
|
my $SystemMaintenanceData = $SystemMaintenanceObject->SystemMaintenanceGet(
|
|
ID => $ActiveMaintenance,
|
|
UserID => 1,
|
|
);
|
|
|
|
if ( $SystemMaintenanceData->{ShowLoginMessage} ) {
|
|
|
|
my $LoginMessage =
|
|
$SystemMaintenanceData->{LoginMessage}
|
|
|| $ConfigObject->Get('SystemMaintenance::IsActiveDefaultLoginMessage')
|
|
|| "System maintenance is active, not possible to perform a login!";
|
|
|
|
$Self->Block(
|
|
Name => 'SystemMaintenance',
|
|
Data => {
|
|
LoginMessage => $LoginMessage,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# show prelogin block, if in prelogin mode (e.g. SSO login)
|
|
if ( defined $Param{'Mode'} && $Param{'Mode'} eq 'PreLogin' ) {
|
|
$Self->Block(
|
|
Name => 'PreLogin',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# if not in PreLogin mode, show normal login form
|
|
else {
|
|
|
|
$Self->Block(
|
|
Name => 'LoginBox',
|
|
Data => \%Param,
|
|
);
|
|
|
|
# show 2 factor password input if we have at least one backend enabled
|
|
COUNT:
|
|
for my $Count ( '', 1 .. 10 ) {
|
|
next COUNT if !$ConfigObject->Get("AuthTwoFactorModule$Count");
|
|
|
|
# if no empty shared secrets are allowed, input is mandatory
|
|
my %MandatoryOptions;
|
|
if ( !$ConfigObject->Get("AuthTwoFactorModule${Count}::AllowEmptySecret") ) {
|
|
%MandatoryOptions = (
|
|
MandatoryClass => 'Mandatory',
|
|
ValidateRequired => 'Validate_Required',
|
|
);
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => 'AuthTwoFactor',
|
|
Data => {
|
|
%Param,
|
|
%MandatoryOptions,
|
|
},
|
|
);
|
|
|
|
if (%MandatoryOptions) {
|
|
$Self->Block(
|
|
Name => 'AuthTwoFactorMandatory',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
last COUNT;
|
|
}
|
|
|
|
# get lost password
|
|
if (
|
|
$ConfigObject->Get('LostPassword')
|
|
&& $ConfigObject->Get('AuthModule') eq 'Kernel::System::Auth::DB'
|
|
)
|
|
{
|
|
$Self->Block(
|
|
Name => 'LostPasswordLink',
|
|
Data => \%Param,
|
|
);
|
|
|
|
$Self->Block(
|
|
Name => 'LostPassword',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
}
|
|
|
|
# send data to JS
|
|
$Self->AddJSData(
|
|
Key => 'LoginFailed',
|
|
Value => $Param{LoginFailed},
|
|
);
|
|
|
|
# create & return output
|
|
$Output .= $Self->Output(
|
|
TemplateFile => 'Login',
|
|
Data => \%Param,
|
|
);
|
|
|
|
# remove the version tag from the header if configured
|
|
$Self->_DisableBannerCheck( OutputRef => \$Output );
|
|
|
|
return $Output;
|
|
}
|
|
|
|
sub ChallengeTokenCheck {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# return if feature is disabled
|
|
return 1 if !$Kernel::OM->Get('Kernel::Config')->Get('SessionCSRFProtection');
|
|
|
|
# get challenge token and check it
|
|
my $ChallengeToken = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'ChallengeToken' ) || '';
|
|
|
|
# check regular ChallengeToken
|
|
return 1 if $ChallengeToken eq $Self->{UserChallengeToken};
|
|
|
|
# check ChallengeToken of all own sessions
|
|
my $SessionObject = $Kernel::OM->Get('Kernel::System::AuthSession');
|
|
my @Sessions = $SessionObject->GetAllSessionIDs();
|
|
|
|
SESSION:
|
|
for my $SessionID (@Sessions) {
|
|
my %Data = $SessionObject->GetSessionIDData( SessionID => $SessionID );
|
|
next SESSION if !$Data{UserID};
|
|
next SESSION if $Data{UserID} ne $Self->{UserID};
|
|
next SESSION if !$Data{UserChallengeToken};
|
|
|
|
# check ChallengeToken
|
|
return 1 if $ChallengeToken eq $Data{UserChallengeToken};
|
|
}
|
|
|
|
# no valid token found
|
|
if ( $Param{Type} && lc $Param{Type} eq 'customer' ) {
|
|
$Self->CustomerFatalError(
|
|
Message => 'Invalid Challenge Token!',
|
|
);
|
|
}
|
|
else {
|
|
$Self->FatalError(
|
|
Message => 'Invalid Challenge Token!',
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub FatalError {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# Prevent endless recursion in case of problems with Template engine.
|
|
return if ( $Self->{InFatalError}++ );
|
|
|
|
if ( $Param{Message} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Caller => 1,
|
|
Priority => 'error',
|
|
Message => $Param{Message},
|
|
);
|
|
}
|
|
my $Output = $Self->Header(
|
|
Area => 'Frontend',
|
|
Title => 'Fatal Error'
|
|
);
|
|
$Output .= $Self->Error(%Param);
|
|
$Output .= $Self->Footer();
|
|
$Self->Print( Output => \$Output );
|
|
exit;
|
|
}
|
|
|
|
sub SecureMode {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Output = $Self->Header(
|
|
Area => 'Frontend',
|
|
Title => 'Secure Mode'
|
|
);
|
|
$Output .= $Self->Output(
|
|
TemplateFile => 'AdminSecureMode',
|
|
Data => \%Param
|
|
);
|
|
$Output .= $Self->Footer();
|
|
return $Output;
|
|
}
|
|
|
|
sub FatalDie {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
if ( $Param{Message} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Caller => 1,
|
|
Priority => 'error',
|
|
Message => $Param{Message},
|
|
);
|
|
}
|
|
|
|
# get backend error messages
|
|
for (qw(Message Traceback)) {
|
|
my $Backend = 'Backend' . $_;
|
|
$Param{$Backend} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => $_
|
|
) || '';
|
|
$Param{$Backend} = $Self->Ascii2Html(
|
|
Text => $Param{$Backend},
|
|
HTMLResultMode => 1,
|
|
);
|
|
}
|
|
if ( !$Param{Message} ) {
|
|
$Param{Message} = $Param{BackendMessage};
|
|
}
|
|
die $Param{Message};
|
|
}
|
|
|
|
sub ErrorScreen {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Output = $Self->Header( Title => 'Error' );
|
|
$Output .= $Self->Error(%Param);
|
|
$Output .= $Self->Footer();
|
|
return $Output;
|
|
}
|
|
|
|
sub Error {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# get backend error messages
|
|
for (qw(Message Traceback)) {
|
|
my $Backend = 'Backend' . $_;
|
|
$Param{$Backend} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => $_
|
|
) || '';
|
|
}
|
|
if ( !$Param{BackendMessage} && !$Param{BackendTraceback} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => $Param{Message} || '?',
|
|
);
|
|
for (qw(Message Traceback)) {
|
|
my $Backend = 'Backend' . $_;
|
|
$Param{$Backend} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => $_
|
|
) || '';
|
|
}
|
|
}
|
|
|
|
if ( !$Param{Message} ) {
|
|
$Param{Message} = $Param{BackendMessage};
|
|
|
|
# Don't check for business package if the database was not yet configured (in the installer).
|
|
if (
|
|
$Kernel::OM->Get('Kernel::Config')->Get('SecureMode')
|
|
&& $Kernel::OM->Get('Kernel::Config')->Get('DatabaseDSN')
|
|
&& !$Kernel::OM->Get('Kernel::System::OTRSBusiness')->OTRSBusinessIsInstalled()
|
|
)
|
|
{
|
|
$Param{ShowOTRSBusinessHint}++;
|
|
}
|
|
}
|
|
|
|
if ( $Param{BackendTraceback} ) {
|
|
$Self->Block(
|
|
Name => 'ShowBackendTraceback',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# create & return output
|
|
return $Self->Output(
|
|
TemplateFile => 'Error',
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
sub Warning {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# get backend error messages
|
|
$Param{BackendMessage} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Notice',
|
|
What => 'Message',
|
|
)
|
|
|| $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => 'Message',
|
|
) || '';
|
|
|
|
if ( !$Param{Message} ) {
|
|
$Param{Message} = $Param{BackendMessage};
|
|
}
|
|
|
|
# create & return output
|
|
return $Self->Output(
|
|
TemplateFile => 'Warning',
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
=head2 Notify()
|
|
|
|
create notify lines
|
|
|
|
infos, the text will be translated
|
|
|
|
my $Output = $LayoutObject->Notify(
|
|
Priority => 'Warning',
|
|
Info => 'Some Info Message',
|
|
);
|
|
|
|
data with link, the text will be translated
|
|
|
|
my $Output = $LayoutObject->Notify(
|
|
Priority => 'Warning',
|
|
Data => 'Template content',
|
|
Link => 'http://example.com/',
|
|
LinkClass => 'some_CSS_class', # optional
|
|
);
|
|
|
|
errors, the text will be translated
|
|
|
|
my $Output = $LayoutObject->Notify(
|
|
Priority => 'Error',
|
|
Info => 'Some Error Message',
|
|
);
|
|
|
|
errors from log backend, if no error exists, a '' will be returned
|
|
|
|
my $Output = $LayoutObject->Notify(
|
|
Priority => 'Error',
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Notify {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# create & return output
|
|
if ( !$Param{Info} && !$Param{Data} ) {
|
|
$Param{BackendMessage} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Notice',
|
|
What => 'Message',
|
|
)
|
|
|| $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => 'Message',
|
|
) || '';
|
|
|
|
$Param{Info} = $Param{BackendMessage};
|
|
|
|
# return if we have nothing to show
|
|
return '' if !$Param{Info};
|
|
}
|
|
|
|
my $BoxClass = 'Notice';
|
|
|
|
if ( $Param{Info} ) {
|
|
$Param{Info} =~ s/\n//g;
|
|
}
|
|
if ( $Param{Priority} && $Param{Priority} eq 'Error' ) {
|
|
$BoxClass = 'Error';
|
|
}
|
|
elsif ( $Param{Priority} && $Param{Priority} eq 'Success' ) {
|
|
$BoxClass = 'Success';
|
|
}
|
|
elsif ( $Param{Priority} && $Param{Priority} eq 'Info' ) {
|
|
$BoxClass = 'Info';
|
|
}
|
|
|
|
if ( $Param{Link} ) {
|
|
$Self->Block(
|
|
Name => 'LinkStart',
|
|
Data => {
|
|
LinkStart => $Param{Link},
|
|
LinkClass => $Param{LinkClass} || '',
|
|
},
|
|
);
|
|
}
|
|
if ( $Param{Data} ) {
|
|
$Self->Block(
|
|
Name => 'Data',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
else {
|
|
$Self->Block(
|
|
Name => 'Text',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
if ( $Param{Link} ) {
|
|
$Self->Block(
|
|
Name => 'LinkStop',
|
|
Data => {
|
|
LinkStop => '</a>',
|
|
},
|
|
);
|
|
}
|
|
return $Self->Output(
|
|
TemplateFile => 'Notify',
|
|
Data => {
|
|
%Param,
|
|
BoxClass => $BoxClass,
|
|
},
|
|
);
|
|
}
|
|
|
|
=head2 NotifyNonUpdatedTickets()
|
|
|
|
Adds notification about tickets which are not updated.
|
|
|
|
my $Output = $LayoutObject->NotifyNonUpdatedTickets();
|
|
|
|
=cut
|
|
|
|
sub NotifyNonUpdatedTickets {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $NonUpdatedTicketsString = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => 'Ticket',
|
|
Key => 'NonUpdatedTicketsString-' . $Self->{UserID},
|
|
);
|
|
|
|
return if !$NonUpdatedTicketsString;
|
|
|
|
# Delete this value from the cache.
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
|
Type => 'Ticket',
|
|
Key => 'NonUpdatedTicketsString-' . $Self->{UserID},
|
|
);
|
|
|
|
return $Self->Notify(
|
|
Info => $Self->{LanguageObject}
|
|
->Translate( "The following tickets are not updated: %s.", $NonUpdatedTicketsString ),
|
|
);
|
|
|
|
}
|
|
|
|
=head2 Header()
|
|
|
|
generates the HTML for the page begin in the Agent interface.
|
|
|
|
my $Output = $LayoutObject->Header(
|
|
Type => 'Small', # (optional) '' (Default, full header) or 'Small' (blank header)
|
|
ShowToolbarItems => 0, # (optional) default 1 (0|1)
|
|
ShowPrefLink => 0, # (optional) default 1 (0|1)
|
|
ShowLogoutButton => 0, # (optional) default 1 (0|1)
|
|
|
|
DisableIFrameOriginRestricted => 1, # (optional, default 0) - suppress X-Frame-Options header.
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Header {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Type = $Param{Type} || '';
|
|
|
|
# check params
|
|
if ( !defined $Param{ShowToolbarItems} ) {
|
|
$Param{ShowToolbarItems} = 1;
|
|
}
|
|
|
|
if ( !defined $Param{ShowPrefLink} ) {
|
|
$Param{ShowPrefLink} = 1;
|
|
}
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# do not show preferences link if the preferences module is disabled
|
|
my $Modules = $ConfigObject->Get('Frontend::Module');
|
|
if ( !$Modules->{AgentPreferences} ) {
|
|
$Param{ShowPrefLink} = 0;
|
|
}
|
|
|
|
if ( !defined $Param{ShowLogoutButton} ) {
|
|
$Param{ShowLogoutButton} = 1;
|
|
}
|
|
|
|
# set rtl if needed
|
|
if ( $Self->{TextDirection} && $Self->{TextDirection} eq 'rtl' ) {
|
|
$Param{BodyClass} = 'RTL';
|
|
}
|
|
elsif ( $ConfigObject->Get('Frontend::DebugMode') ) {
|
|
$Self->Block(
|
|
Name => 'DebugRTLButton',
|
|
);
|
|
}
|
|
|
|
# Generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
|
|
$Self->LoaderCreateAgentCSSCalls();
|
|
|
|
my %AgentLogo;
|
|
|
|
# check if we need to display a custom logo for the selected skin
|
|
my $AgentLogoCustom = $ConfigObject->Get('AgentLogoCustom');
|
|
if (
|
|
$Self->{SkinSelected}
|
|
&& $AgentLogoCustom
|
|
&& IsHashRefWithData($AgentLogoCustom)
|
|
&& $AgentLogoCustom->{ $Self->{SkinSelected} }
|
|
)
|
|
{
|
|
%AgentLogo = %{ $AgentLogoCustom->{ $Self->{SkinSelected} } };
|
|
}
|
|
|
|
# Otherwise show default header logo, if configured
|
|
elsif ( defined $ConfigObject->Get('AgentLogo') ) {
|
|
%AgentLogo = %{ $ConfigObject->Get('AgentLogo') };
|
|
}
|
|
|
|
if ( %AgentLogo && keys %AgentLogo ) {
|
|
|
|
my %Data;
|
|
for my $CSSStatement ( sort keys %AgentLogo ) {
|
|
if ( $CSSStatement eq 'URL' ) {
|
|
my $WebPath = '';
|
|
if ( $AgentLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
|
|
$WebPath = $ConfigObject->Get('Frontend::WebPath');
|
|
}
|
|
$Data{'URL'} = 'url(' . $WebPath . $AgentLogo{$CSSStatement} . ')';
|
|
}
|
|
else {
|
|
$Data{$CSSStatement} = $AgentLogo{$CSSStatement};
|
|
}
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => 'HeaderLogoCSS',
|
|
Data => \%Data,
|
|
);
|
|
}
|
|
|
|
# add cookies if exists
|
|
my $Output = '';
|
|
if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
|
|
for ( sort keys %{ $Self->{SetCookies} } ) {
|
|
$Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
|
|
}
|
|
}
|
|
|
|
my $File = $Param{Filename} || $Self->{Action} || 'unknown';
|
|
|
|
# set file name for "save page as"
|
|
$Param{ContentDisposition} = "filename=\"$File.html\"";
|
|
|
|
# area and title
|
|
if ( !$Param{Area} ) {
|
|
$Param{Area} = (
|
|
defined $Self->{Action}
|
|
? $ConfigObject->Get('Frontend::Module')->{ $Self->{Action} }->{NavBarName}
|
|
: ''
|
|
);
|
|
}
|
|
if ( !$Param{Title} ) {
|
|
$Param{Title} = $ConfigObject->Get('Frontend::Module')->{ $Self->{Action} }->{Title}
|
|
|| '';
|
|
}
|
|
for my $Word (qw(Value Title Area)) {
|
|
if ( $Param{$Word} ) {
|
|
$Param{TitleArea} .= $Self->{LanguageObject}->Translate( $Param{$Word} ) . ' - ';
|
|
}
|
|
}
|
|
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
# run header meta modules
|
|
my $HeaderMetaModule = $ConfigObject->Get('Frontend::HeaderMetaModule');
|
|
if ( ref $HeaderMetaModule eq 'HASH' ) {
|
|
my %Jobs = %{$HeaderMetaModule};
|
|
|
|
MODULE:
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load and run module
|
|
next MODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
|
|
my $Object = $Jobs{$Job}->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
next MODULE if !$Object;
|
|
$Object->Run( %Param, Config => $Jobs{$Job} );
|
|
}
|
|
}
|
|
|
|
# run tool bar item modules
|
|
if ( $Self->{UserID} && $Self->{UserType} eq 'User' ) {
|
|
my $ToolBarModule = $ConfigObject->Get('Frontend::ToolBarModule');
|
|
if ( $Param{ShowToolbarItems} && ref $ToolBarModule eq 'HASH' ) {
|
|
|
|
$Self->Block(
|
|
Name => 'ToolBar',
|
|
Data => \%Param,
|
|
);
|
|
|
|
my %Modules;
|
|
my %Jobs = %{$ToolBarModule};
|
|
|
|
# get group object
|
|
my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
|
|
|
|
MODULE:
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load and run module
|
|
next MODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
|
|
my $Object = $Jobs{$Job}->{Module}->new(
|
|
%{$Self}, # UserID etc.
|
|
);
|
|
next MODULE if !$Object;
|
|
|
|
my $ToolBarAccessOk;
|
|
|
|
# if group restriction for tool-bar is set, check user permission
|
|
if ( $Jobs{$Job}->{Group} ) {
|
|
|
|
# remove white-spaces
|
|
$Jobs{$Job}->{Group} =~ s{\s}{}xmsg;
|
|
|
|
# get group configurations
|
|
my @Items = split( ';', $Jobs{$Job}->{Group} );
|
|
|
|
ITEM:
|
|
for my $Item (@Items) {
|
|
|
|
# split values into permission and group
|
|
my ( $Permission, $GroupName ) = split( ':', $Item );
|
|
|
|
# log an error if not valid setting
|
|
if ( !$Permission || !$GroupName ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Invalid config for ToolBarModule $Job - Key Group: '$Item'! "
|
|
. "Need something like 'Permission:Group;'",
|
|
);
|
|
}
|
|
|
|
# get groups for current user
|
|
my %Groups = $GroupObject->PermissionUserGet(
|
|
UserID => $Self->{UserID},
|
|
Type => $Permission,
|
|
);
|
|
|
|
# next job if user have not groups
|
|
next ITEM if !%Groups;
|
|
|
|
# check user belongs to the correct group
|
|
my %GroupsReverse = reverse %Groups;
|
|
next ITEM if !$GroupsReverse{$GroupName};
|
|
|
|
$ToolBarAccessOk = 1;
|
|
|
|
last ITEM;
|
|
}
|
|
|
|
# go to the next module if not permissions
|
|
# for the current one
|
|
next MODULE if !$ToolBarAccessOk;
|
|
}
|
|
|
|
%Modules = ( $Object->Run( %Param, Config => $Jobs{$Job} ), %Modules );
|
|
}
|
|
|
|
# show tool bar items
|
|
MODULE:
|
|
for my $Key ( sort keys %Modules ) {
|
|
next MODULE if !%{ $Modules{$Key} };
|
|
|
|
# For ToolBarSearchFulltext module take into consideration SearchInArchive settings.
|
|
# See bug#13790 (https://bugs.otrs.org/show_bug.cgi?id=13790).
|
|
if ( $ConfigObject->Get('Ticket::ArchiveSystem') && $Modules{$Key}->{Block} eq 'ToolBarSearchFulltext' )
|
|
{
|
|
$Modules{$Key}->{SearchInArchive}
|
|
= $ConfigObject->Get('Ticket::Frontend::AgentTicketSearch')->{Defaults}->{SearchInArchive};
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => $Modules{$Key}->{Block},
|
|
Data => {
|
|
%{ $Modules{$Key} },
|
|
AccessKeyReference => $Modules{$Key}->{AccessKey}
|
|
? " ($Modules{$Key}->{AccessKey})"
|
|
: '',
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Chat', Silent => 1 ) ) {
|
|
if ( $ConfigObject->Get('ChatEngine::Active') ) {
|
|
$Self->AddJSData(
|
|
Key => 'ChatEngine::Active',
|
|
Value => $ConfigObject->Get('ChatEngine::Active')
|
|
);
|
|
}
|
|
}
|
|
|
|
# generate avatar
|
|
if ( $ConfigObject->Get('Frontend::AvatarEngine') eq 'Gravatar' && $Self->{UserEmail} ) {
|
|
my $DefaultIcon = $ConfigObject->Get('Frontend::Gravatar::DefaultImage') || 'mm';
|
|
$Param{Avatar}
|
|
= '//www.gravatar.com/avatar/' . md5_hex( lc $Self->{UserEmail} ) . '?s=100&d=' . $DefaultIcon;
|
|
}
|
|
else {
|
|
my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
|
|
User => $Self->{UserLogin},
|
|
NoOutOfOffice => 1,
|
|
);
|
|
|
|
$Param{UserInitials} = $Self->UserInitialsGet( Fullname => $User{UserFullname} );
|
|
}
|
|
|
|
# show logged in notice
|
|
if ( $Param{ShowPrefLink} ) {
|
|
$Self->Block(
|
|
Name => 'Login',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
else {
|
|
$Self->Block(
|
|
Name => 'LoginWithoutLink',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# show logout button (if registered)
|
|
if (
|
|
$Param{ShowLogoutButton}
|
|
&& $ConfigObject->Get('Frontend::Module')->{Logout}
|
|
)
|
|
{
|
|
$Self->Block(
|
|
Name => 'Logout',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( $ConfigObject->Get('SecureMode') ) {
|
|
$Param{OTRSBusinessIsInstalled} = $Kernel::OM->Get('Kernel::System::OTRSBusiness')->OTRSBusinessIsInstalled();
|
|
}
|
|
|
|
# create & return output
|
|
$Output .= $Self->Output(
|
|
TemplateFile => "Header$Type",
|
|
Data => \%Param
|
|
);
|
|
|
|
# remove the version tag from the header if configured
|
|
$Self->_DisableBannerCheck( OutputRef => \$Output );
|
|
|
|
return $Output;
|
|
}
|
|
|
|
sub Footer {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Type = $Param{Type} || '';
|
|
my $HasDatepicker = $Self->{HasDatepicker} || 0;
|
|
|
|
# generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
|
|
$Self->LoaderCreateAgentJSCalls();
|
|
$Self->LoaderCreateJavaScriptTranslationData();
|
|
$Self->LoaderCreateJavaScriptTemplateData();
|
|
|
|
# get datepicker data, if needed in module
|
|
if ($HasDatepicker) {
|
|
my $VacationDays = $Self->DatepickerGetVacationDays();
|
|
my $TextDirection = $Self->{LanguageObject}->{TextDirection} || '';
|
|
|
|
# send data to JS
|
|
$Self->AddJSData(
|
|
Key => 'Datepicker',
|
|
Value => {
|
|
VacationDays => $VacationDays,
|
|
IsRTL => ( $TextDirection eq 'rtl' ) ? 1 : 0,
|
|
},
|
|
);
|
|
}
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# send data to JS if NewTicketInNewWindow is enabled
|
|
if ( $ConfigObject->Get('NewTicketInNewWindow::Enabled') ) {
|
|
$Self->AddJSData(
|
|
Key => 'NewTicketInNewWindow',
|
|
Value => 1,
|
|
);
|
|
}
|
|
|
|
# AutoComplete-Config
|
|
my $AutocompleteConfig = $ConfigObject->Get('AutoComplete::Agent');
|
|
|
|
for my $ConfigElement ( sort keys %{$AutocompleteConfig} ) {
|
|
$AutocompleteConfig->{$ConfigElement}->{ButtonText}
|
|
= $Self->{LanguageObject}->Translate( $AutocompleteConfig->{$ConfigElement}->{ButtonText} );
|
|
}
|
|
|
|
# Search frontend (JavaScript)
|
|
my $SearchFrontendConfig = $ConfigObject->Get('Frontend::Search::JavaScript');
|
|
|
|
# get target javascript function
|
|
my $JSCall = '';
|
|
|
|
if ( $SearchFrontendConfig && $Self->{Action} ) {
|
|
for my $Group ( sort keys %{$SearchFrontendConfig} ) {
|
|
REGEXP:
|
|
for my $RegExp ( sort keys %{ $SearchFrontendConfig->{$Group} } ) {
|
|
if ( $Self->{Action} =~ /$RegExp/ ) {
|
|
$JSCall = $SearchFrontendConfig->{$Group}->{$RegExp};
|
|
last REGEXP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# get OTRS business object
|
|
my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
|
|
|
|
# don't check for business package if the database was not yet configured (in the installer)
|
|
if ( $ConfigObject->Get('SecureMode') ) {
|
|
$Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
|
|
$Param{OTRSSTORMIsInstalled} = $OTRSBusinessObject->OTRSSTORMIsInstalled();
|
|
$Param{OTRSCONTROLIsInstalled} = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
|
|
}
|
|
|
|
# Check if video chat is enabled.
|
|
if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::VideoChat', Silent => 1 ) ) {
|
|
$Param{VideoChatEnabled} = $Kernel::OM->Get('Kernel::System::VideoChat')->IsEnabled()
|
|
|| $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'UnitTestMode' ) // 0;
|
|
}
|
|
|
|
# Set an array with pending states.
|
|
my @PendingStateIDs = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
|
|
StateType => [ 'pending reminder', 'pending auto' ],
|
|
Result => 'ID',
|
|
);
|
|
|
|
# add JS data
|
|
my %JSConfig = (
|
|
Baselink => $Self->{Baselink},
|
|
CGIHandle => $Self->{CGIHandle},
|
|
WebPath => $ConfigObject->Get('Frontend::WebPath'),
|
|
Action => $Self->{Action},
|
|
Subaction => $Self->{Subaction},
|
|
SessionIDCookie => $Self->{SessionIDCookie},
|
|
SessionName => $Self->{SessionName},
|
|
SessionID => $Self->{SessionID},
|
|
SessionUseCookie => $ConfigObject->Get('SessionUseCookie'),
|
|
ChallengeToken => $Self->{UserChallengeToken},
|
|
CustomerPanelSessionName => $ConfigObject->Get('CustomerPanelSessionName'),
|
|
UserLanguage => $Self->{UserLanguage},
|
|
WebMaxFileUpload => $ConfigObject->Get('WebMaxFileUpload'),
|
|
RichTextSet => $ConfigObject->Get('Frontend::RichText'),
|
|
CheckEmailAddresses => $ConfigObject->Get('CheckEmailAddresses'),
|
|
MenuDragDropEnabled => $ConfigObject->Get('Frontend::MenuDragDropEnabled'),
|
|
OpenMainMenuOnHover => $ConfigObject->Get('OpenMainMenuOnHover'),
|
|
CustomerInfoSet => $ConfigObject->Get('Ticket::Frontend::CustomerInfoCompose'),
|
|
IncludeUnknownTicketCustomers => $ConfigObject->Get('Ticket::IncludeUnknownTicketCustomers'),
|
|
InputFieldsActivated => $ConfigObject->Get('ModernizeFormFields'),
|
|
OTRSBusinessIsInstalled => $Param{OTRSBusinessIsInstalled},
|
|
VideoChatEnabled => $Param{VideoChatEnabled},
|
|
PendingStateIDs => \@PendingStateIDs,
|
|
CheckSearchStringsForStopWords => (
|
|
$ConfigObject->Get('Ticket::SearchIndex::WarnOnStopWordUsage')
|
|
&&
|
|
(
|
|
$ConfigObject->Get('Ticket::SearchIndexModule')
|
|
eq 'Kernel::System::Ticket::ArticleSearchIndex::DB'
|
|
)
|
|
) ? 1 : 0,
|
|
SearchFrontend => $JSCall,
|
|
Autocomplete => $AutocompleteConfig,
|
|
);
|
|
|
|
for my $Config ( sort keys %JSConfig ) {
|
|
$Self->AddJSData(
|
|
Key => $Config,
|
|
Value => $JSConfig{$Config},
|
|
);
|
|
}
|
|
|
|
# create & return output
|
|
return $Self->Output(
|
|
TemplateFile => "Footer$Type",
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
sub Print {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# run output content filters
|
|
if ( $Self->{FilterContent} && ref $Self->{FilterContent} eq 'HASH' ) {
|
|
|
|
# extract filter list
|
|
my %FilterList = %{ $Self->{FilterContent} };
|
|
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
FILTER:
|
|
for my $Filter ( sort keys %FilterList ) {
|
|
|
|
# extract filter config
|
|
my $FilterConfig = $FilterList{$Filter};
|
|
|
|
next FILTER if !$FilterConfig;
|
|
next FILTER if ref $FilterConfig ne 'HASH';
|
|
|
|
# extract template list
|
|
my $TemplateList = $FilterConfig->{Templates};
|
|
|
|
# check template list
|
|
if ( !$TemplateList || ref $TemplateList ne 'HASH' || !%{$TemplateList} ) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message =>
|
|
"Please add a template list to output filter $FilterConfig->{Module} "
|
|
. "to improve performance. Use ALL if OutputFilter should modify all "
|
|
. "templates of the system (deprecated).",
|
|
);
|
|
}
|
|
|
|
# check template list
|
|
if ( $Param{TemplateFile} && ref $TemplateList eq 'HASH' && !$TemplateList->{ALL} ) {
|
|
next FILTER if !$TemplateList->{ $Param{TemplateFile} };
|
|
}
|
|
|
|
next FILTER if !$MainObject->Require( $FilterConfig->{Module} );
|
|
|
|
# create new instance
|
|
my $Object = $FilterConfig->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
|
|
next FILTER if !$Object;
|
|
|
|
# run output filter
|
|
$Object->Run(
|
|
%{$FilterConfig},
|
|
Data => $Param{Output},
|
|
TemplateFile => $Param{TemplateFile} || '',
|
|
);
|
|
}
|
|
}
|
|
|
|
# There seems to be a bug in FastCGI that it cannot handle unicode output properly.
|
|
# Work around this by converting to an utf8 byte stream instead.
|
|
# See also http://bugs.otrs.org/show_bug.cgi?id=6284 and
|
|
# http://bugs.otrs.org/show_bug.cgi?id=9802.
|
|
if ( $INC{'CGI/Fast.pm'} || $ENV{FCGI_ROLE} || $ENV{FCGI_SOCKET_PATH} ) { # are we on FCGI?
|
|
$Kernel::OM->Get('Kernel::System::Encode')->EncodeOutput( $Param{Output} );
|
|
binmode STDOUT, ':bytes';
|
|
}
|
|
|
|
# Disable perl warnings in case of printing unicode private chars,
|
|
# see https://rt.perl.org/Public/Bug/Display.html?id=121226.
|
|
no warnings 'nonchar'; ## no critic
|
|
|
|
print ${ $Param{Output} };
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 Ascii2Html()
|
|
|
|
convert ASCII to html string
|
|
|
|
my $HTML = $LayoutObject->Ascii2Html(
|
|
Text => 'Some <> Test <font color="red">Test</font>',
|
|
Max => 20, # max 20 chars flowed by [..]
|
|
VMax => 15, # first 15 lines
|
|
NewLine => 0, # move \r to \n
|
|
HTMLResultMode => 0, # replace " " with C< >
|
|
StripEmptyLines => 0,
|
|
Type => 'Normal', # JSText or Normal text
|
|
LinkFeature => 0, # do some URL detections
|
|
);
|
|
|
|
also string ref is possible
|
|
|
|
my $HTMLStringRef = $LayoutObject->Ascii2Html(
|
|
Text => \$String,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Ascii2Html {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed param
|
|
return '' if !defined $Param{Text};
|
|
|
|
# check text
|
|
my $TextScalar;
|
|
my $Text;
|
|
if ( !ref $Param{Text} ) {
|
|
$TextScalar = 1;
|
|
$Text = \$Param{Text};
|
|
}
|
|
elsif ( ref $Param{Text} eq 'SCALAR' ) {
|
|
$Text = $Param{Text};
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Invalid ref "' . ref( $Param{Text} ) . '" of Text param!',
|
|
);
|
|
return '';
|
|
}
|
|
|
|
# run output filter text
|
|
my @Filters;
|
|
if ( $Param{LinkFeature} && $Self->{FilterText} && ref $Self->{FilterText} eq 'HASH' ) {
|
|
|
|
# extract filter list
|
|
my %FilterList = %{ $Self->{FilterText} };
|
|
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
FILTER:
|
|
for my $Filter ( sort keys %FilterList ) {
|
|
|
|
# extract filter config
|
|
my $FilterConfig = $FilterList{$Filter};
|
|
|
|
next FILTER if !$FilterConfig;
|
|
next FILTER if ref $FilterConfig ne 'HASH';
|
|
|
|
# extract template list
|
|
my $TemplateList = $FilterConfig->{Templates};
|
|
|
|
# check template list
|
|
if ( !$TemplateList || ref $TemplateList ne 'HASH' || !%{$TemplateList} ) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message =>
|
|
"Please add a template list to output filter $FilterConfig->{Module} "
|
|
. "to improve performance. Use ALL if OutputFilter should modify all "
|
|
. "templates of the system (deprecated).",
|
|
);
|
|
}
|
|
|
|
# check template list
|
|
if ( $Param{TemplateFile} && ref $TemplateList eq 'HASH' && !$TemplateList->{ALL} ) {
|
|
next FILTER if !$TemplateList->{ $Param{TemplateFile} };
|
|
}
|
|
|
|
$Self->FatalDie() if !$MainObject->Require( $FilterConfig->{Module} );
|
|
|
|
# create new instance
|
|
my $Object = $FilterConfig->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
|
|
next FILTER if !$Object;
|
|
|
|
push(
|
|
@Filters,
|
|
{
|
|
Object => $Object,
|
|
Filter => $FilterConfig,
|
|
},
|
|
);
|
|
}
|
|
|
|
# pre run
|
|
for my $Filter (@Filters) {
|
|
|
|
$Text = $Filter->{Object}->Pre(
|
|
Filter => $Filter->{Filter},
|
|
Data => $Text,
|
|
);
|
|
}
|
|
}
|
|
|
|
# max width
|
|
if ( $Param{Max} && length ${$Text} > $Param{Max} ) {
|
|
${$Text} = substr( ${$Text}, 0, $Param{Max} - 5 ) . '[...]';
|
|
}
|
|
|
|
# newline
|
|
if ( $Param{NewLine} && length( ${$Text} ) < 140_000 ) {
|
|
${$Text} =~ s/(\n\r|\r\r\n|\r\n)/\n/g;
|
|
${$Text} =~ s/\r/\n/g;
|
|
${$Text} =~ s/(.{4,$Param{NewLine}})(?:\s|\z)/$1\n/gm;
|
|
}
|
|
|
|
# remove tabs
|
|
${$Text} =~ s/\t/ /g;
|
|
|
|
# strip empty lines
|
|
if ( $Param{StripEmptyLines} ) {
|
|
${$Text} =~ s/^\s*\n//mg;
|
|
}
|
|
|
|
# max lines
|
|
if ( $Param{VMax} ) {
|
|
my @TextList = split( "\n", ${$Text} );
|
|
${$Text} = '';
|
|
my $Counter = 1;
|
|
for (@TextList) {
|
|
if ( $Counter <= $Param{VMax} ) {
|
|
${$Text} .= $_ . "\n";
|
|
}
|
|
$Counter++;
|
|
}
|
|
if ( $Counter >= $Param{VMax} ) {
|
|
${$Text} .= "[...]\n";
|
|
}
|
|
}
|
|
|
|
# html quoting
|
|
${$Text} =~ s/&/&/g;
|
|
${$Text} =~ s/</</g;
|
|
${$Text} =~ s/>/>/g;
|
|
${$Text} =~ s/"/"/g;
|
|
|
|
# text -> html format quoting
|
|
if ( $Param{LinkFeature} ) {
|
|
for my $Filter (@Filters) {
|
|
$Text = $Filter->{Object}->Post(
|
|
Filter => $Filter->{Filter},
|
|
Data => $Text,
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( $Param{HTMLResultMode} ) {
|
|
${$Text} =~ s/\n/<br\/>\n/g;
|
|
${$Text} =~ s/ / /g;
|
|
|
|
# Convert the space at the beginning of the line (see bug#14346 - https://bugs.otrs.org/show_bug.cgi?id=14346).
|
|
${$Text} =~ s/\n /\n /g;
|
|
}
|
|
|
|
if ( $Param{Type} && $Param{Type} eq 'JSText' ) {
|
|
${$Text} =~ s/'/\\'/g;
|
|
}
|
|
|
|
return $Text if ref $Param{Text};
|
|
return ${$Text};
|
|
}
|
|
|
|
=head2 LinkQuote()
|
|
|
|
detect links in text
|
|
|
|
my $HTMLWithLinks = $LayoutObject->LinkQuote(
|
|
Text => $HTMLWithOutLinks,
|
|
);
|
|
|
|
also string ref is possible
|
|
|
|
my $HTMLWithLinksRef = $LayoutObject->LinkQuote(
|
|
Text => \$HTMLWithOutLinksRef,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub LinkQuote {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Text = $Param{Text} || '';
|
|
my $Target = $Param{Target} || 'NewPage' . int( rand(199) );
|
|
|
|
# check ref
|
|
my $TextScalar;
|
|
if ( !ref $Text ) {
|
|
$TextScalar = $Text;
|
|
$Text = \$TextScalar;
|
|
}
|
|
|
|
# run output filter text
|
|
my @Filters;
|
|
if ( $Self->{FilterText} && ref $Self->{FilterText} eq 'HASH' ) {
|
|
|
|
# extract filter list
|
|
my %FilterList = %{ $Self->{FilterText} };
|
|
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
FILTER:
|
|
for my $Filter ( sort keys %FilterList ) {
|
|
|
|
# extract filter config
|
|
my $FilterConfig = $FilterList{$Filter};
|
|
|
|
next FILTER if !$FilterConfig;
|
|
next FILTER if ref $FilterConfig ne 'HASH';
|
|
|
|
# extract template list
|
|
my $TemplateList = $FilterConfig->{Templates};
|
|
|
|
# check template list
|
|
if ( !$TemplateList || ref $TemplateList ne 'HASH' || !%{$TemplateList} ) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message =>
|
|
"Please add a template list to output filter $FilterConfig->{Module} "
|
|
. "to improve performance. Use ALL if OutputFilter should modify all "
|
|
. "templates of the system (deprecated).",
|
|
);
|
|
}
|
|
|
|
# check template list
|
|
if ( $Param{TemplateFile} && ref $TemplateList eq 'HASH' && !$TemplateList->{ALL} ) {
|
|
next FILTER if !$TemplateList->{ $Param{TemplateFile} };
|
|
}
|
|
|
|
$Self->FatalDie() if !$MainObject->Require( $FilterConfig->{Module} );
|
|
|
|
# create new instance
|
|
my $Object = $FilterConfig->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
|
|
next FILTER if !$Object;
|
|
|
|
push @Filters, {
|
|
Object => $Object,
|
|
Filter => $FilterConfig,
|
|
};
|
|
}
|
|
}
|
|
|
|
for my $Filter (@Filters) {
|
|
$Text = $Filter->{Object}->Pre(
|
|
Filter => $Filter->{Filter},
|
|
Data => $Text
|
|
);
|
|
}
|
|
for my $Filter (@Filters) {
|
|
$Text = $Filter->{Object}->Post(
|
|
Filter => $Filter->{Filter},
|
|
Data => $Text
|
|
);
|
|
}
|
|
|
|
# do mail to quote
|
|
${$Text} =~ s/(mailto:.+?)(\.\s|\s|\)|\"|]|')/<a href=\"$1\">$1<\/a>$2/gi;
|
|
|
|
# check ref && return result like called
|
|
if ($TextScalar) {
|
|
return ${$Text};
|
|
}
|
|
else {
|
|
return $Text;
|
|
}
|
|
}
|
|
|
|
=head2 HTMLLinkQuote()
|
|
|
|
detect links in HTML code
|
|
|
|
my $HTMLWithLinks = $LayoutObject->HTMLLinkQuote(
|
|
String => $HTMLString,
|
|
);
|
|
|
|
also string ref is possible
|
|
|
|
my $HTMLWithLinksRef = $LayoutObject->HTMLLinkQuote(
|
|
String => \$HTMLString,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub HTMLLinkQuote {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
return $Kernel::OM->Get('Kernel::System::HTMLUtils')->LinkQuote(
|
|
String => $Param{String},
|
|
TargetAdd => 1,
|
|
Target => '_blank',
|
|
);
|
|
}
|
|
|
|
=head2 LinkEncode()
|
|
|
|
perform URL encoding on query string parameter names or values.
|
|
|
|
my $ParamValueEncoded = $LayoutObject->LinkEncode($ParamValue);
|
|
|
|
Don't encode entire URLs, because this will make them invalid
|
|
(?, & and ; will be encoded as well). Only pass one parameter name
|
|
or value at a time.
|
|
|
|
=cut
|
|
|
|
sub LinkEncode {
|
|
my ( $Self, $Link ) = @_;
|
|
|
|
return if !defined $Link;
|
|
|
|
return URI::Escape::uri_escape_utf8($Link);
|
|
}
|
|
|
|
sub CustomerAgeInHours {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Age = defined( $Param{Age} ) ? $Param{Age} : return;
|
|
my $Space = $Param{Space} || '<br/>';
|
|
my $AgeStrg = '';
|
|
my $HourDsc = Translatable('h');
|
|
my $MinuteDsc = Translatable('m');
|
|
if ( $Kernel::OM->Get('Kernel::Config')->Get('TimeShowCompleteDescription') ) {
|
|
$HourDsc = Translatable('hour(s)');
|
|
$MinuteDsc = Translatable('minute(s)');
|
|
}
|
|
if ( $Age =~ /^-(.*)/ ) {
|
|
$Age = $1;
|
|
$AgeStrg = '-';
|
|
}
|
|
|
|
# get hours
|
|
if ( $Age >= 3600 ) {
|
|
$AgeStrg .= int( ( $Age / 3600 ) ) . ' ';
|
|
$AgeStrg .= $Self->{LanguageObject}->Translate($HourDsc);
|
|
$AgeStrg .= $Space;
|
|
}
|
|
|
|
# get minutes (just if age < 1 day)
|
|
if ( $Age <= 3600 || int( ( $Age / 60 ) % 60 ) ) {
|
|
$AgeStrg .= int( ( $Age / 60 ) % 60 ) . ' ';
|
|
$AgeStrg .= $Self->{LanguageObject}->Translate($MinuteDsc);
|
|
}
|
|
return $AgeStrg;
|
|
}
|
|
|
|
sub CustomerAge {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
my $Age = defined( $Param{Age} ) ? $Param{Age} : return;
|
|
my $Space = $Param{Space} || '<br/>';
|
|
my $AgeStrg = '';
|
|
my $DayDsc = Translatable('d');
|
|
my $HourDsc = Translatable('h');
|
|
my $MinuteDsc = Translatable('m');
|
|
if ( $ConfigObject->Get('TimeShowCompleteDescription') ) {
|
|
$DayDsc = Translatable('day(s)');
|
|
$HourDsc = Translatable('hour(s)');
|
|
$MinuteDsc = Translatable('minute(s)');
|
|
}
|
|
if ( $Age =~ /^-(.*)/ ) {
|
|
$Age = $1;
|
|
$AgeStrg = '-';
|
|
}
|
|
|
|
# get days
|
|
if ( $Age >= 86400 ) {
|
|
$AgeStrg .= int( ( $Age / 3600 ) / 24 ) . ' ';
|
|
$AgeStrg .= $Self->{LanguageObject}->Translate($DayDsc);
|
|
$AgeStrg .= $Space;
|
|
}
|
|
|
|
# get hours
|
|
if ( $Age >= 3600 ) {
|
|
$AgeStrg .= int( ( $Age / 3600 ) % 24 ) . ' ';
|
|
$AgeStrg .= $Self->{LanguageObject}->Translate($HourDsc);
|
|
$AgeStrg .= $Space;
|
|
}
|
|
|
|
# get minutes (just if age < 1 day)
|
|
if ( ( $Param{TimeShowAlwaysLong} || $ConfigObject->Get('TimeShowAlwaysLong') || $Age < 86400 ) && $Age != 0 ) {
|
|
$AgeStrg .= int( ( $Age / 60 ) % 60 ) . ' ';
|
|
$AgeStrg .= $Self->{LanguageObject}->Translate($MinuteDsc);
|
|
}
|
|
return $AgeStrg;
|
|
}
|
|
|
|
=head2 BuildSelection()
|
|
|
|
build a HTML option element based on given data
|
|
|
|
my $HTML = $LayoutObject->BuildSelection(
|
|
Data => $ArrayRef, # use $HashRef, $ArrayRef or $ArrayHashRef (see below)
|
|
Name => 'TheName', # name of element
|
|
ID => 'HTMLID', # (optional) the HTML ID for this element, if not provided, the name will be used as ID as well
|
|
Multiple => 0, # (optional) default 0 (0|1)
|
|
Size => 1, # (optional) default 1 element size
|
|
Class => 'class', # (optional) a css class, include 'Modernize' to activate InputFields
|
|
Disabled => 0, # (optional) default 0 (0|1) disable the element
|
|
AutoComplete => 'off', # (optional)
|
|
OnChange => 'javascript', # (optional)
|
|
OnClick => 'javascript', # (optional)
|
|
|
|
SelectedID => 1, # (optional) use integer or arrayref (unable to use with ArrayHashRef)
|
|
SelectedID => [1, 5, 3], # (optional) use integer or arrayref (unable to use with ArrayHashRef)
|
|
SelectedValue => 'test', # (optional) use string or arrayref (unable to use with ArrayHashRef)
|
|
SelectedValue => ['test', 'test1'], # (optional) use string or arrayref (unable to use with ArrayHashRef)
|
|
|
|
Sort => 'NumericValue', # (optional) (AlphanumericValue|NumericValue|AlphanumericKey|NumericKey|TreeView|IndividualKey|IndividualValue) unable to use with ArrayHashRef
|
|
SortIndividual => ['sec', 'min'] # (optional) only sort is set to IndividualKey or IndividualValue
|
|
SortReverse => 0, # (optional) reverse the list
|
|
|
|
Translation => 1, # (optional) default 1 (0|1) translate value
|
|
PossibleNone => 0, # (optional) default 0 (0|1) add a leading empty selection
|
|
TreeView => 0, # (optional) default 0 (0|1)
|
|
DisabledBranch => 'Branch', # (optional) disable all elements of this branch (use string or arrayref)
|
|
Max => 100, # (optional) default 100 max size of the shown value
|
|
HTMLQuote => 0, # (optional) default 1 (0|1) disable html quote
|
|
Title => 'C<Tooltip> Text', # (optional) string will be shown as c<Tooltip> on c<mouseover>
|
|
OptionTitle => 1, # (optional) default 0 (0|1) show title attribute (the option value) on every option element
|
|
|
|
Filters => { # (optional) filter data, used by InputFields
|
|
LastOwners => { # filter id
|
|
Name => 'Last owners', # name of the filter
|
|
Values => { # filtered data structure
|
|
Key1 => 'Value1',
|
|
Key2 => 'Value2',
|
|
Key3 => 'Value3',
|
|
},
|
|
Active => 1, # (optional) default 0 (0|1) make this filter immediately active
|
|
},
|
|
InvolvedAgents => {
|
|
Name => 'Involved in this ticket',
|
|
Values => \%HashWithData,
|
|
},
|
|
},
|
|
ExpandFilters => 1, # (optional) default 0 (0|1) expand filters list by default
|
|
|
|
ValidateDateAfter => '2016-01-01', # (optional) validate that date is after supplied value
|
|
ValidateDateBefore => '2016-01-01', # (optional) validate that date is before supplied value
|
|
);
|
|
|
|
my $HashRef = {
|
|
Key1 => 'Value1',
|
|
Key2 => 'Value2',
|
|
Key3 => 'Value3',
|
|
};
|
|
|
|
my $ArrayRef = [
|
|
'KeyValue1',
|
|
'KeyValue2',
|
|
'KeyValue3',
|
|
'KeyValue4',
|
|
];
|
|
|
|
my $ArrayHashRef = [
|
|
{
|
|
Key => '1',
|
|
Value => 'Value1',
|
|
},
|
|
{
|
|
Key => '2',
|
|
Value => 'Value1::Subvalue1',
|
|
Selected => 1,
|
|
},
|
|
{
|
|
Key => '3',
|
|
Value => 'Value1::Subvalue2',
|
|
},
|
|
{
|
|
Key => '4',
|
|
Value => 'Value2',
|
|
Disabled => 1,
|
|
}
|
|
];
|
|
|
|
=cut
|
|
|
|
sub BuildSelection {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for (qw(Name Data)) {
|
|
if ( !$Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# The parameters 'Ajax' and 'OnChange' are exclusive
|
|
if ( $Param{Ajax} && $Param{OnChange} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "The parameters 'OnChange' and 'Ajax' exclude each other!"
|
|
);
|
|
return;
|
|
}
|
|
|
|
# set OnChange if AJAX is used
|
|
if ( $Param{Ajax} ) {
|
|
if ( !$Param{Ajax}->{Depend} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Depend Param Ajax option!',
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
if ( !$Param{Ajax}->{Update} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Update Param Ajax option()!',
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
my $Selector = $Param{ID} || $Param{Name};
|
|
$Param{OnChange} = "Core.AJAX.FormUpdate(\$('#"
|
|
. $Selector . "'), '" . $Param{Ajax}->{Subaction} . "',"
|
|
. " '$Param{Name}',"
|
|
. " ['"
|
|
. join( "', '", @{ $Param{Ajax}->{Update} } ) . "']);";
|
|
}
|
|
|
|
# create OptionRef
|
|
my $OptionRef = $Self->_BuildSelectionOptionRefCreate(%Param);
|
|
|
|
# create AttributeRef
|
|
my $AttributeRef = $Self->_BuildSelectionAttributeRefCreate(%Param);
|
|
|
|
# create DataRef
|
|
my $DataRef = $Self->_BuildSelectionDataRefCreate(
|
|
Data => $Param{Data},
|
|
AttributeRef => $AttributeRef,
|
|
OptionRef => $OptionRef,
|
|
);
|
|
|
|
# create FiltersRef
|
|
my @Filters;
|
|
my $FilterActive;
|
|
if ( $Param{Filters} ) {
|
|
my $Index = 1;
|
|
for my $Filter ( sort keys %{ $Param{Filters} } ) {
|
|
if (
|
|
$Param{Filters}->{$Filter}->{Name}
|
|
&& $Param{Filters}->{$Filter}->{Values}
|
|
)
|
|
{
|
|
my $FilterData = $Self->_BuildSelectionDataRefCreate(
|
|
Data => $Param{Filters}->{$Filter}->{Values},
|
|
AttributeRef => $AttributeRef,
|
|
OptionRef => $OptionRef,
|
|
);
|
|
push @Filters, {
|
|
Name => $Param{Filters}->{$Filter}->{Name},
|
|
Data => $FilterData,
|
|
};
|
|
if ( $Param{Filters}->{$Filter}->{Active} ) {
|
|
$FilterActive = $Index;
|
|
}
|
|
}
|
|
else {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Each Filter must provide Name and Values!',
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
$Index++;
|
|
}
|
|
@Filters = sort { $a->{Name} cmp $b->{Name} } @Filters;
|
|
}
|
|
|
|
# generate output
|
|
my $String = $Self->_BuildSelectionOutput(
|
|
AttributeRef => $AttributeRef,
|
|
DataRef => $DataRef,
|
|
OptionTitle => $Param{OptionTitle},
|
|
TreeView => $Param{TreeView},
|
|
FiltersRef => \@Filters,
|
|
FilterActive => $FilterActive,
|
|
ExpandFilters => $Param{ExpandFilters},
|
|
ValidateDateAfter => $Param{ValidateDateAfter},
|
|
ValidateDateBefore => $Param{ValidateDateBefore},
|
|
);
|
|
return $String;
|
|
}
|
|
|
|
sub NoPermission {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $WithHeader = $Param{WithHeader} || 'yes';
|
|
|
|
if ( !$Param{Message} ) {
|
|
$Param{Message} = $Self->{LanguageObject}->Translate(
|
|
"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:"
|
|
);
|
|
}
|
|
|
|
# get config option for possible next actions
|
|
my $PossibleNextActions = $Kernel::OM->Get('Kernel::Config')->Get('PossibleNextActions');
|
|
|
|
POSSIBLE:
|
|
if ( IsHashRefWithData($PossibleNextActions) ) {
|
|
$Self->Block(
|
|
Name => 'PossibleNextActionContainer',
|
|
);
|
|
for my $Key ( sort keys %{$PossibleNextActions} ) {
|
|
next POSSIBLE if !$Key;
|
|
next POSSIBLE if !$PossibleNextActions->{$Key};
|
|
|
|
$Self->Block(
|
|
Name => 'PossibleNextActionRow',
|
|
Data => {
|
|
Link => $Key,
|
|
Description => $PossibleNextActions->{$Key},
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# create output
|
|
my $Output;
|
|
$Output = $Self->Header( Title => 'Insufficient Rights' ) if ( $WithHeader eq 'yes' );
|
|
$Output .= $Self->Output(
|
|
TemplateFile => 'NoPermission',
|
|
Data => \%Param
|
|
);
|
|
$Output .= $Self->Footer() if ( $WithHeader eq 'yes' );
|
|
|
|
# return output
|
|
return $Output;
|
|
}
|
|
|
|
=head2 Permission()
|
|
|
|
check if access to a frontend module exists
|
|
|
|
my $Access = $LayoutObject->Permission(
|
|
Action => 'AdminCustomerUser',
|
|
Type => 'rw', # ro|rw possible
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Permission {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
for my $Needed (qw(Action Type)) {
|
|
if ( !defined $Param{$Needed} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Got no $Needed!",
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
}
|
|
|
|
# Get config option for frontend module.
|
|
my $Config = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::Module')->{ $Param{Action} };
|
|
return if !$Config;
|
|
|
|
my $Item = $Config->{ $Param{Type} eq 'ro' ? 'GroupRo' : 'Group' };
|
|
|
|
my $GroupObject = $Kernel::OM->Get(
|
|
$Self->{UserType} eq 'Customer' ? 'Kernel::System::CustomerGroup' : 'Kernel::System::Group'
|
|
);
|
|
|
|
# No access restriction?
|
|
if (
|
|
ref $Config->{GroupRo} eq 'ARRAY'
|
|
&& !scalar @{ $Config->{GroupRo} }
|
|
&& ref $Config->{Group} eq 'ARRAY'
|
|
&& !scalar @{ $Config->{Group} }
|
|
)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
# Array access restriction.
|
|
elsif ( IsArrayRefWithData($Item) ) {
|
|
for my $GroupName ( @{$Item} ) {
|
|
return 1 if $GroupObject->PermissionCheck(
|
|
UserID => $Self->{UserID},
|
|
GroupName => $GroupName,
|
|
Type => $Param{Type},
|
|
);
|
|
}
|
|
}
|
|
|
|
# Allow access if there is no configuration for module group permission.
|
|
elsif ( !IsArrayRefWithData( $Config->{GroupRo} ) && !IsArrayRefWithData( $Config->{Group} ) ) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub CheckMimeType {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Output = '';
|
|
if ( !$Param{Action} ) {
|
|
$Param{Action} = '[% Env("Action") %]';
|
|
}
|
|
|
|
# check if it is a text/plain email
|
|
if ( $Param{MimeType} && $Param{MimeType} !~ /text\/plain/i ) {
|
|
$Output = '<p><i class="small">'
|
|
. $Self->{LanguageObject}->Translate("This is a")
|
|
. " $Param{MimeType} "
|
|
. $Self->{LanguageObject}->Translate("email")
|
|
. ', <a href="'
|
|
. $Self->{Baselink}
|
|
. "Action=$Param{Action};TicketID="
|
|
. "$Param{TicketID};ArticleID=$Param{ArticleID};Subaction=ShowHTMLeMail\" "
|
|
. 'target="HTMLeMail">'
|
|
. $Self->{LanguageObject}->Translate("click here")
|
|
. '</a> '
|
|
. $Self->{LanguageObject}->Translate("to open it in a new window.")
|
|
. '</i></p>';
|
|
}
|
|
|
|
# just to be compat
|
|
elsif ( $Param{Body} =~ /^<.DOCTYPE\s+html|^<HTML>/i ) {
|
|
$Output = '<p><i class="small">'
|
|
. $Self->{LanguageObject}->Translate("This is a")
|
|
. " $Param{MimeType} "
|
|
. $Self->{LanguageObject}->Translate("email")
|
|
. ', <a href="'
|
|
. $Self->{Baselink}
|
|
. 'Action=$Param{Action};TicketID='
|
|
. "$Param{TicketID};ArticleID=$Param{ArticleID};Subaction=ShowHTMLeMail\" "
|
|
. 'target="HTMLeMail">'
|
|
. $Self->{LanguageObject}->Translate("click here")
|
|
. '</a> '
|
|
. $Self->{LanguageObject}->Translate("to open it in a new window.")
|
|
. '</i></p>';
|
|
}
|
|
|
|
# return note string
|
|
return $Output;
|
|
}
|
|
|
|
sub ReturnValue {
|
|
my ( $Self, $What ) = @_;
|
|
|
|
return $Self->{$What};
|
|
}
|
|
|
|
=head2 Attachment()
|
|
|
|
returns browser output to display/download a attachment
|
|
|
|
$HTML = $LayoutObject->Attachment(
|
|
Type => 'inline', # optional, default: attachment, possible: inline|attachment
|
|
Filename => 'FileName.png', # optional
|
|
AdditionalHeader => $AdditionalHeader, # optional
|
|
ContentType => 'image/png',
|
|
Content => $Content,
|
|
Sandbox => 1, # optional, default 0; use content security policy to prohibit external
|
|
# scripts, flash etc.
|
|
);
|
|
|
|
or for AJAX html snippets
|
|
|
|
$HTML = $LayoutObject->Attachment(
|
|
Type => 'inline', # optional, default: attachment, possible: inline|attachment
|
|
Filename => 'FileName.html', # optional
|
|
ContentType => 'text/html',
|
|
Charset => 'utf-8', # optional
|
|
Content => $Content,
|
|
NoCache => 1, # optional
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Attachment {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed params
|
|
for (qw(Content ContentType)) {
|
|
if ( !defined $Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Got no $_!",
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
}
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# return attachment
|
|
my $Output = 'Content-Disposition: ';
|
|
if ( $Param{Type} ) {
|
|
$Output .= $Param{Type};
|
|
$Output .= '; ';
|
|
}
|
|
else {
|
|
$Output .= $ConfigObject->Get('AttachmentDownloadType') || 'attachment';
|
|
$Output .= '; ';
|
|
}
|
|
|
|
if ( $Param{Filename} ) {
|
|
|
|
# IE 10+ supports this
|
|
my $URLEncodedFilename = URI::Escape::uri_escape_utf8( $Param{Filename} );
|
|
$Output .= " filename=\"$Param{Filename}\"; filename*=utf-8''$URLEncodedFilename";
|
|
}
|
|
$Output .= "\n";
|
|
|
|
# get attachment size
|
|
$Param{Size} = bytes::length( $Param{Content} );
|
|
|
|
# add no cache headers
|
|
if ( $Param{NoCache} ) {
|
|
$Output .= "Expires: Tue, 1 Jan 1980 12:00:00 GMT\n";
|
|
$Output .= "Cache-Control: no-cache\n";
|
|
$Output .= "Pragma: no-cache\n";
|
|
}
|
|
$Output .= "Content-Length: $Param{Size}\n";
|
|
$Output .= "X-UA-Compatible: IE=edge,chrome=1\n";
|
|
|
|
if ( !$ConfigObject->Get('DisableIFrameOriginRestricted') ) {
|
|
$Output .= "X-Frame-Options: SAMEORIGIN\n";
|
|
}
|
|
|
|
if ( $Param{Sandbox} && !$Kernel::OM->Get('Kernel::Config')->Get('DisableContentSecurityPolicy') ) {
|
|
|
|
# Disallow external and inline scripts, active content, frames, but keep allowing inline styles
|
|
# as this is a common use case in emails.
|
|
# Also disallow referrer headers to prevent referrer leaks via old-style policy directive. Please note this has
|
|
# been deprecated and will be removed in future OTRS versions in favor of a separate header (see below).
|
|
# img-src: allow external and inline (data:) images
|
|
# script-src: block all scripts
|
|
# object-src: allow 'self' so that the browser can load plugins for PDF display
|
|
# frame-src: block all frames
|
|
# style-src: allow inline styles for nice email display
|
|
# referrer: don't send referrers to prevent referrer-leak attacks
|
|
$Output
|
|
.= "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";
|
|
|
|
# Use Referrer-Policy header to suppress referrer information in modern browsers
|
|
# (to prevent referrer-leak attacks).
|
|
$Output .= "Referrer-Policy: no-referrer\n";
|
|
}
|
|
|
|
if ( $Param{AdditionalHeader} ) {
|
|
$Output .= $Param{AdditionalHeader} . "\n";
|
|
}
|
|
|
|
if ( $Param{Charset} ) {
|
|
$Output .= "Content-Type: $Param{ContentType}; charset=$Param{Charset};\n\n";
|
|
}
|
|
else {
|
|
$Output .= "Content-Type: $Param{ContentType}\n\n";
|
|
}
|
|
|
|
# disable utf8 flag, to write binary to output
|
|
my $EncodeObject = $Kernel::OM->Get('Kernel::System::Encode');
|
|
$EncodeObject->EncodeOutput( \$Output );
|
|
$EncodeObject->EncodeOutput( \$Param{Content} );
|
|
|
|
# fix for firefox HEAD problem
|
|
if ( !$ENV{REQUEST_METHOD} || $ENV{REQUEST_METHOD} ne 'HEAD' ) {
|
|
$Output .= $Param{Content};
|
|
}
|
|
|
|
# reset binmode, don't use utf8
|
|
binmode STDOUT, ':bytes';
|
|
|
|
return $Output;
|
|
}
|
|
|
|
=head2 PageNavBar()
|
|
|
|
generates a page navigation bar
|
|
|
|
my %PageNavBar = $LayoutObject->PageNavBar(
|
|
Limit => 100, # marks result of TotalHits red if Limit is gerater then AllHits
|
|
WindowSize => 15, # max shown pages to click
|
|
StartHit => 1, # start to show items
|
|
PageShown => 15, # number of shown items a page
|
|
AllHits => 56, # number of total hits
|
|
Action => 'AgentXXX', # e. g. 'Action=' . $Self->{LayoutObject}->{Action}
|
|
Link => $Link, # e. g. 'Subaction=View;'
|
|
AJAXReplace => 'IDElement', # IDElement which should be replaced
|
|
IDPrefix => 'Tickets', # Prefix for the id parameter
|
|
);
|
|
|
|
return values of hash
|
|
|
|
TotalHits # total hits
|
|
Result # shown items e. g. "1-5" or "16-30"
|
|
SiteNavBar # html for page nav bar e. g. "1 2 3 4"
|
|
|
|
ResultLong # shown items e. g. "1-5 of 32" or "16-30 of 64"
|
|
SiteNavBarLong # html for page nav bar e. g. "Page: 1 2 3 4"
|
|
|
|
=cut
|
|
|
|
sub PageNavBar {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Limit = $Param{Limit} || 0;
|
|
$Param{AllHits} = 0 if ( !$Param{AllHits} );
|
|
$Param{StartHit} = 0 if ( !$Param{AllHits} );
|
|
my $Pages = int( ( $Param{AllHits} / $Param{PageShown} ) + 0.99999 );
|
|
my $Page = int( ( $Param{StartHit} / $Param{PageShown} ) + 0.99999 );
|
|
my $WindowSize = $Param{WindowSize} || 5;
|
|
my $IDPrefix = $Param{IDPrefix} || 'Generic';
|
|
|
|
# build Results (1-5 or 16-30)
|
|
if ( $Param{AllHits} >= ( $Param{StartHit} + $Param{PageShown} ) ) {
|
|
$Param{Results} = $Param{StartHit} . "-" . ( $Param{StartHit} + $Param{PageShown} - 1 );
|
|
}
|
|
else {
|
|
$Param{Results} = "$Param{StartHit}-$Param{AllHits}";
|
|
}
|
|
|
|
# check total hits
|
|
if ( $Limit == $Param{AllHits} ) {
|
|
$Param{TotalHits} = "<span class=\"PaginationLimit\">$Param{AllHits}</span>";
|
|
}
|
|
else {
|
|
$Param{TotalHits} = $Param{AllHits};
|
|
}
|
|
|
|
# build page nav bar
|
|
my $WindowStart = sprintf( "%.0f", ( $Param{StartHit} / $Param{PageShown} ) );
|
|
$WindowStart = int( ( $WindowStart / $WindowSize ) ) + 1;
|
|
$WindowStart = ( $WindowStart * $WindowSize ) - ($WindowSize);
|
|
my $Action = $Param{Action} || '';
|
|
my $Link = $Param{Link} || '';
|
|
my $Baselink = "$Self->{Baselink}$Action;$Link";
|
|
my $i = 0;
|
|
my %PaginationData;
|
|
my $WidgetName;
|
|
my $ClassWidgetName;
|
|
|
|
if ( $Param{AJAXReplace} ) {
|
|
$WidgetName = $Param{AJAXReplace};
|
|
$WidgetName =~ s{-}{}xmsg;
|
|
|
|
$ClassWidgetName = $WidgetName;
|
|
$ClassWidgetName =~ s/^Dashboard//;
|
|
}
|
|
|
|
while ( $i <= ( $Pages - 1 ) ) {
|
|
$i++;
|
|
|
|
# show normal page 1,2,3,...
|
|
if ( $i <= ( $WindowStart + $WindowSize ) && $i > $WindowStart ) {
|
|
my $BaselinkAll = $Baselink
|
|
. "StartWindow=$WindowStart;StartHit="
|
|
. ( ( ( $i - 1 ) * $Param{PageShown} ) + 1 );
|
|
my $SelectedPage = '';
|
|
my $PageNumber = $i;
|
|
|
|
if ( $Page == $i ) {
|
|
$SelectedPage = 'Selected';
|
|
}
|
|
if ( $Param{AJAXReplace} ) {
|
|
|
|
$PaginationData{$PageNumber} = {
|
|
Baselink => $BaselinkAll,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
WidgetName => $ClassWidgetName
|
|
};
|
|
|
|
$Self->Block(
|
|
Name => 'PageAjax',
|
|
Data => {
|
|
BaselinkAll => $BaselinkAll,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
PageNumber => $PageNumber,
|
|
IDPrefix => $IDPrefix,
|
|
SelectedPage => $SelectedPage,
|
|
WidgetName => $ClassWidgetName
|
|
},
|
|
);
|
|
}
|
|
else {
|
|
$Self->Block(
|
|
Name => 'Page',
|
|
Data => {
|
|
BaselinkAll => $BaselinkAll,
|
|
PageNumber => $PageNumber,
|
|
IDPrefix => $IDPrefix,
|
|
SelectedPage => $SelectedPage
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# over window ">>" and ">|"
|
|
elsif ( $i > ( $WindowStart + $WindowSize ) ) {
|
|
my $StartWindow = $WindowStart + $WindowSize + 1;
|
|
my $LastStartWindow = int( $Pages / $WindowSize );
|
|
my $BaselinkOneForward = $Baselink . "StartHit=" . ( ( $i - 1 ) * $Param{PageShown} + 1 );
|
|
my $BaselinkAllForward = $Baselink . "StartHit=" . ( ( $Param{PageShown} * ( $Pages - 1 ) ) + 1 );
|
|
|
|
if ( $Param{AJAXReplace} ) {
|
|
$PaginationData{$BaselinkOneForward} = {
|
|
Baselink => $BaselinkOneForward,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
WidgetName => $ClassWidgetName
|
|
};
|
|
$PaginationData{$BaselinkAllForward} = {
|
|
Baselink => $BaselinkAllForward,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
WidgetName => $ClassWidgetName
|
|
};
|
|
|
|
$Self->Block(
|
|
Name => 'PageForwardAjax',
|
|
Data => {
|
|
BaselinkOneForward => $BaselinkOneForward,
|
|
BaselinkAllForward => $BaselinkAllForward,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
IDPrefix => $IDPrefix,
|
|
WidgetName => $ClassWidgetName
|
|
},
|
|
);
|
|
}
|
|
else {
|
|
$Self->Block(
|
|
Name => 'PageForward',
|
|
Data => {
|
|
BaselinkOneForward => $BaselinkOneForward,
|
|
BaselinkAllForward => $BaselinkAllForward,
|
|
IDPrefix => $IDPrefix,
|
|
},
|
|
);
|
|
}
|
|
|
|
$i = 99999999;
|
|
}
|
|
|
|
# over window "<<" and "|<"
|
|
elsif ( $i < $WindowStart && ( $i - 1 ) < $Pages ) {
|
|
my $StartWindow = $WindowStart - $WindowSize - 1;
|
|
my $BaselinkAllBack = $Baselink . 'StartHit=1;StartWindow=1';
|
|
my $BaselinkOneBack = $Baselink . 'StartHit=' . ( ( $WindowStart - 1 ) * ( $Param{PageShown} ) + 1 );
|
|
|
|
if ( $Param{AJAXReplace} ) {
|
|
|
|
$PaginationData{$BaselinkOneBack} = {
|
|
Baselink => $BaselinkOneBack,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
WidgetName => $ClassWidgetName
|
|
};
|
|
$PaginationData{$BaselinkAllBack} = {
|
|
Baselink => $BaselinkAllBack,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
WidgetName => $ClassWidgetName
|
|
};
|
|
|
|
$Self->Block(
|
|
Name => 'PageBackAjax',
|
|
Data => {
|
|
BaselinkOneBack => $BaselinkOneBack,
|
|
BaselinkAllBack => $BaselinkAllBack,
|
|
AjaxReplace => $Param{AJAXReplace},
|
|
IDPrefix => $IDPrefix,
|
|
WidgetName => $ClassWidgetName
|
|
},
|
|
);
|
|
}
|
|
else {
|
|
$Self->Block(
|
|
Name => 'PageBack',
|
|
Data => {
|
|
BaselinkOneBack => $BaselinkOneBack,
|
|
BaselinkAllBack => $BaselinkAllBack,
|
|
IDPrefix => $IDPrefix,
|
|
},
|
|
);
|
|
}
|
|
|
|
$i = $WindowStart - 1;
|
|
}
|
|
}
|
|
|
|
# send data to JS
|
|
if ( $Param{AJAXReplace} ) {
|
|
$Self->AddJSData(
|
|
Key => 'PaginationData' . $ClassWidgetName,
|
|
Value => \%PaginationData
|
|
);
|
|
}
|
|
|
|
$Param{SearchNavBar} = $Self->Output(
|
|
TemplateFile => 'Pagination',
|
|
AJAX => $Param{AJAX},
|
|
);
|
|
|
|
# only show total amount of pages if there is more than one
|
|
if ( $Pages > 1 ) {
|
|
$Param{NavBarLong} = "- " . $Self->{LanguageObject}->Translate("Page") . ": $Param{SearchNavBar}";
|
|
}
|
|
else {
|
|
$Param{SearchNavBar} = '';
|
|
}
|
|
|
|
# return data
|
|
return (
|
|
TotalHits => $Param{TotalHits},
|
|
Result => $Param{Results},
|
|
ResultLong => "$Param{Results} "
|
|
. $Self->{LanguageObject}->Translate("of")
|
|
. " $Param{TotalHits}",
|
|
SiteNavBar => $Param{SearchNavBar},
|
|
SiteNavBarLong => $Param{NavBarLong},
|
|
Link => $Param{Link},
|
|
);
|
|
}
|
|
|
|
sub NavigationBar {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
if ( !$Param{Type} ) {
|
|
$Param{Type} = $Self->{ModuleReg}->{NavBarName} || 'Ticket';
|
|
}
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# Create menu items.
|
|
my %NavBar;
|
|
|
|
my $FrontendRegistration = $ConfigObject->Get('Frontend::Module');
|
|
my $FrontendNavigation = $ConfigObject->Get('Frontend::Navigation');
|
|
|
|
my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
|
|
|
|
MODULE:
|
|
for my $Module ( sort keys %{$FrontendNavigation} ) {
|
|
|
|
# Skip if module is disabled in frontend registration.
|
|
next MODULE if !IsHashRefWithData( $FrontendRegistration->{$Module} );
|
|
|
|
# Top-level frontend navigation configuration should always be a hash.
|
|
next MODULE if !IsHashRefWithData( $FrontendNavigation->{$Module} );
|
|
|
|
my @ModuleNavigationConfigs;
|
|
|
|
# Go through all defined navigation configurations for the module and sort them by the key (00#-Module).
|
|
NAVIGATION_CONFIG:
|
|
for my $Key ( sort keys %{ $FrontendNavigation->{$Module} || {} } ) {
|
|
next NAVIGATION_CONFIG if $Key !~ m{^\d+};
|
|
|
|
# FIXME: Support both old (HASH) and new (ARRAY of HASH) navigation configurations, for reasons of backwards
|
|
# compatibility. Once we are sure everything has been migrated correctly, support for HASH-only
|
|
# configuration can be dropped in future major release.
|
|
if ( IsHashRefWithData( $FrontendNavigation->{$Module}->{$Key} ) ) {
|
|
push @ModuleNavigationConfigs, $FrontendNavigation->{$Module}->{$Key};
|
|
}
|
|
elsif ( IsArrayRefWithData( $FrontendNavigation->{$Module}->{$Key} ) ) {
|
|
push @ModuleNavigationConfigs, @{ $FrontendNavigation->{$Module}->{$Key} };
|
|
}
|
|
|
|
# Skip incompatible configuration.
|
|
else {
|
|
next NAVIGATION_CONFIG;
|
|
}
|
|
}
|
|
|
|
ITEM:
|
|
for my $Item (@ModuleNavigationConfigs) {
|
|
next ITEM if !$Item->{NavBar};
|
|
|
|
$Item->{CSS} = '';
|
|
|
|
# Highlight active area link.
|
|
if (
|
|
( $Item->{Type} && $Item->{Type} eq 'Menu' )
|
|
&& ( $Item->{NavBar} && $Item->{NavBar} eq $Param{Type} )
|
|
)
|
|
{
|
|
$Item->{CSS} .= ' Selected';
|
|
}
|
|
|
|
my $InheritPermissions = 0;
|
|
|
|
# Inherit permissions from frontend registration if no permissions were defined for the navigation entry.
|
|
if ( !$Item->{GroupRo} && !$Item->{Group} ) {
|
|
if ( $FrontendRegistration->{GroupRo} ) {
|
|
$Item->{GroupRo} = $FrontendRegistration->{GroupRo};
|
|
}
|
|
if ( $FrontendRegistration->{Group} ) {
|
|
$Item->{Group} = $FrontendRegistration->{Group};
|
|
}
|
|
$InheritPermissions = 1;
|
|
}
|
|
|
|
my $Shown = 0;
|
|
|
|
PERMISSION:
|
|
for my $Permission (qw(GroupRo Group)) {
|
|
|
|
# No access restriction.
|
|
if (
|
|
ref $Item->{GroupRo} eq 'ARRAY'
|
|
&& !scalar @{ $Item->{GroupRo} }
|
|
&& ref $Item->{Group} eq 'ARRAY'
|
|
&& !scalar @{ $Item->{Group} }
|
|
)
|
|
{
|
|
$Shown = 1;
|
|
last PERMISSION;
|
|
}
|
|
|
|
# Array access restriction.
|
|
elsif ( $Item->{$Permission} && ref $Item->{$Permission} eq 'ARRAY' ) {
|
|
GROUP:
|
|
for my $Group ( @{ $Item->{$Permission} } ) {
|
|
next GROUP if !$Group;
|
|
my $HasPermission = $GroupObject->PermissionCheck(
|
|
UserID => $Self->{UserID},
|
|
GroupName => $Group,
|
|
Type => $Permission eq 'GroupRo' ? 'ro' : 'rw',
|
|
|
|
);
|
|
if ($HasPermission) {
|
|
$Shown = 1;
|
|
last PERMISSION;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# If we passed the initial permission check and didn't inherit permissions from the module registration,
|
|
# make sure to also check access to the module, since navigation item might be out of sync.
|
|
if ( $Shown && !$InheritPermissions ) {
|
|
my $ModulePermission;
|
|
|
|
PERMISSION:
|
|
for my $Permission (qw(GroupRo Group)) {
|
|
|
|
# No access restriction.
|
|
if (
|
|
ref $FrontendRegistration->{$Module}->{GroupRo} eq 'ARRAY'
|
|
&& !scalar @{ $FrontendRegistration->{$Module}->{GroupRo} }
|
|
&& ref $FrontendRegistration->{$Module}->{Group} eq 'ARRAY'
|
|
&& !scalar @{ $FrontendRegistration->{$Module}->{Group} }
|
|
)
|
|
{
|
|
|
|
$ModulePermission = 1;
|
|
last PERMISSION;
|
|
}
|
|
|
|
# Array access restriction.
|
|
elsif (
|
|
$FrontendRegistration->{$Module}->{$Permission}
|
|
&& ref $FrontendRegistration->{$Module}->{$Permission} eq 'ARRAY'
|
|
)
|
|
{
|
|
GROUP:
|
|
for my $Group ( @{ $FrontendRegistration->{$Module}->{$Permission} } ) {
|
|
next GROUP if !$Group;
|
|
my $HasPermission = $GroupObject->PermissionCheck(
|
|
UserID => $Self->{UserID},
|
|
GroupName => $Group,
|
|
Type => $Permission eq 'GroupRo' ? 'ro' : 'rw',
|
|
|
|
);
|
|
if ($HasPermission) {
|
|
$ModulePermission = 1;
|
|
last PERMISSION;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Hide item if no permission was granted to access the module.
|
|
if ( !$ModulePermission ) {
|
|
$Shown = 0;
|
|
}
|
|
}
|
|
|
|
next ITEM if !$Shown;
|
|
|
|
# set prio of item
|
|
my $Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
|
|
COUNT:
|
|
for ( 1 .. 51 ) {
|
|
last COUNT if !$NavBar{$Key};
|
|
|
|
$Item->{Prio}++;
|
|
$Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
|
|
}
|
|
|
|
# show as main menu
|
|
if ( $Item->{Type} eq 'Menu' ) {
|
|
$NavBar{$Key} = $Item;
|
|
}
|
|
|
|
# show as sub of main menu
|
|
else {
|
|
$NavBar{Sub}->{ $Item->{NavBar} }->{$Key} = $Item;
|
|
}
|
|
}
|
|
}
|
|
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
# run menu item modules
|
|
if ( ref $ConfigObject->Get('Frontend::NavBarModule') eq 'HASH' ) {
|
|
my %Jobs = %{ $ConfigObject->Get('Frontend::NavBarModule') };
|
|
|
|
MENUMODULE:
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load module
|
|
next MENUMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
|
|
my $Object = $Jobs{$Job}->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
next MENUMODULE if !$Object;
|
|
|
|
# run module
|
|
%NavBar = (
|
|
%NavBar,
|
|
$Object->Run(
|
|
%Param,
|
|
Config => $Jobs{$Job},
|
|
NavBar => \%NavBar || {}
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
# show nav bar
|
|
ITEM:
|
|
for my $Key ( sort keys %NavBar ) {
|
|
next ITEM if $Key eq 'Sub';
|
|
next ITEM if !%{ $NavBar{$Key} };
|
|
my $Item = $NavBar{$Key};
|
|
$Item->{NameForID} = $Item->{Name};
|
|
$Item->{NameForID} =~ s/[ &;]//ig;
|
|
my $Sub = $NavBar{Sub}->{ $Item->{NavBar} };
|
|
|
|
$Self->Block(
|
|
Name => 'ItemArea',
|
|
Data => {
|
|
%$Item,
|
|
AccessKeyReference => $Item->{AccessKey} ? " ($Item->{AccessKey})" : '',
|
|
},
|
|
);
|
|
|
|
# show sub menu (only if sub elements available)
|
|
next ITEM if !$Sub;
|
|
next ITEM if !keys %{$Sub};
|
|
|
|
$Self->Block(
|
|
Name => 'ItemAreaSub',
|
|
Data => $Item,
|
|
);
|
|
for my $Key ( sort keys %{$Sub} ) {
|
|
my $ItemSub = $Sub->{$Key};
|
|
$ItemSub->{NameForID} = $ItemSub->{Name};
|
|
$ItemSub->{NameForID} =~ s/[ &;]//ig;
|
|
$ItemSub->{NameTop} = $Item->{NameForID};
|
|
$ItemSub->{Description}
|
|
||= $ItemSub->{Name}; # use 'name' as fallback, this is shown as the link title
|
|
$Self->Block(
|
|
Name => 'ItemAreaSubItem', #$Item->{Block} || 'Item',
|
|
Data => {
|
|
%$ItemSub,
|
|
AccessKeyReference => $ItemSub->{AccessKey} ? " ($ItemSub->{AccessKey})" : '',
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# get user preferences for custom nav bar item ordering
|
|
my %UserPreferences = $Kernel::OM->Get('Kernel::System::User')->GetPreferences(
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
my $NavbarOrderItems = $UserPreferences{'UserNavBarItemsOrder'} || '';
|
|
$Self->AddJSData(
|
|
Key => 'NavbarOrderItems',
|
|
Value => $NavbarOrderItems,
|
|
);
|
|
|
|
my $FrontendSearch = $ConfigObject->Get('Frontend::Search') || {};
|
|
|
|
my $SearchAdded;
|
|
|
|
# show search icon if any search router is configured
|
|
if ( IsHashRefWithData($FrontendSearch) ) {
|
|
|
|
KEY:
|
|
for my $Key ( sort keys %{$FrontendSearch} ) {
|
|
next KEY if !IsHashRefWithData( $FrontendSearch->{$Key} );
|
|
|
|
for my $Regex ( sort keys %{ $FrontendSearch->{$Key} } ) {
|
|
next KEY if !$Regex;
|
|
|
|
# Check if regex matches current action.
|
|
if ( $Self->{Action} =~ m{$Regex}g ) {
|
|
|
|
# Extract Action from the configuration.
|
|
my ($Action) = $FrontendSearch->{$Key}->{$Regex} =~ m{Action=(.*?)(;.*)?$};
|
|
|
|
# Do not show Search icon if action is not registered.
|
|
next KEY if !$FrontendRegistration->{$Action};
|
|
|
|
$Self->Block(
|
|
Name => 'SearchIcon',
|
|
);
|
|
|
|
$SearchAdded = 1;
|
|
last KEY;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# If Search icon is not added, check if AgentTicketSearch is enabled and add it.
|
|
if ( !$SearchAdded && $FrontendRegistration->{AgentTicketSearch} ) {
|
|
$Self->Block(
|
|
Name => 'SearchIcon',
|
|
);
|
|
}
|
|
|
|
# create & return output
|
|
my $Output = $Self->Output(
|
|
TemplateFile => 'AgentNavigationBar',
|
|
Data => \%Param,
|
|
);
|
|
|
|
# run nav bar output modules
|
|
my $NavBarOutputModuleConfig = $ConfigObject->Get('Frontend::NavBarOutputModule');
|
|
if ( ref $NavBarOutputModuleConfig eq 'HASH' ) {
|
|
my %Jobs = %{$NavBarOutputModuleConfig};
|
|
|
|
OUTPUTMODULE:
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load module
|
|
next OUTPUTMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
|
|
my $Object = $Jobs{$Job}->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
next OUTPUTMODULE if !$Object;
|
|
|
|
# run module
|
|
$Output .= $Object->Run( %Param, Config => $Jobs{$Job} );
|
|
}
|
|
}
|
|
|
|
# run notification modules
|
|
my $FrontendNotifyModuleConfig = $ConfigObject->Get('Frontend::NotifyModule');
|
|
if ( ref $FrontendNotifyModuleConfig eq 'HASH' ) {
|
|
my %Jobs = %{$FrontendNotifyModuleConfig};
|
|
|
|
NOTIFICATIONMODULE:
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load module
|
|
next NOTIFICATIONMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
|
|
my $Object = $Jobs{$Job}->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
next NOTIFICATIONMODULE if !$Object;
|
|
|
|
# run module
|
|
$Output .= $Object->Run( %Param, Config => $Jobs{$Job} );
|
|
}
|
|
}
|
|
|
|
# run nav bar modules
|
|
if ( $Self->{NavigationModule} ) {
|
|
|
|
# run navbar modules
|
|
my %Jobs = %{ $Self->{NavigationModule} };
|
|
|
|
# load module
|
|
if ( !$MainObject->Require( $Jobs{Module} ) ) {
|
|
return $Output;
|
|
}
|
|
|
|
my $Object = $Jobs{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
|
|
if ( !$Object ) {
|
|
return $Output;
|
|
}
|
|
|
|
# run module
|
|
$Output .= $Object->Run( %Param, Config => \%Jobs );
|
|
}
|
|
return $Output;
|
|
}
|
|
|
|
sub TransformDateSelection {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# get key prefix
|
|
my $Prefix = $Param{Prefix} || '';
|
|
|
|
# time zone translation if needed
|
|
# from user time zone to OTRS time zone
|
|
if ( $Self->{UserTimeZone} ) {
|
|
my $DateTimeObject = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
Year => $Param{ $Prefix . 'Year' },
|
|
Month => $Param{ $Prefix . 'Month' },
|
|
Day => $Param{ $Prefix . 'Day' },
|
|
Hour => $Param{ $Prefix . 'Hour' } || 0,
|
|
Minute => $Param{ $Prefix . 'Minute' } || 0,
|
|
Second => $Param{ $Prefix . 'Second' } || 0,
|
|
TimeZone => $Self->{UserTimeZone},
|
|
},
|
|
);
|
|
|
|
if ($DateTimeObject) {
|
|
$DateTimeObject->ToOTRSTimeZone();
|
|
my $DateTimeValues = $DateTimeObject->Get();
|
|
|
|
$Param{ $Prefix . 'Year' } = $DateTimeValues->{Year};
|
|
$Param{ $Prefix . 'Month' } = $DateTimeValues->{Month};
|
|
$Param{ $Prefix . 'Day' } = $DateTimeValues->{Day};
|
|
$Param{ $Prefix . 'Hour' } = $DateTimeValues->{Hour};
|
|
$Param{ $Prefix . 'Minute' } = $DateTimeValues->{Minute};
|
|
$Param{ $Prefix . 'Second' } = $DateTimeValues->{Second};
|
|
}
|
|
}
|
|
|
|
# reset prefix
|
|
$Param{Prefix} = '';
|
|
|
|
return %Param;
|
|
}
|
|
|
|
=head2 BuildDateSelection()
|
|
|
|
build the HTML code to represent a date selection based on the given data.
|
|
Depending on the SysConfig settings the controls to set the date could be multiple select or input fields
|
|
|
|
my $HTML = $LayoutObject->BuildDateSelection(
|
|
Prefix => 'some prefix', # optional, (needed to specify other parameters)
|
|
<Prefix>Year => 2015, # optional, defaults to current year, used to set the initial value
|
|
<Prefix>Month => 6, # optional, defaults to current month, used to set the initial value
|
|
<Prefix>Day => 9, # optional, defaults to current day, used to set the initial value
|
|
<Prefix>Hour => 12, # optional, defaults to current hour, used to set the initial value
|
|
<Prefix>Minute => 26, # optional, defaults to current minute, used to set the initial value
|
|
<Prefix>Second => 59, # optional, defaults to current second, used to set the initial value
|
|
<Prefix>Optional => 1, # optional, default 0, when active a checkbox is included to specify
|
|
# if the values should be saved or not
|
|
<Prefix>Used => 1, # optional, default 0, used to set the initial state of the checkbox
|
|
# mentioned above
|
|
<Prefix>Required => 1, # optional, default 0 (Deprecated)
|
|
<prefix>Class => 'some class', # optional, specify an additional class to the HTML elements
|
|
Area => 'some area', # optional, default 'Agent' (Deprecated)
|
|
DiffTime => 123, # optional, default 0, used to set the initial time influencing the
|
|
# current time (in seconds)
|
|
OverrideTimeZone => 1, # optional (1 or 0), when active the time is not translated to the user
|
|
# time zone
|
|
YearPeriodFuture => 3, # optional, used to define the number of years in future to be display
|
|
# in the year select
|
|
YearPeriodPast => 2, # optional, used to define the number of years in past to be display
|
|
# in the year select
|
|
YearDiff => 0, # optional. used to define the number of years to be displayed
|
|
# in the year select (alternatively to YearPeriodFuture and YearPeriodPast)
|
|
ValidateDateInFuture => 1, # optional (1 or 0), when active sets an special class to validate
|
|
# that the date set in the controls to be in the future
|
|
ValidateDateNotInFuture => 1, # optional (1 or 0), when active sets an special class to validate
|
|
# that the date set in the controls not to be in the future
|
|
ValidateDateAfterPrefix => 'Start', # optional (Prefix), when defined sets a special class to validate
|
|
# that the date set in the controls comes after the date with Prefix
|
|
ValidateDateAfterValue => '2016-01-01', # optional (Date), when defined sets a special data parameter to validate
|
|
# that the date set in the controls comes after the supplied date
|
|
ValidateDateBeforePrefix => 'End', # optional (Prefix), when defined sets a special class to validate
|
|
# that the date set in the controls comes before the date with Prefix
|
|
ValidateDateBeforeValue => '2016-01-01', # optional (Date), when defined sets a special data parameter to validate
|
|
# that the date set in the controls comes before the supplied date
|
|
Calendar => 2, # optional, used to define the SysConfig calendar on which the Datepicker
|
|
# will be based on to show the vacation days and the start week day
|
|
Format => 'DateInputFormat', # optional, or 'DateInputFormatLong', used to define if only date or
|
|
# date/time components should be shown (DateInputFormatLong shows date/time)
|
|
Validate => 1, # optional (1 or 0), defines if the date selection should be validated on
|
|
# client side with JS
|
|
Disabled => 1, # optional (1 or 0), when active select and checkbox controls gets the
|
|
# disabled attribute and input fields gets the read only attribute
|
|
);
|
|
|
|
=cut
|
|
|
|
sub BuildDateSelection {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
my $DateInputStyle = $ConfigObject->Get('TimeInputFormat');
|
|
my $MinuteStep = $ConfigObject->Get('TimeInputMinutesStep');
|
|
my $Prefix = $Param{Prefix} || '';
|
|
my $DiffTime = $Param{DiffTime} || 0;
|
|
my $Format = defined( $Param{Format} ) ? $Param{Format} : 'DateInputFormatLong';
|
|
my $Area = $Param{Area} || 'Agent';
|
|
my $Optional = $Param{ $Prefix . 'Optional' } || 0;
|
|
my $Required = $Param{ $Prefix . 'Required' } || 0;
|
|
my $Used = $Param{ $Prefix . 'Used' } || 0;
|
|
my $Class = $Param{ $Prefix . 'Class' } || '';
|
|
|
|
# Defines, if the date selection should be validated on client side with JS
|
|
my $Validate = $Param{Validate} || 0;
|
|
|
|
# Validate that the date is in the future (e. g. pending times)
|
|
my $ValidateDateInFuture = $Param{ValidateDateInFuture} || 0;
|
|
my $ValidateDateNotInFuture = $Param{ValidateDateNotInFuture} || 0;
|
|
|
|
# Validate that the date is set after/before supplied date
|
|
my $ValidateDateAfterPrefix = $Param{ValidateDateAfterPrefix} || '';
|
|
my $ValidateDateAfterValue = $Param{ValidateDateAfterValue} || '';
|
|
my $ValidateDateBeforePrefix = $Param{ValidateDateBeforePrefix} || '';
|
|
my $ValidateDateBeforeValue = $Param{ValidateDateBeforeValue} || '';
|
|
|
|
my $GetCurSysDTUnitFromLowest = sub {
|
|
my %Param = @_;
|
|
|
|
my $DateTimeObject = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
TimeZone => $Self->{UserTimeZone}
|
|
}
|
|
);
|
|
if ( $Param{AddSeconds} ) {
|
|
$DateTimeObject->Add( Seconds => $Param{AddSeconds} );
|
|
}
|
|
|
|
my %Details = %{ $DateTimeObject->Get() };
|
|
|
|
return map { $Details{$_} } (qw(Second Minute Hour Day Month Year));
|
|
};
|
|
|
|
my ( $s, $m, $h, $D, $M, $Y ) = $GetCurSysDTUnitFromLowest->(
|
|
AddSeconds => $DiffTime,
|
|
);
|
|
my ( $Cs, $Cm, $Ch, $CD, $CM, $CY ) = $GetCurSysDTUnitFromLowest->();
|
|
|
|
# time zone translation
|
|
if (
|
|
$Self->{UserTimeZone}
|
|
&& $Param{ $Prefix . 'Year' }
|
|
&& $Param{ $Prefix . 'Month' }
|
|
&& $Param{ $Prefix . 'Day' }
|
|
&& !$Param{OverrideTimeZone}
|
|
)
|
|
{
|
|
my $DateTimeObject = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
Year => $Param{ $Prefix . 'Year' },
|
|
Month => $Param{ $Prefix . 'Month' },
|
|
Day => $Param{ $Prefix . 'Day' },
|
|
Hour => $Param{ $Prefix . 'Hour' } || 0,
|
|
Minute => $Param{ $Prefix . 'Minute' } || 0,
|
|
Second => $Param{ $Prefix . 'Second' } || 0,
|
|
},
|
|
);
|
|
|
|
if ($DateTimeObject) {
|
|
$DateTimeObject->ToTimeZone( TimeZone => $Self->{UserTimeZone} );
|
|
my $DateTimeValues = $DateTimeObject->Get();
|
|
|
|
$Param{ $Prefix . 'Year' } = $DateTimeValues->{Year};
|
|
$Param{ $Prefix . 'Month' } = $DateTimeValues->{Month};
|
|
$Param{ $Prefix . 'Day' } = $DateTimeValues->{Day};
|
|
$Param{ $Prefix . 'Hour' } = $DateTimeValues->{Hour};
|
|
$Param{ $Prefix . 'Minute' } = $DateTimeValues->{Minute};
|
|
$Param{ $Prefix . 'Second' } = $DateTimeValues->{Second};
|
|
}
|
|
}
|
|
|
|
# year
|
|
if ( $DateInputStyle eq 'Option' ) {
|
|
my %Year;
|
|
if ( defined $Param{YearPeriodPast} && defined $Param{YearPeriodFuture} ) {
|
|
for ( $Y - $Param{YearPeriodPast} .. $Y + $Param{YearPeriodFuture} ) {
|
|
$Year{$_} = $_;
|
|
}
|
|
}
|
|
else {
|
|
for ( 2001 .. $Y + 1 + ( $Param{YearDiff} || 0 ) ) {
|
|
$Year{$_} = $_;
|
|
}
|
|
}
|
|
|
|
# Check if the DiffTime is in a future year. In this case, we add the missing years between
|
|
# $CY (current year) and $Y (year) to allow the user to manually set back the year if needed.
|
|
if ( $Y > $CY ) {
|
|
for ( $CY .. $Y ) {
|
|
$Year{$_} = $_;
|
|
}
|
|
}
|
|
|
|
$Param{Year} = $Self->BuildSelection(
|
|
Name => $Prefix . 'Year',
|
|
Data => \%Year,
|
|
SelectedID => int( $Param{ $Prefix . 'Year' } || $Y ),
|
|
Translation => 0,
|
|
Class => $Validate ? "Validate_DateYear $Class" : $Class,
|
|
Title => $Self->{LanguageObject}->Translate('Year'),
|
|
Disabled => $Param{Disabled},
|
|
);
|
|
}
|
|
else {
|
|
$Param{Year} = "<input type=\"text\" "
|
|
. ( $Validate ? "class=\"Validate_DateYear $Class\" " : "class=\"$Class\" " )
|
|
. "name=\"${Prefix}Year\" id=\"${Prefix}Year\" size=\"4\" maxlength=\"4\" "
|
|
. "title=\""
|
|
. $Self->{LanguageObject}->Translate('Year')
|
|
. "\" value=\""
|
|
. sprintf( "%02d", ( $Param{ $Prefix . 'Year' } || $Y ) ) . "\" "
|
|
. ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
|
|
}
|
|
|
|
# month
|
|
if ( $DateInputStyle eq 'Option' ) {
|
|
my %Month = map { $_ => sprintf( "%02d", $_ ); } ( 1 .. 12 );
|
|
$Param{Month} = $Self->BuildSelection(
|
|
Name => $Prefix . 'Month',
|
|
Data => \%Month,
|
|
SelectedID => int( $Param{ $Prefix . 'Month' } || $M ),
|
|
Translation => 0,
|
|
Class => $Validate ? "Validate_DateMonth $Class" : $Class,
|
|
Title => $Self->{LanguageObject}->Translate('Month'),
|
|
Disabled => $Param{Disabled},
|
|
);
|
|
}
|
|
else {
|
|
$Param{Month} = "<input type=\"text\" "
|
|
. ( $Validate ? "class=\"Validate_DateMonth $Class\" " : "class=\"$Class\" " )
|
|
. "name=\"${Prefix}Month\" id=\"${Prefix}Month\" size=\"2\" maxlength=\"2\" "
|
|
. "title=\""
|
|
. $Self->{LanguageObject}->Translate('Month')
|
|
. "\" value=\""
|
|
. sprintf( "%02d", ( $Param{ $Prefix . 'Month' } || $M ) ) . "\" "
|
|
. ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
|
|
}
|
|
|
|
my $DateValidateClasses = '';
|
|
if ($Validate) {
|
|
$DateValidateClasses
|
|
.= "Validate_DateDay Validate_DateYear_${Prefix}Year Validate_DateMonth_${Prefix}Month";
|
|
|
|
if ( $Format eq 'DateInputFormatLong' ) {
|
|
$DateValidateClasses
|
|
.= " Validate_DateHour_${Prefix}Hour Validate_DateMinute_${Prefix}Minute";
|
|
}
|
|
|
|
if ($ValidateDateInFuture) {
|
|
$DateValidateClasses .= " Validate_DateInFuture";
|
|
}
|
|
if ($ValidateDateNotInFuture) {
|
|
$DateValidateClasses .= " Validate_DateNotInFuture";
|
|
}
|
|
if ( $ValidateDateAfterPrefix || $ValidateDateAfterValue ) {
|
|
$DateValidateClasses .= ' Validate_DateAfter';
|
|
}
|
|
if ( $ValidateDateBeforePrefix || $ValidateDateBeforeValue ) {
|
|
$DateValidateClasses .= ' Validate_DateBefore';
|
|
}
|
|
if ($ValidateDateAfterPrefix) {
|
|
$DateValidateClasses .= " Validate_DateAfter_$ValidateDateAfterPrefix";
|
|
}
|
|
if ($ValidateDateBeforePrefix) {
|
|
$DateValidateClasses .= " Validate_DateBefore_$ValidateDateBeforePrefix";
|
|
}
|
|
}
|
|
|
|
# day
|
|
if ( $DateInputStyle eq 'Option' ) {
|
|
my %Day = map { $_ => sprintf( "%02d", $_ ); } ( 1 .. 31 );
|
|
$Param{Day} = $Self->BuildSelection(
|
|
Name => $Prefix . 'Day',
|
|
Data => \%Day,
|
|
SelectedID => int( $Param{ $Prefix . 'Day' } || $D ),
|
|
Translation => 0,
|
|
Class => "$DateValidateClasses $Class",
|
|
Title => $Self->{LanguageObject}->Translate('Day'),
|
|
Disabled => $Param{Disabled},
|
|
ValidateDateAfter => $ValidateDateAfterValue,
|
|
ValidateDateBefore => $ValidateDateBeforeValue,
|
|
);
|
|
}
|
|
else {
|
|
$Param{Day} = "<input type=\"text\" "
|
|
. "class=\"$DateValidateClasses $Class\" "
|
|
. "name=\"${Prefix}Day\" id=\"${Prefix}Day\" size=\"2\" maxlength=\"2\" "
|
|
. "title=\""
|
|
. $Self->{LanguageObject}->Translate('Day')
|
|
. "\" value=\""
|
|
. sprintf( "%02d", ( $Param{ $Prefix . 'Day' } || $D ) ) . "\" "
|
|
. ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
|
|
|
|
}
|
|
if ( $Format eq 'DateInputFormatLong' ) {
|
|
|
|
# hour
|
|
if ( $DateInputStyle eq 'Option' ) {
|
|
my %Hour = map { $_ => sprintf( "%02d", $_ ); } ( 0 .. 23 );
|
|
$Param{Hour} = $Self->BuildSelection(
|
|
Name => $Prefix . 'Hour',
|
|
Data => \%Hour,
|
|
SelectedID => defined( $Param{ $Prefix . 'Hour' } )
|
|
? int( $Param{ $Prefix . 'Hour' } )
|
|
: int($h),
|
|
Translation => 0,
|
|
Class => $Validate ? ( 'Validate_DateHour ' . $Class ) : $Class,
|
|
Title => $Self->{LanguageObject}->Translate('Hours'),
|
|
Disabled => $Param{Disabled},
|
|
);
|
|
}
|
|
else {
|
|
$Param{Hour} = "<input type=\"text\" "
|
|
. ( $Validate ? "class=\"Validate_DateHour $Class\" " : "class=\"$Class\" " )
|
|
. "name=\"${Prefix}Hour\" id=\"${Prefix}Hour\" size=\"2\" maxlength=\"2\" "
|
|
. "title=\""
|
|
. $Self->{LanguageObject}->Translate('Hours')
|
|
. "\" value=\""
|
|
. sprintf(
|
|
"%02d",
|
|
( defined( $Param{ $Prefix . 'Hour' } ) ? int( $Param{ $Prefix . 'Hour' } ) : $h )
|
|
)
|
|
. "\" "
|
|
. ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
|
|
|
|
}
|
|
|
|
# minute
|
|
if ( $DateInputStyle eq 'Option' ) {
|
|
my %Minute
|
|
= map { $_ => sprintf( "%02d", $_ ); } map { $_ * $MinuteStep } ( 0 .. ( 60 / $MinuteStep - 1 ) );
|
|
$Param{Minute} = $Self->BuildSelection(
|
|
Name => $Prefix . 'Minute',
|
|
Data => \%Minute,
|
|
SelectedID => defined( $Param{ $Prefix . 'Minute' } )
|
|
? int( $Param{ $Prefix . 'Minute' } )
|
|
: int( $m - $m % $MinuteStep ),
|
|
Translation => 0,
|
|
Class => $Validate ? ( 'Validate_DateMinute ' . $Class ) : $Class,
|
|
Title => $Self->{LanguageObject}->Translate('Minutes'),
|
|
Disabled => $Param{Disabled},
|
|
);
|
|
}
|
|
else {
|
|
$Param{Minute} = "<input type=\"text\" "
|
|
. ( $Validate ? "class=\"Validate_DateMinute $Class\" " : "class=\"$Class\" " )
|
|
. "name=\"${Prefix}Minute\" id=\"${Prefix}Minute\" size=\"2\" maxlength=\"2\" "
|
|
. "title=\""
|
|
. $Self->{LanguageObject}->Translate('Minutes')
|
|
. "\" value=\""
|
|
. sprintf(
|
|
"%02d",
|
|
(
|
|
defined( $Param{ $Prefix . 'Minute' } )
|
|
? int( $Param{ $Prefix . 'Minute' } )
|
|
: $m
|
|
)
|
|
) . "\" "
|
|
. ( $Param{Disabled} ? 'readonly="readonly"' : '' ) . "/>";
|
|
}
|
|
}
|
|
|
|
# Get first day of the week
|
|
my $WeekDayStart = $ConfigObject->Get('CalendarWeekDayStart');
|
|
if ( $Param{Calendar} ) {
|
|
if ( $ConfigObject->Get( "TimeZone::Calendar" . $Param{Calendar} . "Name" ) ) {
|
|
$WeekDayStart = $ConfigObject->Get( "CalendarWeekDayStart::Calendar" . $Param{Calendar} );
|
|
}
|
|
}
|
|
if ( !defined $WeekDayStart ) {
|
|
$WeekDayStart = 1;
|
|
}
|
|
|
|
my $Output;
|
|
|
|
# optional checkbox
|
|
if ($Optional) {
|
|
my $Checked = '';
|
|
if ($Used) {
|
|
$Checked = ' checked="checked"';
|
|
}
|
|
$Output .= "<input type=\"checkbox\" name=\""
|
|
. $Prefix
|
|
. "Used\" id=\"" . $Prefix . "Used\" value=\"1\""
|
|
. $Checked
|
|
. " class=\"$Class\""
|
|
. " title=\""
|
|
. $Self->{LanguageObject}->Translate('Check to activate this date')
|
|
. "\" "
|
|
. ( $Param{Disabled} ? 'disabled="disabled"' : '' )
|
|
. "/> ";
|
|
}
|
|
|
|
# remove 'Second' because it is never used and bug #9441
|
|
delete $Param{ $Prefix . 'Second' };
|
|
|
|
# date format
|
|
$Output .= $Self->{LanguageObject}->Time(
|
|
Action => 'Return',
|
|
Format => 'DateInputFormat',
|
|
Mode => 'NotNumeric',
|
|
%Param,
|
|
);
|
|
|
|
# prepare datepicker for specific calendar
|
|
my $VacationDays = '';
|
|
if ( $Param{Calendar} ) {
|
|
$VacationDays = $Self->DatepickerGetVacationDays(
|
|
Calendar => $Param{Calendar},
|
|
);
|
|
}
|
|
my $VacationDaysJSON = $Self->JSONEncode(
|
|
Data => $VacationDays,
|
|
);
|
|
|
|
# Add Datepicker JS to output.
|
|
my $DatepickerJS = '
|
|
Core.UI.Datepicker.Init({
|
|
Day: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Day"),
|
|
Month: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Month"),
|
|
Year: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Year"),
|
|
Hour: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Hour"),
|
|
Minute: $("#" + Core.App.EscapeSelector("' . $Prefix . '") + "Minute"),
|
|
VacationDays: ' . $VacationDaysJSON . ',
|
|
DateInFuture: ' . ( $ValidateDateInFuture ? 'true' : 'false' ) . ',
|
|
DateNotInFuture: ' . ( $ValidateDateNotInFuture ? 'true' : 'false' ) . ',
|
|
WeekDayStart: ' . $WeekDayStart . '
|
|
});';
|
|
|
|
$Self->AddJSOnDocumentComplete( Code => $DatepickerJS );
|
|
$Self->{HasDatepicker} = 1; # Call some Datepicker init code.
|
|
|
|
return $Output;
|
|
}
|
|
|
|
=head2 HumanReadableDataSize()
|
|
|
|
Produces human readable data size.
|
|
|
|
my $SizeStr = $LayoutObject->HumanReadableDataSize(
|
|
Size => 123, # size in bytes
|
|
);
|
|
|
|
Returns
|
|
|
|
$SizeStr = '123 B'; # example with decimal point: 123.4 MB
|
|
|
|
=cut
|
|
|
|
sub HumanReadableDataSize {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# Use simple string concatenation to format real number. "sprintf" uses dot (.) as decimal separator unless
|
|
# locale and POSIX (LC_NUMERIC) is used. Even in this case, you are not allowed to use custom separator
|
|
# (as defined in language files).
|
|
|
|
my $FormatSize = sub {
|
|
my ($Number) = @_;
|
|
|
|
my $ReadableSize;
|
|
|
|
if ( IsInteger($Number) ) {
|
|
$ReadableSize = $Number;
|
|
}
|
|
else {
|
|
|
|
# Get integer and decimal parts.
|
|
my ( $Integer, $Float ) = split( m{\.}, sprintf( "%.1f", $Number ) );
|
|
|
|
my $Separator = $Self->{LanguageObject}->{DecimalSeparator} || '.';
|
|
|
|
# Format size with provided decimal separator.
|
|
$ReadableSize = $Integer . $Separator . $Float;
|
|
}
|
|
|
|
return $ReadableSize;
|
|
};
|
|
|
|
if ( !defined( $Param{Size} ) ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Size!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
if ( !IsInteger( $Param{Size} ) ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Size must be integer!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# Use convention described on https://en.wikipedia.org/wiki/File_size
|
|
my ( $SizeStr, $ReadableSize );
|
|
|
|
if ( $Param{Size} >= ( 1024**4 ) ) {
|
|
|
|
$ReadableSize = $FormatSize->( $Param{Size} / ( 1024**4 ) );
|
|
$SizeStr = $Self->{LanguageObject}->Translate( '%s TB', $ReadableSize );
|
|
}
|
|
elsif ( $Param{Size} >= ( 1024**3 ) ) {
|
|
|
|
$ReadableSize = $FormatSize->( $Param{Size} / ( 1024**3 ) );
|
|
$SizeStr = $Self->{LanguageObject}->Translate( '%s GB', $ReadableSize );
|
|
}
|
|
elsif ( $Param{Size} >= ( 1024**2 ) ) {
|
|
|
|
$ReadableSize = $FormatSize->( $Param{Size} / ( 1024**2 ) );
|
|
$SizeStr = $Self->{LanguageObject}->Translate( '%s MB', $ReadableSize );
|
|
}
|
|
elsif ( $Param{Size} >= 1024 ) {
|
|
|
|
$ReadableSize = $FormatSize->( $Param{Size} / 1024 );
|
|
$SizeStr = $Self->{LanguageObject}->Translate( '%s KB', $ReadableSize );
|
|
}
|
|
else {
|
|
$SizeStr = $Self->{LanguageObject}->Translate( '%s B', $Param{Size} );
|
|
}
|
|
|
|
return $SizeStr;
|
|
}
|
|
|
|
sub CustomerLogin {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Output = '';
|
|
$Param{TitleArea} = $Self->{LanguageObject}->Translate('Login') . ' - ';
|
|
|
|
# set Action parameter for the loader
|
|
$Self->{Action} = 'CustomerLogin';
|
|
$Param{IsLoginPage} = 1;
|
|
$Param{'XLoginHeader'} = 1;
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
if ( $ConfigObject->Get('SessionUseCookie') ) {
|
|
|
|
# always set a cookie, so that at the time the user submits
|
|
# the password, we know already if the browser supports cookies.
|
|
# ( the session cookie isn't available at that time ).
|
|
my $CookieSecureAttribute = 0;
|
|
if ( $ConfigObject->Get('HttpType') eq 'https' ) {
|
|
|
|
# Restrict Cookie to HTTPS if it is used.
|
|
$CookieSecureAttribute = 1;
|
|
}
|
|
$Self->{SetCookies}->{OTRSBrowserHasCookie} = $Kernel::OM->Get('Kernel::System::Web::Request')->SetCookie(
|
|
Key => 'OTRSBrowserHasCookie',
|
|
Value => 1,
|
|
Expires => '+1y',
|
|
Path => $ConfigObject->Get('ScriptAlias'),
|
|
Secure => $CookieSecureAttribute,
|
|
HttpOnly => 1,
|
|
);
|
|
}
|
|
|
|
# add cookies if exists
|
|
if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
|
|
for ( sort keys %{ $Self->{SetCookies} } ) {
|
|
$Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
|
|
}
|
|
}
|
|
|
|
# check if message should be shown
|
|
if ( $Param{Message} ) {
|
|
$Self->Block(
|
|
Name => 'Message',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# Generate the minified CSS and JavaScript files and the tags referencing them (see LayoutLoader)
|
|
$Self->LoaderCreateCustomerCSSCalls();
|
|
$Self->LoaderCreateCustomerJSCalls();
|
|
$Self->LoaderCreateJavaScriptTranslationData();
|
|
$Self->LoaderCreateJavaScriptTemplateData();
|
|
|
|
my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
|
|
$Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
|
|
$Param{OTRSSTORMIsInstalled} = $OTRSBusinessObject->OTRSSTORMIsInstalled();
|
|
$Param{OTRSCONTROLIsInstalled} = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
|
|
|
|
$Self->AddJSData(
|
|
Key => 'Baselink',
|
|
Value => $Self->{Baselink},
|
|
);
|
|
|
|
# Add header logo, if configured
|
|
if ( defined $ConfigObject->Get('CustomerLogo') ) {
|
|
my %CustomerLogo = %{ $ConfigObject->Get('CustomerLogo') };
|
|
my %Data;
|
|
|
|
for my $CSSStatement ( sort keys %CustomerLogo ) {
|
|
if ( $CSSStatement eq 'URL' ) {
|
|
my $WebPath = '';
|
|
if ( $CustomerLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
|
|
$WebPath = $ConfigObject->Get('Frontend::WebPath');
|
|
}
|
|
$Data{'URL'} = 'url(' . $WebPath . $CustomerLogo{$CSSStatement} . ')';
|
|
}
|
|
else {
|
|
$Data{$CSSStatement} = $CustomerLogo{$CSSStatement};
|
|
}
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => 'HeaderLogoCSS',
|
|
Data => \%Data,
|
|
);
|
|
|
|
$Self->Block(
|
|
Name => 'HeaderLogo',
|
|
);
|
|
}
|
|
|
|
# get system maintenance object
|
|
my $SystemMaintenanceObject = $Kernel::OM->Get('Kernel::System::SystemMaintenance');
|
|
|
|
my $ActiveMaintenance = $SystemMaintenanceObject->SystemMaintenanceIsActive();
|
|
|
|
# check if system maintenance is active
|
|
if ($ActiveMaintenance) {
|
|
my $SystemMaintenanceData = $SystemMaintenanceObject->SystemMaintenanceGet(
|
|
ID => $ActiveMaintenance,
|
|
UserID => 1,
|
|
);
|
|
if ( $SystemMaintenanceData->{ShowLoginMessage} ) {
|
|
my $LoginMessage =
|
|
$SystemMaintenanceData->{LoginMessage}
|
|
|| $ConfigObject->Get('SystemMaintenance::IsActiveDefaultLoginMessage')
|
|
|| "System maintenance is active, not possible to perform a login!";
|
|
|
|
$Self->Block(
|
|
Name => 'SystemMaintenance',
|
|
Data => {
|
|
LoginMessage => $LoginMessage,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# show prelogin block, if in prelogin mode (e.g. SSO login)
|
|
if ( defined $Param{'Mode'} && $Param{'Mode'} eq 'PreLogin' ) {
|
|
$Self->Block(
|
|
Name => 'PreLogin',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# if not in PreLogin mode, show normal login form
|
|
else {
|
|
|
|
$Self->Block(
|
|
Name => 'LoginBox',
|
|
Data => \%Param,
|
|
);
|
|
|
|
# show 2 factor password input if we have at least one backend enabled
|
|
COUNT:
|
|
for my $Count ( '', 1 .. 10 ) {
|
|
next COUNT if !$ConfigObject->Get("Customer::AuthTwoFactorModule$Count");
|
|
|
|
$Self->Block(
|
|
Name => 'AuthTwoFactor',
|
|
Data => \%Param,
|
|
);
|
|
last COUNT;
|
|
}
|
|
|
|
# get lost password output
|
|
if (
|
|
$ConfigObject->Get('CustomerPanelLostPassword')
|
|
&& $ConfigObject->Get('Customer::AuthModule') eq
|
|
'Kernel::System::CustomerAuth::DB'
|
|
)
|
|
{
|
|
$Self->Block(
|
|
Name => 'LostPasswordLink',
|
|
Data => \%Param,
|
|
);
|
|
$Self->Block(
|
|
Name => 'LostPassword',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# get create account output
|
|
if (
|
|
$ConfigObject->Get('CustomerPanelCreateAccount')
|
|
&& $ConfigObject->Get('Customer::AuthModule') eq
|
|
'Kernel::System::CustomerAuth::DB'
|
|
)
|
|
|
|
{
|
|
$Self->Block(
|
|
Name => 'CreateAccountLink',
|
|
Data => \%Param,
|
|
);
|
|
$Self->Block(
|
|
Name => 'CreateAccount',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
}
|
|
|
|
# send data to JS
|
|
$Self->AddJSData(
|
|
Key => 'LoginFailed',
|
|
Value => $Param{LoginFailed},
|
|
);
|
|
|
|
# Display footer links.
|
|
my $FooterLinks = $ConfigObject->Get('PublicFrontend::FooterLinks');
|
|
if ( IsHashRefWithData($FooterLinks) ) {
|
|
|
|
my @FooterLinks;
|
|
|
|
for my $Link ( sort keys %{$FooterLinks} ) {
|
|
|
|
push @FooterLinks, {
|
|
Description => $FooterLinks->{$Link},
|
|
Target => $Link,
|
|
};
|
|
}
|
|
|
|
$Param{FooterLinks} = \@FooterLinks;
|
|
}
|
|
|
|
# create & return output
|
|
$Output .= $Self->Output(
|
|
TemplateFile => 'CustomerLogin',
|
|
Data => \%Param,
|
|
);
|
|
|
|
# remove the version tag from the header if configured
|
|
$Self->_DisableBannerCheck( OutputRef => \$Output );
|
|
|
|
return $Output;
|
|
}
|
|
|
|
sub CustomerHeader {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Type = $Param{Type} || '';
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# add cookies if exists
|
|
my $Output = '';
|
|
if ( $Self->{SetCookies} && $ConfigObject->Get('SessionUseCookie') ) {
|
|
for ( sort keys %{ $Self->{SetCookies} } ) {
|
|
$Output .= "Set-Cookie: $Self->{SetCookies}->{$_}\n";
|
|
}
|
|
}
|
|
|
|
my $File = $Param{Filename} || $Self->{Action} || 'unknown';
|
|
|
|
# set file name for "save page as"
|
|
$Param{ContentDisposition} = "filename=\"$File.html\"";
|
|
|
|
# area and title
|
|
if (
|
|
!$Param{Area}
|
|
&& $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }
|
|
)
|
|
{
|
|
$Param{Area} = $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }
|
|
->{NavBarName} || '';
|
|
}
|
|
if (
|
|
!$Param{Title}
|
|
&& $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }
|
|
)
|
|
{
|
|
$Param{Title} = $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} }->{Title}
|
|
|| '';
|
|
}
|
|
if (
|
|
!$Param{Area}
|
|
&& $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }
|
|
)
|
|
{
|
|
$Param{Area} = $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }
|
|
->{NavBarName} || '';
|
|
}
|
|
if (
|
|
!$Param{Title}
|
|
&& $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }
|
|
)
|
|
{
|
|
$Param{Title} = $ConfigObject->Get('PublicFrontend::Module')->{ $Self->{Action} }->{Title}
|
|
|| '';
|
|
}
|
|
for my $Word (qw(Value Title Area)) {
|
|
if ( $Param{$Word} ) {
|
|
$Param{TitleArea} .= $Self->{LanguageObject}->Translate( $Param{$Word} ) . ' - ';
|
|
}
|
|
}
|
|
|
|
my $Frontend;
|
|
if ( $ConfigObject->Get('CustomerFrontend::Module')->{ $Self->{Action} } ) {
|
|
$Frontend = 'Customer';
|
|
}
|
|
else {
|
|
$Frontend = 'Public';
|
|
}
|
|
|
|
# run header meta modules for customer and public frontends
|
|
my $HeaderMetaModule = $ConfigObject->Get( $Frontend . 'Frontend::HeaderMetaModule' );
|
|
if ( ref $HeaderMetaModule eq 'HASH' ) {
|
|
my %Jobs = %{$HeaderMetaModule};
|
|
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
MODULE:
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load and run module
|
|
next MODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
|
|
my $Object = $Jobs{$Job}->{Module}->new( %{$Self}, LayoutObject => $Self );
|
|
next MODULE if !$Object;
|
|
$Object->Run( %Param, Config => $Jobs{$Job} );
|
|
}
|
|
}
|
|
|
|
# set rtl if needed
|
|
if ( $Self->{TextDirection} && $Self->{TextDirection} eq 'rtl' ) {
|
|
$Param{BodyClass} = 'RTL';
|
|
}
|
|
elsif ( $ConfigObject->Get('Frontend::DebugMode') ) {
|
|
$Self->Block(
|
|
Name => 'DebugRTLButton',
|
|
);
|
|
}
|
|
|
|
# Add header logo, if configured
|
|
if ( defined $ConfigObject->Get('CustomerLogo') ) {
|
|
my %CustomerLogo = %{ $ConfigObject->Get('CustomerLogo') };
|
|
my %Data;
|
|
|
|
for my $CSSStatement ( sort keys %CustomerLogo ) {
|
|
if ( $CSSStatement eq 'URL' ) {
|
|
my $WebPath = '';
|
|
if ( $CustomerLogo{$CSSStatement} !~ /(http|ftp|https):\//i ) {
|
|
$WebPath = $ConfigObject->Get('Frontend::WebPath');
|
|
}
|
|
$Data{'URL'} = 'url(' . $WebPath . $CustomerLogo{$CSSStatement} . ')';
|
|
}
|
|
else {
|
|
$Data{$CSSStatement} = $CustomerLogo{$CSSStatement};
|
|
}
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => 'HeaderLogoCSS',
|
|
Data => \%Data,
|
|
);
|
|
|
|
$Self->Block(
|
|
Name => 'HeaderLogo',
|
|
);
|
|
}
|
|
|
|
# Generate the minified CSS and JavaScript files
|
|
# and the tags referencing them (see LayoutLoader)
|
|
$Self->LoaderCreateCustomerCSSCalls();
|
|
|
|
# create & return output
|
|
$Output .= $Self->Output(
|
|
TemplateFile => "CustomerHeader$Type",
|
|
Data => \%Param,
|
|
);
|
|
|
|
# remove the version tag from the header if configured
|
|
$Self->_DisableBannerCheck( OutputRef => \$Output );
|
|
|
|
return $Output;
|
|
}
|
|
|
|
sub CustomerFooter {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Type = $Param{Type} || '';
|
|
my $HasDatepicker = $Self->{HasDatepicker} || 0;
|
|
|
|
# Generate the minified CSS and JavaScript files
|
|
# and the tags referencing them (see LayoutLoader)
|
|
$Self->LoaderCreateCustomerJSCalls();
|
|
$Self->LoaderCreateJavaScriptTranslationData();
|
|
$Self->LoaderCreateJavaScriptTemplateData();
|
|
|
|
# get datepicker data, if needed in module
|
|
if ($HasDatepicker) {
|
|
my $VacationDays = $Self->DatepickerGetVacationDays();
|
|
my $TextDirection = $Self->{LanguageObject}->{TextDirection} || '';
|
|
|
|
# send data to JS
|
|
$Self->AddJSData(
|
|
Key => 'Datepicker',
|
|
Value => {
|
|
VacationDays => $VacationDays,
|
|
IsRTL => ( $TextDirection eq 'rtl' ) ? 1 : 0,
|
|
},
|
|
);
|
|
}
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# Check if video chat is enabled.
|
|
if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::VideoChat', Silent => 1 ) ) {
|
|
$Param{VideoChatEnabled} = $Kernel::OM->Get('Kernel::System::VideoChat')->IsEnabled()
|
|
|| $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam( Param => 'UnitTestMode' ) // 0;
|
|
}
|
|
|
|
# Check if customer user has permission for chat.
|
|
my $CustomerChatPermission;
|
|
if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Chat', Silent => 1 ) ) {
|
|
|
|
my $CustomerChatConfig = $ConfigObject->Get('CustomerFrontend::Module')->{'CustomerChat'} || {};
|
|
|
|
if (
|
|
$Kernel::OM->Get('Kernel::Config')->Get('CustomerGroupSupport')
|
|
&& (
|
|
IsArrayRefWithData( $CustomerChatConfig->{GroupRo} )
|
|
|| IsArrayRefWithData( $CustomerChatConfig->{Group} )
|
|
)
|
|
)
|
|
{
|
|
|
|
my $CustomerGroupObject = $Kernel::OM->Get('Kernel::System::CustomerGroup');
|
|
|
|
GROUP:
|
|
for my $GroupName ( @{ $CustomerChatConfig->{GroupRo} }, @{ $CustomerChatConfig->{Group} } ) {
|
|
$CustomerChatPermission = $CustomerGroupObject->PermissionCheck(
|
|
UserID => $Self->{UserID},
|
|
GroupName => $GroupName,
|
|
Type => 'ro',
|
|
);
|
|
last GROUP if $CustomerChatPermission;
|
|
}
|
|
}
|
|
else {
|
|
$CustomerChatPermission = 1;
|
|
}
|
|
}
|
|
|
|
# don't check for business package if the database was not yet configured (in the installer)
|
|
if ( $ConfigObject->Get('SecureMode') ) {
|
|
my $OTRSBusinessObject = $Kernel::OM->Get('Kernel::System::OTRSBusiness');
|
|
$Param{OTRSBusinessIsInstalled} = $OTRSBusinessObject->OTRSBusinessIsInstalled();
|
|
$Param{OTRSSTORMIsInstalled} = $OTRSBusinessObject->OTRSSTORMIsInstalled();
|
|
$Param{OTRSCONTROLIsInstalled} = $OTRSBusinessObject->OTRSCONTROLIsInstalled();
|
|
}
|
|
|
|
# AutoComplete-Config
|
|
my $AutocompleteConfig = $ConfigObject->Get('AutoComplete::Customer');
|
|
|
|
for my $ConfigElement ( sort keys %{$AutocompleteConfig} ) {
|
|
$AutocompleteConfig->{$ConfigElement}->{ButtonText}
|
|
= $Self->{LanguageObject}->Translate( $AutocompleteConfig->{$ConfigElement}{ButtonText} );
|
|
}
|
|
|
|
# add JS data
|
|
my %JSConfig = (
|
|
Baselink => $Self->{Baselink},
|
|
CGIHandle => $Self->{CGIHandle},
|
|
WebPath => $ConfigObject->Get('Frontend::WebPath'),
|
|
Action => $Self->{Action},
|
|
Subaction => $Self->{Subaction},
|
|
SessionIDCookie => $Self->{SessionIDCookie},
|
|
SessionName => $Self->{SessionName},
|
|
SessionID => $Self->{SessionID},
|
|
SessionUseCookie => $ConfigObject->Get('SessionUseCookie'),
|
|
ChallengeToken => $Self->{UserChallengeToken},
|
|
CustomerPanelSessionName => $ConfigObject->Get('CustomerPanelSessionName'),
|
|
UserLanguage => $Self->{UserLanguage},
|
|
CheckEmailAddresses => $ConfigObject->Get('CheckEmailAddresses'),
|
|
OTRSBusinessIsInstalled => $Param{OTRSBusinessIsInstalled},
|
|
OTRSSTORMIsInstalled => $Param{OTRSSTORMIsInstalled},
|
|
OTRSCONTROLIsInstalled => $Param{OTRSCONTROLIsInstalled},
|
|
InputFieldsActivated => $ConfigObject->Get('ModernizeCustomerFormFields'),
|
|
Autocomplete => $AutocompleteConfig,
|
|
VideoChatEnabled => $Param{VideoChatEnabled},
|
|
WebMaxFileUpload => $ConfigObject->Get('WebMaxFileUpload'),
|
|
CustomerChatPermission => $CustomerChatPermission,
|
|
);
|
|
|
|
for my $Config ( sort keys %JSConfig ) {
|
|
$Self->AddJSData(
|
|
Key => $Config,
|
|
Value => $JSConfig{$Config},
|
|
);
|
|
}
|
|
|
|
# Display footer links.
|
|
my $FooterLinks = $ConfigObject->Get('PublicFrontend::FooterLinks');
|
|
if ( IsHashRefWithData($FooterLinks) ) {
|
|
|
|
my @FooterLinks;
|
|
|
|
for my $Link ( sort keys %{$FooterLinks} ) {
|
|
|
|
push @FooterLinks, {
|
|
Description => $FooterLinks->{$Link},
|
|
Target => $Link,
|
|
};
|
|
}
|
|
|
|
$Param{FooterLinks} = \@FooterLinks;
|
|
}
|
|
|
|
# create & return output
|
|
return $Self->Output(
|
|
TemplateFile => "CustomerFooter$Type",
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
sub CustomerFatalError {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# Prevent endless recursion in case of problems with Template engine.
|
|
return if ( $Self->{InFatalError}++ );
|
|
|
|
if ( $Param{Message} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Caller => 1,
|
|
Priority => 'error',
|
|
Message => $Param{Message},
|
|
);
|
|
}
|
|
my $Output = $Self->CustomerHeader(
|
|
Area => 'Frontend',
|
|
Title => 'Fatal Error'
|
|
);
|
|
$Output .= $Self->CustomerError(%Param);
|
|
$Output .= $Self->CustomerFooter();
|
|
$Self->Print( Output => \$Output );
|
|
exit;
|
|
}
|
|
|
|
sub CustomerNavigationBar {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# create menu items
|
|
my %NavBarModule;
|
|
my $FrontendModule = $ConfigObject->Get('CustomerFrontend::Module');
|
|
my $NavigationConfig = $ConfigObject->Get('CustomerFrontend::Navigation');
|
|
|
|
my $GroupObject = $Kernel::OM->Get('Kernel::System::CustomerGroup');
|
|
|
|
MODULE:
|
|
for my $Module ( sort keys %{$NavigationConfig} ) {
|
|
|
|
# Skip if module is disabled in frontend registration.
|
|
next MODULE if !IsHashRefWithData( $FrontendModule->{$Module} );
|
|
|
|
# Top-level frontend navigation configuration should always be a hash.
|
|
next MODULE if !IsHashRefWithData( $NavigationConfig->{$Module} );
|
|
|
|
my @ModuleNavigationConfigs;
|
|
|
|
# Go through all defined navigation configurations for the module and sort them by the key (00#-Module).
|
|
NAVIGATION_CONFIG:
|
|
for my $Key ( sort keys %{ $NavigationConfig->{$Module} || {} } ) {
|
|
next NAVIGATION_CONFIG if $Key !~ m{^\d+};
|
|
|
|
# FIXME: Support both old (HASH) and new (ARRAY of HASH) navigation configurations, for reasons of backwards
|
|
# compatibility. Once we are sure everything has been migrated correctly, support for HASH-only
|
|
# configuration can be dropped in future major release.
|
|
if ( IsHashRefWithData( $NavigationConfig->{$Module}->{$Key} ) ) {
|
|
push @ModuleNavigationConfigs, $NavigationConfig->{$Module}->{$Key};
|
|
}
|
|
elsif ( IsArrayRefWithData( $NavigationConfig->{$Module}->{$Key} ) ) {
|
|
push @ModuleNavigationConfigs, @{ $NavigationConfig->{$Module}->{$Key} };
|
|
}
|
|
|
|
# Skip incompatible configuration.
|
|
else {
|
|
next NAVIGATION_CONFIG;
|
|
}
|
|
}
|
|
|
|
ITEM:
|
|
for my $Item (@ModuleNavigationConfigs) {
|
|
next ITEM if !$Item->{NavBar};
|
|
|
|
my $InheritPermissions = 0;
|
|
|
|
# Inherit permissions from frontend registration if no permissions were defined for the navigation entry.
|
|
if ( !$Item->{GroupRo} && !$Item->{Group} ) {
|
|
if ( $FrontendModule->{GroupRo} ) {
|
|
$Item->{GroupRo} = $FrontendModule->{GroupRo};
|
|
}
|
|
if ( $FrontendModule->{Group} ) {
|
|
$Item->{Group} = $FrontendModule->{Group};
|
|
}
|
|
$InheritPermissions = 1;
|
|
}
|
|
|
|
my $Shown = 0;
|
|
|
|
PERMISSION:
|
|
for my $Permission (qw(GroupRo Group)) {
|
|
|
|
# No access restriction.
|
|
if (
|
|
ref $Item->{GroupRo} eq 'ARRAY'
|
|
&& !scalar @{ $Item->{GroupRo} }
|
|
&& ref $Item->{Group} eq 'ARRAY'
|
|
&& !scalar @{ $Item->{Group} }
|
|
)
|
|
{
|
|
$Shown = 1;
|
|
last PERMISSION;
|
|
}
|
|
|
|
# Array access restriction.
|
|
elsif ( $Item->{$Permission} && ref $Item->{$Permission} eq 'ARRAY' ) {
|
|
for my $Group ( @{ $Item->{$Permission} } ) {
|
|
my $HasPermission = $GroupObject->PermissionCheck(
|
|
UserID => $Self->{UserID},
|
|
GroupName => $Group,
|
|
Type => $Permission eq 'GroupRo' ? 'ro' : 'rw',
|
|
);
|
|
if ($HasPermission) {
|
|
$Shown = 1;
|
|
last PERMISSION;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# If we passed the initial permission check and didn't inherit permissions from the module registration,
|
|
# make sure to also check access to the module, since navigation item might be out of sync.
|
|
if ( $Shown && !$InheritPermissions ) {
|
|
my $ModulePermission;
|
|
|
|
PERMISSION:
|
|
for my $Permission (qw(GroupRo Group)) {
|
|
|
|
# No access restriction.
|
|
if (
|
|
ref $FrontendModule->{$Module}->{GroupRo} eq 'ARRAY'
|
|
&& !scalar @{ $FrontendModule->{$Module}->{GroupRo} }
|
|
&& ref $FrontendModule->{$Module}->{Group} eq 'ARRAY'
|
|
&& !scalar @{ $FrontendModule->{$Module}->{Group} }
|
|
)
|
|
{
|
|
|
|
$ModulePermission = 1;
|
|
last PERMISSION;
|
|
}
|
|
|
|
# Array access restriction.
|
|
elsif (
|
|
$FrontendModule->{$Module}->{$Permission}
|
|
&& ref $FrontendModule->{$Module}->{$Permission} eq 'ARRAY'
|
|
)
|
|
{
|
|
GROUP:
|
|
for my $Group ( @{ $FrontendModule->{$Module}->{$Permission} } ) {
|
|
next GROUP if !$Group;
|
|
my $HasPermission = $GroupObject->PermissionCheck(
|
|
UserID => $Self->{UserID},
|
|
GroupName => $Group,
|
|
Type => $Permission eq 'GroupRo' ? 'ro' : 'rw',
|
|
|
|
);
|
|
if ($HasPermission) {
|
|
$ModulePermission = 1;
|
|
last PERMISSION;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Hide item if no permission was granted to access the module.
|
|
if ( !$ModulePermission ) {
|
|
$Shown = 0;
|
|
}
|
|
}
|
|
|
|
next ITEM if !$Shown;
|
|
|
|
# set prio of item
|
|
my $Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
|
|
COUNT:
|
|
for ( 1 .. 51 ) {
|
|
last COUNT if !$NavBarModule{$Key};
|
|
|
|
$Item->{Prio}++;
|
|
$Key = ( $Item->{Block} || '' ) . sprintf( "%07d", $Item->{Prio} );
|
|
}
|
|
|
|
# Show as main menu.
|
|
if ( $Item->{Type} eq 'Menu' ) {
|
|
$NavBarModule{$Key} = $Item;
|
|
}
|
|
|
|
# show as sub of main menu
|
|
elsif ( $Item->{Type} eq 'Submenu' ) {
|
|
$NavBarModule{Sub}->{ $Item->{NavBar} }->{$Key} = $Item;
|
|
}
|
|
}
|
|
}
|
|
|
|
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
|
|
|
# run menu item modules
|
|
if ( ref $ConfigObject->Get('CustomerFrontend::NavBarModule') eq 'HASH' ) {
|
|
my %Jobs = %{ $ConfigObject->Get('CustomerFrontend::NavBarModule') };
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load module
|
|
if ( !$MainObject->Require( $Jobs{$Job}->{Module} ) ) {
|
|
$Self->FatalError();
|
|
}
|
|
my $Object = $Jobs{$Job}->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
UserID => $Self->{UserID},
|
|
Debug => $Self->{Debug},
|
|
);
|
|
|
|
# run module
|
|
%NavBarModule = (
|
|
%NavBarModule,
|
|
$Object->Run(
|
|
%Param,
|
|
Config => $Jobs{$Job},
|
|
NavBarModule => \%NavBarModule || {},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
my $Total = keys %NavBarModule;
|
|
my $Counter = 0;
|
|
|
|
if ( $NavBarModule{Sub} ) {
|
|
$Total = int($Total) - 1;
|
|
}
|
|
|
|
# Only highlight the first matched navigation entry. If there are several entries
|
|
# with the same Action and Subaction, it cannot be determined which one was used.
|
|
# Therefore we just highlight the first one.
|
|
my $SelectedFlag;
|
|
|
|
ITEM:
|
|
for my $Item ( sort keys %NavBarModule ) {
|
|
next ITEM if !%{ $NavBarModule{$Item} };
|
|
next ITEM if $Item eq 'Sub';
|
|
$Counter++;
|
|
my $Sub;
|
|
if ( $NavBarModule{$Item}->{NavBar} ) {
|
|
$Sub = $NavBarModule{Sub}->{ $NavBarModule{$Item}->{NavBar} };
|
|
}
|
|
|
|
# highlight active link
|
|
$NavBarModule{$Item}->{Class} = '';
|
|
if ( $NavBarModule{$Item}->{Link} ) {
|
|
if (
|
|
!$SelectedFlag
|
|
&& $NavBarModule{$Item}->{Link} =~ /Action=$Self->{Action}/
|
|
&& $NavBarModule{$Item}->{Link} =~ /$Self->{Subaction}/ # Subaction can be empty
|
|
)
|
|
{
|
|
$NavBarModule{$Item}->{Class} .= ' Selected';
|
|
$SelectedFlag = 1;
|
|
}
|
|
}
|
|
if ( $Counter == $Total ) {
|
|
$NavBarModule{$Item}->{Class} .= ' Last';
|
|
}
|
|
$Self->Block(
|
|
Name => $NavBarModule{$Item}->{Block} || 'Item',
|
|
Data => $NavBarModule{$Item},
|
|
);
|
|
|
|
# show sub menu
|
|
next ITEM if !$Sub;
|
|
$Self->Block(
|
|
Name => 'ItemAreaSub',
|
|
Data => $Item,
|
|
);
|
|
for my $Key ( sort keys %{$Sub} ) {
|
|
my $ItemSub = $Sub->{$Key};
|
|
$ItemSub->{NameForID} = $ItemSub->{Name};
|
|
$ItemSub->{NameForID} =~ s/[ &;]//ig;
|
|
$ItemSub->{NameTop} = $NavBarModule{$Item}->{NameForID};
|
|
|
|
# check if we must mark the parent element as selected
|
|
if ( $ItemSub->{Link} ) {
|
|
if (
|
|
$ItemSub->{Link} =~ /Action=$Self->{Action}/
|
|
&& $ItemSub->{Link} =~ /$Self->{Subaction}/ # Subaction can be empty
|
|
)
|
|
{
|
|
$NavBarModule{$Item}->{Class} .= ' Selected';
|
|
$ItemSub->{Class} .= ' SubSelected';
|
|
$SelectedFlag = 1;
|
|
}
|
|
}
|
|
|
|
$Self->Block(
|
|
Name => 'ItemAreaSubItem',
|
|
Data => {
|
|
%$ItemSub,
|
|
AccessKeyReference => $ItemSub->{AccessKey} ? " ($ItemSub->{AccessKey})" : '',
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# run notification modules
|
|
my $FrontendNotifyModuleConfig = $ConfigObject->Get('CustomerFrontend::NotifyModule');
|
|
if ( ref $FrontendNotifyModuleConfig eq 'HASH' ) {
|
|
my %Jobs = %{$FrontendNotifyModuleConfig};
|
|
|
|
NOTIFICATIONMODULE:
|
|
for my $Job ( sort keys %Jobs ) {
|
|
|
|
# load module
|
|
next NOTIFICATIONMODULE if !$MainObject->Require( $Jobs{$Job}->{Module} );
|
|
my $Object = $Jobs{$Job}->{Module}->new(
|
|
%{$Self},
|
|
LayoutObject => $Self,
|
|
);
|
|
next NOTIFICATIONMODULE if !$Object;
|
|
|
|
# run module
|
|
$Param{Notification} .= $Object->Run( %Param, Config => $Jobs{$Job} );
|
|
}
|
|
}
|
|
|
|
# create the customer user login info (usually at the right side of the navigation bar)
|
|
if ( !$Self->{UserLoginIdentifier} ) {
|
|
$Param{UserLoginIdentifier} = $Self->{UserEmail} ne $Self->{UserCustomerID}
|
|
?
|
|
"( $Self->{UserEmail} / $Self->{UserCustomerID} )"
|
|
: $Self->{UserEmail};
|
|
}
|
|
else {
|
|
$Param{UserLoginIdentifier} = $Self->{UserLoginIdentifier};
|
|
}
|
|
|
|
# only on valid session
|
|
if ( $Self->{UserID} ) {
|
|
|
|
# show logout button (if registered)
|
|
if ( $FrontendModule->{Logout} ) {
|
|
$Self->Block(
|
|
Name => 'Logout',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# show preferences button (if registered)
|
|
if ( $FrontendModule->{CustomerPreferences} ) {
|
|
if ( $Self->{Action} eq 'CustomerPreferences' ) {
|
|
$Param{Class} = 'Selected';
|
|
}
|
|
$Self->Block(
|
|
Name => 'Preferences',
|
|
Data => \%Param,
|
|
);
|
|
}
|
|
|
|
# Show open chat requests (if chat engine is active).
|
|
if ( $Kernel::OM->Get('Kernel::System::Main')->Require( 'Kernel::System::Chat', Silent => 1 ) ) {
|
|
if ( $ConfigObject->Get('ChatEngine::Active') ) {
|
|
my $ChatObject = $Kernel::OM->Get('Kernel::System::Chat');
|
|
my $Chats = $ChatObject->ChatList(
|
|
Status => 'request',
|
|
TargetType => 'Customer',
|
|
ChatterID => $Self->{UserID},
|
|
ChatterType => 'Customer',
|
|
ChatterActive => 0,
|
|
);
|
|
|
|
my $Count = scalar $Chats;
|
|
|
|
$Self->Block(
|
|
Name => 'ChatRequests',
|
|
Data => {
|
|
Count => $Count,
|
|
Class => ($Count) ? '' : 'Hidden',
|
|
},
|
|
);
|
|
|
|
$Self->AddJSData(
|
|
Key => 'ChatEngine::Active',
|
|
Value => $ConfigObject->Get('ChatEngine::Active')
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
# create & return output
|
|
return $Self->Output(
|
|
TemplateFile => 'CustomerNavigationBar',
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
sub CustomerError {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# get backend error messages
|
|
for (qw(Message Traceback)) {
|
|
$Param{ 'Backend' . $_ } = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => $_
|
|
) || '';
|
|
}
|
|
if ( !$Param{BackendMessage} && !$Param{BackendTraceback} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => $Param{Message} || '?',
|
|
);
|
|
for (qw(Message Traceback)) {
|
|
$Param{ 'Backend' . $_ } = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => $_
|
|
) || '';
|
|
}
|
|
}
|
|
|
|
if ( !$Param{Message} ) {
|
|
$Param{Message} = $Param{BackendMessage};
|
|
}
|
|
|
|
# create & return output
|
|
return $Self->Output(
|
|
TemplateFile => 'CustomerError',
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
sub CustomerErrorScreen {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Output = $Self->CustomerHeader( Title => 'Error' );
|
|
$Output .= $Self->CustomerError(%Param);
|
|
$Output .= $Self->CustomerFooter();
|
|
return $Output;
|
|
}
|
|
|
|
sub CustomerWarning {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# get backend error messages
|
|
$Param{BackendMessage} = $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Notice',
|
|
What => 'Message',
|
|
)
|
|
|| $Kernel::OM->Get('Kernel::System::Log')->GetLogEntry(
|
|
Type => 'Error',
|
|
What => 'Message',
|
|
) || '';
|
|
|
|
if ( !$Param{Message} ) {
|
|
$Param{Message} = $Param{BackendMessage};
|
|
}
|
|
|
|
# create & return output
|
|
return $Self->Output(
|
|
TemplateFile => 'CustomerWarning',
|
|
Data => \%Param
|
|
);
|
|
}
|
|
|
|
sub CustomerNoPermission {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $WithHeader = $Param{WithHeader} || 'yes';
|
|
$Param{Message} ||= Translatable('No Permission!');
|
|
|
|
# create output
|
|
my $Output;
|
|
$Output = $Self->CustomerHeader( Title => Translatable('No Permission') ) if ( $WithHeader eq 'yes' );
|
|
$Output .= $Self->Output(
|
|
TemplateFile => 'NoPermission',
|
|
Data => \%Param
|
|
);
|
|
$Output .= $Self->CustomerFooter() if ( $WithHeader eq 'yes' );
|
|
|
|
# return output
|
|
return $Output;
|
|
}
|
|
|
|
=head2 Ascii2RichText()
|
|
|
|
converts text to rich text
|
|
|
|
my $HTMLString = $LayoutObject->Ascii2RichText(
|
|
String => $TextString,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub Ascii2RichText {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for (qw(String)) {
|
|
if ( !defined $Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# ascii 2 html
|
|
$Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML(
|
|
String => $Param{String},
|
|
);
|
|
|
|
return $Param{String};
|
|
}
|
|
|
|
=head2 RichText2Ascii()
|
|
|
|
converts text to rich text
|
|
|
|
my $TextString = $LayoutObject->RichText2Ascii(
|
|
String => $HTMLString,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub RichText2Ascii {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for (qw(String)) {
|
|
if ( !defined $Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# ascii 2 html
|
|
$Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToAscii(
|
|
String => $Param{String},
|
|
);
|
|
|
|
return $Param{String};
|
|
}
|
|
|
|
=head2 RichTextDocumentComplete()
|
|
|
|
1) add html, body, ... tags to be a valid html document
|
|
2) replace links of inline content e. g. images to <img src="cid:xxxx" />
|
|
|
|
$HTMLBody = $LayoutObject->RichTextDocumentComplete(
|
|
String => $HTMLBody,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub RichTextDocumentComplete {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for (qw(String)) {
|
|
if ( !defined $Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# replace image link with content id for uploaded images
|
|
my $StringRef = $Self->_RichTextReplaceLinkOfInlineContent(
|
|
String => \$Param{String},
|
|
);
|
|
|
|
# verify html document
|
|
$Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentComplete(
|
|
String => ${$StringRef},
|
|
Charset => $Self->{UserCharset},
|
|
);
|
|
|
|
# do correct direction
|
|
if ( $Self->{TextDirection} ) {
|
|
$Param{String} =~ s/<body/<body dir="$Self->{TextDirection}"/i;
|
|
}
|
|
|
|
# filter links in response
|
|
$Param{String} = $Self->HTMLLinkQuote( String => $Param{String} );
|
|
|
|
return $Param{String};
|
|
}
|
|
|
|
=begin Internal:
|
|
|
|
=cut
|
|
|
|
=head2 _RichTextReplaceLinkOfInlineContent()
|
|
|
|
replace links of inline content e. g. images
|
|
|
|
$HTMLBodyStringRef = $LayoutObject->_RichTextReplaceLinkOfInlineContent(
|
|
String => $HTMLBodyStringRef,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub _RichTextReplaceLinkOfInlineContent {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for (qw(String)) {
|
|
if ( !$Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# replace image link with content id for uploaded images
|
|
${ $Param{String} } =~ s{
|
|
(<img.+?src=("|'))[^"'>]+?ContentID=(.+?)("|')([^>]*>)
|
|
}
|
|
{
|
|
my ($Start, $CID, $Close, $End) = ($1, $3, $4, $5);
|
|
# Make sure we only get the CID and not extra stuff like session information
|
|
$CID =~ s{^([^;&]+).*}{$1}smx;
|
|
$Start . 'cid:' . $CID . $Close . $End;
|
|
}esgxi;
|
|
|
|
return $Param{String};
|
|
}
|
|
|
|
=end Internal:
|
|
|
|
=head2 RichTextDocumentServe()
|
|
|
|
Serve a rich text (HTML) document for local view inside of an C<iframe> in correct charset and with correct links for
|
|
inline documents.
|
|
|
|
By default, all inline/active content (such as C<script>, C<object>, C<applet> or C<embed> tags) will be stripped. If
|
|
there are external images, they will be stripped too, but a message will be shown allowing the user to reload the page
|
|
showing the external images.
|
|
|
|
my %HTMLFile = $LayoutObject->RichTextDocumentServe(
|
|
Data => {
|
|
Content => $HTMLBodyRef,
|
|
ContentType => 'text/html; charset="iso-8859-1"',
|
|
},
|
|
URL => 'AgentTicketAttachment;Subaction=HTMLView;TicketID=123;ArticleID=123;FileID=',
|
|
Attachments => \%AttachmentListOfInlineAttachments,
|
|
|
|
LoadInlineContent => 0, # Serve the document including all inline content. WARNING: This might be dangerous.
|
|
|
|
LoadExternalImages => 0, # Load external images? If this is 0, a message will be included if
|
|
# external images were found and removed.
|
|
);
|
|
|
|
=cut
|
|
|
|
sub RichTextDocumentServe {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for (qw(Data URL Attachments)) {
|
|
if ( !defined $Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# Get charset from passed content type parameter.
|
|
my $Charset;
|
|
if ( $Param{Data}->{ContentType} =~ m/.+?charset=("|'|)(.+)/ig ) {
|
|
$Charset = $2;
|
|
$Charset =~ s/"|'//g;
|
|
}
|
|
if ( !$Charset ) {
|
|
$Charset = 'us-ascii';
|
|
$Param{Data}->{ContentType} .= '; charset="us-ascii"';
|
|
}
|
|
|
|
# Convert to internal charset.
|
|
if ($Charset) {
|
|
$Param{Data}->{Content} = $Kernel::OM->Get('Kernel::System::Encode')->Convert(
|
|
Text => $Param{Data}->{Content},
|
|
From => $Charset,
|
|
To => 'utf-8',
|
|
Check => 1,
|
|
);
|
|
|
|
# Replace charset in content type and content.
|
|
$Param{Data}->{ContentType} =~ s/\Q$Charset\E/utf-8/gi;
|
|
if ( !( $Param{Data}->{Content} =~ s/(<meta[^>]+charset=("|'|))\Q$Charset\E/$1utf-8/gi ) ) {
|
|
|
|
# Add explicit charset if missing.
|
|
$Param{Data}->{Content}
|
|
=~ s/(<meta [^>]+ http-equiv=("|')?Content-Type("|')? [^>]+ content=("|')?[^;"'>]+)/$1; charset=utf-8/ixms;
|
|
}
|
|
}
|
|
|
|
# add html links
|
|
$Param{Data}->{Content} = $Self->HTMLLinkQuote(
|
|
String => $Param{Data}->{Content},
|
|
);
|
|
|
|
# cleanup some html tags to be cross browser compat.
|
|
$Param{Data}->{Content} = $Self->RichTextDocumentCleanup(
|
|
String => $Param{Data}->{Content},
|
|
);
|
|
|
|
# safety check
|
|
if ( !$Param{LoadInlineContent} ) {
|
|
|
|
# Strip out active content first, keeping external images.
|
|
my %SafetyCheckResult = $Kernel::OM->Get('Kernel::System::HTMLUtils')->Safety(
|
|
String => $Param{Data}->{Content},
|
|
NoApplet => 1,
|
|
NoObject => 1,
|
|
NoEmbed => 1,
|
|
NoSVG => 1,
|
|
NoIntSrcLoad => 0,
|
|
NoExtSrcLoad => 0,
|
|
NoJavaScript => 1,
|
|
Debug => $Self->{Debug},
|
|
);
|
|
|
|
$Param{Data}->{Content} = $SafetyCheckResult{String};
|
|
|
|
if ( !$Param{LoadExternalImages} ) {
|
|
|
|
# Strip out external content.
|
|
my %SafetyCheckResult = $Kernel::OM->Get('Kernel::System::HTMLUtils')->Safety(
|
|
String => $Param{Data}->{Content},
|
|
NoApplet => 1,
|
|
NoObject => 1,
|
|
NoEmbed => 1,
|
|
NoSVG => 1,
|
|
NoIntSrcLoad => 0,
|
|
NoExtSrcLoad => 1,
|
|
NoJavaScript => 1,
|
|
Debug => $Self->{Debug},
|
|
);
|
|
|
|
$Param{Data}->{Content} = $SafetyCheckResult{String};
|
|
|
|
# Show confirmation button to load external content explicitly only if BlockLoadingRemoteContent is disabled.
|
|
if (
|
|
$SafetyCheckResult{Replace}
|
|
&& !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::Frontend::BlockLoadingRemoteContent')
|
|
)
|
|
{
|
|
|
|
# Generate blocker message.
|
|
my $Message = $Self->Output( TemplateFile => 'AttachmentBlocker' );
|
|
|
|
# Add it to the beginning of the body, if possible, otherwise prepend it.
|
|
if ( $Param{Data}->{Content} =~ /<body.*?>/si ) {
|
|
$Param{Data}->{Content} =~ s/(<body.*?>)/$1\n$Message/si;
|
|
}
|
|
else {
|
|
$Param{Data}->{Content} = $Message . $Param{Data}->{Content};
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
# build base url for inline images
|
|
my $SessionID = '';
|
|
if ( $Self->{SessionID} && !$Self->{SessionIDCookie} ) {
|
|
$SessionID = ';' . $Self->{SessionName} . '=' . $Self->{SessionID};
|
|
}
|
|
|
|
# replace inline images in content with runtime url to images
|
|
my $AttachmentLink = $Self->{Baselink} . $Param{URL};
|
|
$Param{Data}->{Content} =~ s{
|
|
(=|"|')cid:(.*?)("|'|>|\/>|\s)
|
|
}
|
|
{
|
|
my $Start= $1;
|
|
my $ContentID = $2;
|
|
my $End = $3;
|
|
|
|
# improve html quality
|
|
if ( $Start ne '"' && $Start ne '\'' ) {
|
|
$Start .= '"';
|
|
}
|
|
if ( $End ne '"' && $End ne '\'' ) {
|
|
$End = '"' . $End;
|
|
}
|
|
|
|
# find matching attachment and replace it with runtime url to image
|
|
ATTACHMENT_ID:
|
|
for my $AttachmentID ( sort keys %{ $Param{Attachments} }) {
|
|
next ATTACHMENT_ID if lc $Param{Attachments}->{$AttachmentID}->{ContentID} ne lc "<$ContentID>";
|
|
$ContentID = $AttachmentLink . $AttachmentID . $SessionID;
|
|
last ATTACHMENT_ID;
|
|
}
|
|
|
|
# return new runtime url
|
|
$Start . $ContentID . $End;
|
|
}egxi;
|
|
|
|
# bug #5053
|
|
# inline images using Content-Location as identifier instead of Content-ID even RFC2557
|
|
# http://www.ietf.org/rfc/rfc2557.txt
|
|
|
|
# find matching attachment and replace it with runtlime url to image
|
|
ATTACHMENT:
|
|
for my $AttachmentID ( sort keys %{ $Param{Attachments} } ) {
|
|
next ATTACHMENT if !$Param{Attachments}->{$AttachmentID}->{ContentID};
|
|
|
|
# content id cleanup
|
|
$Param{Attachments}->{$AttachmentID}->{ContentID} =~ s/^<//;
|
|
$Param{Attachments}->{$AttachmentID}->{ContentID} =~ s/>$//;
|
|
|
|
next ATTACHMENT if !$Param{Attachments}->{$AttachmentID}->{ContentID};
|
|
|
|
$Param{Data}->{Content} =~ s{
|
|
(=|"|')(\Q$Param{Attachments}->{$AttachmentID}->{ContentID}\E)("|'|>|\/>|\s)
|
|
}
|
|
{
|
|
my $Start= $1;
|
|
my $ContentID = $2;
|
|
my $End = $3;
|
|
|
|
# improve html quality
|
|
if ( $Start ne '"' && $Start ne '\'' ) {
|
|
$Start .= '"';
|
|
}
|
|
if ( $End ne '"' && $End ne '\'' ) {
|
|
$End = '"' . $End;
|
|
}
|
|
|
|
# return new runtime url
|
|
$ContentID = $AttachmentLink . $AttachmentID . $SessionID;
|
|
$Start . $ContentID . $End;
|
|
}egxi;
|
|
}
|
|
|
|
return %{ $Param{Data} };
|
|
}
|
|
|
|
=head2 RichTextDocumentCleanup()
|
|
|
|
please see L<Kernel::System::HTML::Layout::DocumentCleanup()>
|
|
|
|
=cut
|
|
|
|
sub RichTextDocumentCleanup {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for (qw(String)) {
|
|
if ( !defined $Param{$_} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $_!"
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
$Param{String} = $Kernel::OM->Get('Kernel::System::HTMLUtils')->DocumentCleanup(
|
|
String => $Param{String},
|
|
);
|
|
|
|
return $Param{String};
|
|
}
|
|
|
|
=begin Internal:
|
|
|
|
=cut
|
|
|
|
=head2 _BuildSelectionOptionRefCreate()
|
|
|
|
create the option hash
|
|
|
|
my $OptionRef = $LayoutObject->_BuildSelectionOptionRefCreate(
|
|
%Param,
|
|
);
|
|
|
|
my $OptionRef = {
|
|
Sort => 'numeric',
|
|
PossibleNone => 0,
|
|
Max => 100,
|
|
}
|
|
|
|
=cut
|
|
|
|
sub _BuildSelectionOptionRefCreate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# set SelectedID option
|
|
my $OptionRef = {};
|
|
if ( defined $Param{SelectedID} ) {
|
|
if ( ref $Param{SelectedID} eq 'ARRAY' ) {
|
|
for my $Key ( @{ $Param{SelectedID} } ) {
|
|
$OptionRef->{SelectedID}->{$Key} = 1;
|
|
}
|
|
}
|
|
else {
|
|
$OptionRef->{SelectedID}->{ $Param{SelectedID} } = 1;
|
|
}
|
|
}
|
|
|
|
# set SelectedValue option
|
|
if ( defined $Param{SelectedValue} ) {
|
|
if ( ref $Param{SelectedValue} eq 'ARRAY' ) {
|
|
for my $Value ( @{ $Param{SelectedValue} } ) {
|
|
$OptionRef->{SelectedValue}->{$Value} = 1;
|
|
}
|
|
}
|
|
else {
|
|
$OptionRef->{SelectedValue}->{ $Param{SelectedValue} } = 1;
|
|
}
|
|
}
|
|
|
|
# set Sort option
|
|
$OptionRef->{Sort} = 0;
|
|
if ( $Param{Sort} ) {
|
|
$OptionRef->{Sort} = $Param{Sort};
|
|
}
|
|
|
|
# look if a individual sort is available
|
|
if ( $Param{SortIndividual} && ref $Param{SortIndividual} eq 'ARRAY' ) {
|
|
$OptionRef->{SortIndividual} = $Param{SortIndividual};
|
|
}
|
|
|
|
# set SortReverse option
|
|
$OptionRef->{SortReverse} = 0;
|
|
if ( $Param{SortReverse} ) {
|
|
$OptionRef->{SortReverse} = 1;
|
|
}
|
|
|
|
# set Translation option
|
|
$OptionRef->{Translation} = 1;
|
|
if ( defined $Param{Translation} && $Param{Translation} eq 0 ) {
|
|
$OptionRef->{Translation} = 0;
|
|
}
|
|
|
|
# correcting selected value hash if translation is on
|
|
if (
|
|
$OptionRef->{Translation}
|
|
&& $OptionRef->{SelectedValue}
|
|
&& ref $OptionRef->{SelectedValue} eq 'HASH'
|
|
)
|
|
{
|
|
my %SelectedValueNew;
|
|
for my $OriginalKey ( sort keys %{ $OptionRef->{SelectedValue} } ) {
|
|
my $TranslatedKey = $Self->{LanguageObject}->Translate($OriginalKey);
|
|
$SelectedValueNew{$TranslatedKey} = 1;
|
|
}
|
|
$OptionRef->{SelectedValue} = \%SelectedValueNew;
|
|
}
|
|
|
|
# set PossibleNone option
|
|
$OptionRef->{PossibleNone} = 0;
|
|
if ( $Param{PossibleNone} ) {
|
|
$OptionRef->{PossibleNone} = 1;
|
|
}
|
|
|
|
# set TreeView option
|
|
$OptionRef->{TreeView} = 0;
|
|
if ( $Param{TreeView} ) {
|
|
$OptionRef->{TreeView} = 1;
|
|
$OptionRef->{Sort} = 'TreeView';
|
|
}
|
|
|
|
# set DisabledBranch option
|
|
if ( $Param{DisabledBranch} ) {
|
|
if ( ref $Param{DisabledBranch} eq 'ARRAY' ) {
|
|
for my $Branch ( @{ $Param{DisabledBranch} } ) {
|
|
$OptionRef->{DisabledBranch}->{$Branch} = 1;
|
|
}
|
|
}
|
|
else {
|
|
$OptionRef->{DisabledBranch}->{ $Param{DisabledBranch} } = 1;
|
|
}
|
|
}
|
|
|
|
# set Max option
|
|
$OptionRef->{Max} = $Param{Max} || 100;
|
|
|
|
# set HTMLQuote option
|
|
$OptionRef->{HTMLQuote} = 1;
|
|
if ( defined $Param{HTMLQuote} ) {
|
|
$OptionRef->{HTMLQuote} = $Param{HTMLQuote};
|
|
}
|
|
|
|
return $OptionRef;
|
|
}
|
|
|
|
=head2 _BuildSelectionAttributeRefCreate()
|
|
|
|
create the attribute hash
|
|
|
|
my $AttributeRef = $LayoutObject->_BuildSelectionAttributeRefCreate(
|
|
%Param,
|
|
);
|
|
|
|
my $AttributeRef = {
|
|
name => 'TheName',
|
|
multiple => undef,
|
|
size => 5,
|
|
}
|
|
|
|
=cut
|
|
|
|
sub _BuildSelectionAttributeRefCreate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $AttributeRef = {};
|
|
|
|
# check params with key and value
|
|
for (qw(Name ID Size Class OnChange OnClick AutoComplete)) {
|
|
if ( $Param{$_} ) {
|
|
$AttributeRef->{ lc($_) } = $Param{$_};
|
|
}
|
|
}
|
|
|
|
# add id attriubut
|
|
if ( !$AttributeRef->{id} ) {
|
|
$AttributeRef->{id} = $AttributeRef->{name};
|
|
}
|
|
|
|
# check params with key and value that need to be HTML-Quoted
|
|
for (qw(Title)) {
|
|
if ( $Param{$_} ) {
|
|
$AttributeRef->{ lc($_) } = $Self->Ascii2Html( Text => $Param{$_} );
|
|
}
|
|
}
|
|
|
|
# check HTML params
|
|
for (qw(Multiple Disabled)) {
|
|
if ( $Param{$_} ) {
|
|
$AttributeRef->{ lc($_) } = lc($_);
|
|
}
|
|
}
|
|
|
|
return $AttributeRef;
|
|
}
|
|
|
|
=head2 _BuildSelectionDataRefCreate()
|
|
|
|
create the data hash
|
|
|
|
my $DataRef = $LayoutObject->_BuildSelectionDataRefCreate(
|
|
Data => $ArrayRef, # use $HashRef, $ArrayRef or $ArrayHashRef
|
|
AttributeRef => $AttributeRef,
|
|
OptionRef => $OptionRef,
|
|
);
|
|
|
|
my $DataRef = [
|
|
{
|
|
Key => 11,
|
|
Value => 'Text',
|
|
},
|
|
{
|
|
Key => 'abc',
|
|
Value => ' Text',
|
|
Selected => 1,
|
|
},
|
|
];
|
|
|
|
=cut
|
|
|
|
sub _BuildSelectionDataRefCreate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $AttributeRef = $Param{AttributeRef};
|
|
my $OptionRef = $Param{OptionRef};
|
|
my $DataRef = [];
|
|
|
|
my $Counter = 0;
|
|
|
|
# for HashRef and ArrayRef only
|
|
my %DisabledElements;
|
|
|
|
# dclone $Param{Data} because the subroutine unfortunately modifies
|
|
# the original data ref
|
|
my $DataLocal = $Kernel::OM->Get('Kernel::System::Storable')->Clone( Data => $Param{Data} );
|
|
|
|
# if HashRef was given
|
|
if ( ref $DataLocal eq 'HASH' ) {
|
|
|
|
# get missing parents and mark them for disable later
|
|
if ( $OptionRef->{Sort} eq 'TreeView' ) {
|
|
|
|
# Delete entries in hash with value = undef,
|
|
# because otherwise the reverse statement will cause warnings.
|
|
# Reverse hash, skipping undefined values.
|
|
my %List = map { $DataLocal->{$_} => $_ } grep { defined $DataLocal->{$_} } keys %{$DataLocal};
|
|
|
|
# get each data value
|
|
for my $Key ( sort keys %List ) {
|
|
my $Parents = '';
|
|
|
|
# try to split its parents (e.g. Queue or Service) GrandParent::Parent::Son
|
|
my @Elements = split /::/, $Key;
|
|
|
|
# get each element in the hierarchy
|
|
for my $Element (@Elements) {
|
|
|
|
# add its own parents for the complete name
|
|
my $ElementLongName = $Parents . $Element;
|
|
|
|
# check if element exists in the original data or if it is already marked
|
|
if ( !$List{$ElementLongName} && !$DisabledElements{$ElementLongName} ) {
|
|
|
|
# mark element as disabled
|
|
$DisabledElements{$ElementLongName} = 1;
|
|
|
|
# add the element to the original data to be disabled later
|
|
$DataLocal->{ $ElementLongName . '_Disabled' } = $ElementLongName;
|
|
}
|
|
$Parents .= $Element . '::';
|
|
}
|
|
}
|
|
}
|
|
|
|
# sort hash (before the translation)
|
|
my @SortKeys;
|
|
if ( $OptionRef->{Sort} eq 'IndividualValue' && $OptionRef->{SortIndividual} ) {
|
|
my %List = reverse %{$DataLocal};
|
|
for my $Key ( @{ $OptionRef->{SortIndividual} } ) {
|
|
if ( $List{$Key} ) {
|
|
push @SortKeys, $List{$Key};
|
|
delete $List{$Key};
|
|
}
|
|
}
|
|
push @SortKeys, sort { lc $a cmp lc $b } ( values %List );
|
|
}
|
|
|
|
# translate value
|
|
if ( $OptionRef->{Translation} ) {
|
|
for my $Row ( sort keys %{$DataLocal} ) {
|
|
$DataLocal->{$Row} = $Self->{LanguageObject}->Translate( $DataLocal->{$Row} );
|
|
}
|
|
}
|
|
|
|
# sort hash (after the translation)
|
|
if ( $OptionRef->{Sort} eq 'NumericKey' ) {
|
|
@SortKeys = sort { $a <=> $b } ( keys %{$DataLocal} );
|
|
}
|
|
elsif ( $OptionRef->{Sort} eq 'NumericValue' ) {
|
|
@SortKeys = sort { $DataLocal->{$a} <=> $DataLocal->{$b} } ( keys %{$DataLocal} );
|
|
}
|
|
elsif ( $OptionRef->{Sort} eq 'AlphanumericKey' ) {
|
|
@SortKeys = sort( keys %{$DataLocal} );
|
|
}
|
|
elsif ( $OptionRef->{Sort} eq 'TreeView' ) {
|
|
|
|
# add suffix for correct sorting
|
|
my %SortHash;
|
|
KEY:
|
|
for my $Key ( sort keys %{$DataLocal} ) {
|
|
next KEY if !defined $DataLocal->{$Key};
|
|
$SortHash{$Key} = $DataLocal->{$Key} . '::';
|
|
}
|
|
@SortKeys = sort { lc $SortHash{$a} cmp lc $SortHash{$b} } ( keys %SortHash );
|
|
}
|
|
elsif ( $OptionRef->{Sort} eq 'IndividualKey' && $OptionRef->{SortIndividual} ) {
|
|
my %List = %{$DataLocal};
|
|
for my $Key ( @{ $OptionRef->{SortIndividual} } ) {
|
|
if ( $List{$Key} ) {
|
|
push @SortKeys, $Key;
|
|
delete $List{$Key};
|
|
}
|
|
}
|
|
push @SortKeys, sort { lc $List{$a} cmp lc $List{$b} } ( keys %List );
|
|
}
|
|
elsif ( $OptionRef->{Sort} eq 'IndividualValue' && $OptionRef->{SortIndividual} ) {
|
|
|
|
# already done before the translation
|
|
}
|
|
else {
|
|
@SortKeys = sort {
|
|
lc( $DataLocal->{$a} // '' )
|
|
cmp lc( $DataLocal->{$b} // '' )
|
|
} ( keys %{$DataLocal} );
|
|
$OptionRef->{Sort} = 'AlphanumericValue';
|
|
}
|
|
|
|
# create DataRef
|
|
for my $Row (@SortKeys) {
|
|
$DataRef->[$Counter]->{Key} = $Row;
|
|
$DataRef->[$Counter]->{Value} = $DataLocal->{$Row};
|
|
$Counter++;
|
|
}
|
|
}
|
|
|
|
# if ArrayHashRef was given
|
|
elsif ( ref $DataLocal eq 'ARRAY' && ref $DataLocal->[0] eq 'HASH' ) {
|
|
|
|
# get missing parents and mark them for disable later
|
|
if ( $OptionRef->{Sort} eq 'TreeView' ) {
|
|
|
|
# build a list of element longnames
|
|
my @NewDataLocal;
|
|
|
|
my %List;
|
|
for my $ValueHash ( @{$DataLocal} ) {
|
|
$List{ $ValueHash->{Value} } = 1;
|
|
}
|
|
|
|
# get each data value hash
|
|
for my $ValueHash ( @{$DataLocal} ) {
|
|
|
|
my $Parents = '';
|
|
|
|
# try to split its parents (e.g. Queue or Service) GrandParent::Parent::Son
|
|
my @Elements = split /::/, $ValueHash->{Value};
|
|
|
|
# get each element in the hierarchy
|
|
for my $Element (@Elements) {
|
|
|
|
# add its own parents for the complete name
|
|
my $ElementLongName = $Parents . $Element;
|
|
|
|
# check if element exists in the original data or if it is already marked
|
|
if ( !$List{$ElementLongName} && !$DisabledElements{$ElementLongName} ) {
|
|
|
|
# mark element as disabled
|
|
$DisabledElements{$ElementLongName} = 1;
|
|
|
|
# push the missing element to the data local array
|
|
push @NewDataLocal, {
|
|
Key => $ElementLongName . '_Disabled',
|
|
Value => $ElementLongName,
|
|
Disabled => 1,
|
|
};
|
|
}
|
|
$Parents .= $Element . '::';
|
|
}
|
|
|
|
# push the element to the data local array
|
|
push @NewDataLocal, {
|
|
Key => $ValueHash->{Key},
|
|
Value => $ValueHash->{Value},
|
|
Selected => $ValueHash->{Selected} ? 1 : 0,
|
|
Disabled => $ValueHash->{Disabled} ? 1 : 0,
|
|
};
|
|
}
|
|
|
|
# override the data local with the new one
|
|
@{$DataLocal} = @NewDataLocal;
|
|
}
|
|
|
|
# create DataRef
|
|
for my $Row ( @{$DataLocal} ) {
|
|
if ( ref $Row eq 'HASH' && defined $Row->{Key} ) {
|
|
$DataRef->[$Counter]->{Key} = $Row->{Key};
|
|
$DataRef->[$Counter]->{Value} = $Row->{Value};
|
|
|
|
# translate value
|
|
if ( $OptionRef->{Translation} ) {
|
|
$DataRef->[$Counter]->{Value} = $Self->{LanguageObject}->Translate( $DataRef->[$Counter]->{Value} );
|
|
}
|
|
|
|
# set Selected and Disabled options
|
|
if ( $Row->{Selected} ) {
|
|
$DataRef->[$Counter]->{Selected} = 1;
|
|
}
|
|
elsif ( $Row->{Disabled} ) {
|
|
$DataRef->[$Counter]->{Disabled} = 1;
|
|
}
|
|
$Counter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
# if ArrayRef was given
|
|
elsif ( ref $DataLocal eq 'ARRAY' ) {
|
|
|
|
# get missing parents and mark them for disable later
|
|
if ( $OptionRef->{Sort} eq 'TreeView' ) {
|
|
my %List = map { $_ => 1 } @{$DataLocal};
|
|
|
|
# get each data value
|
|
for my $Key ( sort keys %List ) {
|
|
my $Parents = '';
|
|
|
|
# try to split its parents (e.g. Queue or Service) GrandParent::Parent::Son
|
|
my @Elements = split /::/, $Key;
|
|
|
|
# get each element in the hierarchy
|
|
for my $Element (@Elements) {
|
|
|
|
# add its own parents for the complete name
|
|
my $ElementLongName = $Parents . $Element;
|
|
|
|
# check if element exists in the original data or if it is already marked
|
|
if ( !$List{$ElementLongName} && !$DisabledElements{$ElementLongName} ) {
|
|
|
|
# mark element as disabled
|
|
$DisabledElements{$ElementLongName} = 1;
|
|
|
|
# add the element to the original data to be disabled later
|
|
push @{$DataLocal}, $ElementLongName;
|
|
}
|
|
$Parents .= $Element . '::';
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $OptionRef->{Sort} eq 'IndividualValue' && $OptionRef->{SortIndividual} ) {
|
|
my %List = map { $_ => 1 } @{$DataLocal};
|
|
$DataLocal = [];
|
|
for my $Key ( @{ $OptionRef->{SortIndividual} } ) {
|
|
if ( $List{$Key} ) {
|
|
push @{$DataLocal}, $Key;
|
|
delete $List{$Key};
|
|
}
|
|
}
|
|
push @{$DataLocal}, sort { $a cmp $b } ( keys %List );
|
|
}
|
|
|
|
my %ReverseHash;
|
|
|
|
# translate value
|
|
if ( $OptionRef->{Translation} ) {
|
|
my @TranslateArray;
|
|
for my $Row ( @{$DataLocal} ) {
|
|
my $TranslateString = $Self->{LanguageObject}->Translate($Row);
|
|
push @TranslateArray, $TranslateString;
|
|
$ReverseHash{$TranslateString} = $Row;
|
|
}
|
|
$DataLocal = \@TranslateArray;
|
|
}
|
|
else {
|
|
for my $Row ( @{$DataLocal} ) {
|
|
$ReverseHash{$Row} = $Row;
|
|
}
|
|
}
|
|
|
|
# sort array
|
|
if ( $OptionRef->{Sort} eq 'AlphanumericKey' || $OptionRef->{Sort} eq 'AlphanumericValue' )
|
|
{
|
|
my @SortArray = sort( @{$DataLocal} );
|
|
$DataLocal = \@SortArray;
|
|
}
|
|
elsif ( $OptionRef->{Sort} eq 'NumericKey' || $OptionRef->{Sort} eq 'NumericValue' ) {
|
|
my @SortArray = sort { $a <=> $b } ( @{$DataLocal} );
|
|
$DataLocal = \@SortArray;
|
|
}
|
|
elsif ( $OptionRef->{Sort} eq 'TreeView' ) {
|
|
|
|
# sort array, add '::' in the comparison, for proper sort of Items with Items::SubItems
|
|
my @SortArray = sort { $a . '::' cmp $b . '::' } @{$DataLocal};
|
|
$DataLocal = \@SortArray;
|
|
}
|
|
|
|
# create DataRef
|
|
for my $Row ( @{$DataLocal} ) {
|
|
$DataRef->[$Counter]->{Key} = $ReverseHash{$Row};
|
|
$DataRef->[$Counter]->{Value} = $Row;
|
|
$Counter++;
|
|
}
|
|
}
|
|
|
|
# check disabled items on ArrayRef or HashRef only
|
|
if (
|
|
ref $DataLocal eq 'HASH'
|
|
|| ( ref $DataLocal eq 'ARRAY' && ref $DataLocal->[0] ne 'HASH' )
|
|
)
|
|
{
|
|
for my $Row ( @{$DataRef} ) {
|
|
if ( defined $Row->{Value} && $DisabledElements{ $Row->{Value} } ) {
|
|
$Row->{Key} = '-';
|
|
$Row->{Disabled} = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# DisabledBranch option
|
|
if ( $OptionRef->{DisabledBranch} ) {
|
|
for my $Row ( @{$DataRef} ) {
|
|
for my $Branch ( sort keys %{ $OptionRef->{DisabledBranch} } ) {
|
|
if ( $Row->{Value} =~ /^(\Q$Branch\E)$/ || $Row->{Value} =~ /^(\Q$Branch\E)::/ ) {
|
|
$Row->{Disabled} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# SelectedID and SelectedValue option
|
|
if ( defined $OptionRef->{SelectedID} || $OptionRef->{SelectedValue} ) {
|
|
for my $Row ( @{$DataRef} ) {
|
|
if (
|
|
(
|
|
(
|
|
defined $Row->{Key}
|
|
&& $OptionRef->{SelectedID}->{ $Row->{Key} }
|
|
)
|
|
||
|
|
(
|
|
defined $Row->{Value}
|
|
&& $OptionRef->{SelectedValue}->{ $Row->{Value} }
|
|
)
|
|
)
|
|
&&
|
|
(
|
|
defined $Row->{Value}
|
|
&& !$DisabledElements{ $Row->{Value} }
|
|
)
|
|
)
|
|
{
|
|
$Row->{Selected} = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# SortReverse option
|
|
if ( $OptionRef->{SortReverse} ) {
|
|
@{$DataRef} = reverse( @{$DataRef} );
|
|
}
|
|
|
|
# PossibleNone option
|
|
if ( $OptionRef->{PossibleNone} ) {
|
|
my %None;
|
|
$None{Key} = '';
|
|
$None{Value} = '-';
|
|
|
|
unshift( @{$DataRef}, \%None );
|
|
}
|
|
|
|
# TreeView option
|
|
if ( $OptionRef->{TreeView} ) {
|
|
|
|
ROW:
|
|
for my $Row ( @{$DataRef} ) {
|
|
|
|
next ROW if !$Row->{Value};
|
|
|
|
my @Fragment = split '::', $Row->{Value};
|
|
$Row->{Value} = pop @Fragment;
|
|
|
|
# translate the individual tree options
|
|
if ( $OptionRef->{Translation} ) {
|
|
$Row->{Value} = $Self->{LanguageObject}->Translate( $Row->{Value} );
|
|
}
|
|
|
|
# TODO: Here we are combining Max with HTMLQuote, check below for the REMARK:
|
|
# Max and HTMLQuote needs to be done before spaces insert but after the split of the
|
|
# parents, then it is not possible to do it outside
|
|
if ( $OptionRef->{HTMLQuote} ) {
|
|
$Row->{Value} = $Self->Ascii2Html(
|
|
Text => $Row->{Value},
|
|
Max => $OptionRef->{Max},
|
|
);
|
|
}
|
|
elsif ( $OptionRef->{Max} ) {
|
|
if ( length $Row->{Value} > $OptionRef->{Max} ) {
|
|
$Row->{Value} = substr( $Row->{Value}, 0, $OptionRef->{Max} - 5 ) . '[...]';
|
|
}
|
|
}
|
|
|
|
# Use unicode 'NO-BREAK SPACE' since unicode characters doesn't need to be escaped.
|
|
# Previously, we used ' ' and we had issue that Option needs to be html encoded
|
|
# in AJAX, and it was causing issues.
|
|
my $Space = "\xA0\xA0" x scalar @Fragment;
|
|
$Space ||= '';
|
|
|
|
$Row->{Value} = $Space . $Row->{Value};
|
|
}
|
|
}
|
|
else {
|
|
|
|
# HTMLQuote option
|
|
if ( $OptionRef->{HTMLQuote} ) {
|
|
for my $Row ( @{$DataRef} ) {
|
|
$Row->{Key} = $Self->Ascii2Html( Text => $Row->{Key} );
|
|
$Row->{Value} = $Self->Ascii2Html( Text => $Row->{Value} );
|
|
}
|
|
}
|
|
|
|
# TODO: Check this comment!
|
|
# Max option
|
|
# REMARK: Don't merge the Max handling with Ascii2Html function call of
|
|
# the HTMLQuote handling. In this case you lose the max handling if you
|
|
# deactivate HTMLQuote
|
|
if ( $OptionRef->{Max} ) {
|
|
|
|
# REMARK: This is the same solution as in Ascii2Html
|
|
for my $Row ( @{$DataRef} ) {
|
|
|
|
if ( ref $Row eq 'HASH' ) {
|
|
if ( length $Row->{Value} > $OptionRef->{Max} ) {
|
|
$Row->{Value} = substr( $Row->{Value}, 0, $OptionRef->{Max} - 5 ) . '[...]';
|
|
}
|
|
}
|
|
else {
|
|
if ( length $Row > $OptionRef->{Max} ) {
|
|
$Row = substr( $Row, 0, $OptionRef->{Max} - 5 ) . '[...]';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $DataRef;
|
|
}
|
|
|
|
=head2 _BuildSelectionOutput()
|
|
|
|
create the html string
|
|
|
|
my $HTMLString = $LayoutObject->_BuildSelectionOutput(
|
|
AttributeRef => $AttributeRef,
|
|
DataRef => $DataRef,
|
|
TreeView => 0, # optional, see BuildSelection()
|
|
FiltersRef => \@Filters, # optional, see BuildSelection()
|
|
FilterActive => $FilterActive, # optional, see BuildSelection()
|
|
ExpandFilters => 1, # optional, see BuildSelection()
|
|
ValidateDateAfter => '2016-01-01', # optional, see BuildSelection()
|
|
ValidateDateBefore => '2016-01-01', # optional, see BuildSelection()
|
|
);
|
|
|
|
my $AttributeRef = {
|
|
name => 'TheName',
|
|
multiple => undef,
|
|
size => 5,
|
|
}
|
|
|
|
my $DataRef = [
|
|
{
|
|
Key => 11,
|
|
Value => 'Text',
|
|
Disabled => 1,
|
|
},
|
|
{
|
|
Key => 'abc',
|
|
Value => ' Text',
|
|
Selected => 1,
|
|
},
|
|
];
|
|
|
|
=cut
|
|
|
|
sub _BuildSelectionOutput {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# start generation, if AttributeRef and DataRef was found
|
|
my $String;
|
|
if ( $Param{AttributeRef} && $Param{DataRef} ) {
|
|
|
|
# generate <select> row
|
|
$String = '<select';
|
|
for my $Key ( sort keys %{ $Param{AttributeRef} } ) {
|
|
if ( $Key && defined $Param{AttributeRef}->{$Key} ) {
|
|
$String .= " $Key=\"$Param{AttributeRef}->{$Key}\"";
|
|
}
|
|
elsif ($Key) {
|
|
$String .= " $Key";
|
|
}
|
|
}
|
|
|
|
# add filters if defined
|
|
if ( $Param{FiltersRef} && scalar @{ $Param{FiltersRef} } > 0 ) {
|
|
my $JSON = $Self->JSONEncode(
|
|
Data => {
|
|
Filters => $Param{FiltersRef},
|
|
},
|
|
NoQuotes => 1,
|
|
);
|
|
my $JSONEscaped = $Kernel::OM->Get('Kernel::System::HTMLUtils')->ToHTML(
|
|
String => $JSON,
|
|
);
|
|
$String .= " data-filters=\"$JSONEscaped\"";
|
|
if ( $Param{FilterActive} ) {
|
|
$String .= ' data-filtered="' . int( $Param{FilterActive} ) . '"';
|
|
}
|
|
if ( $Param{ExpandFilters} ) {
|
|
$String .= ' data-expand-filters="' . int( $Param{ExpandFilters} ) . '"';
|
|
}
|
|
}
|
|
|
|
# tree flag for Input Fields
|
|
if ( $Param{TreeView} ) {
|
|
$String .= ' data-tree="true"';
|
|
}
|
|
|
|
# date validation values
|
|
if ( $Param{ValidateDateAfter} ) {
|
|
$String .= ' data-validate-date-after="' . $Param{ValidateDateAfter} . '"';
|
|
}
|
|
if ( $Param{ValidateDateBefore} ) {
|
|
$String .= ' data-validate-date-before="' . $Param{ValidateDateBefore} . '"';
|
|
}
|
|
|
|
$String .= ">\n";
|
|
|
|
# generate <option> rows
|
|
for my $Row ( @{ $Param{DataRef} } ) {
|
|
my $Key = '';
|
|
if ( defined $Row->{Key} ) {
|
|
$Key = $Row->{Key};
|
|
}
|
|
my $Value = '';
|
|
if ( defined $Row->{Value} ) {
|
|
$Value = $Row->{Value};
|
|
}
|
|
my $SelectedDisabled = '';
|
|
if ( $Row->{Selected} ) {
|
|
$SelectedDisabled = ' selected="selected"';
|
|
}
|
|
elsif ( $Row->{Disabled} ) {
|
|
$SelectedDisabled = ' disabled="disabled"';
|
|
}
|
|
my $OptionTitle = '';
|
|
if ( $Param{OptionTitle} ) {
|
|
$OptionTitle = ' title="' . $Value . '"';
|
|
}
|
|
$String .= " <option value=\"$Key\"$SelectedDisabled$OptionTitle>$Value</option>\n";
|
|
}
|
|
$String .= '</select>';
|
|
|
|
if ( $Param{TreeView} ) {
|
|
my $TreeSelectionMessage = $Self->{LanguageObject}->Translate("Show Tree Selection");
|
|
$String
|
|
.= ' <a href="#" title="'
|
|
. $TreeSelectionMessage
|
|
. '" class="ShowTreeSelection"><span>'
|
|
. $TreeSelectionMessage . '</span><i class="fa fa-sitemap"></i></a>';
|
|
}
|
|
|
|
}
|
|
return $String;
|
|
}
|
|
|
|
sub _DisableBannerCheck {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
return 1 if !$Kernel::OM->Get('Kernel::Config')->Get('Secure::DisableBanner');
|
|
return if !$Param{OutputRef};
|
|
|
|
# remove the version tag from the header
|
|
${ $Param{OutputRef} } =~ s{
|
|
^ X-Powered-By: .+? Open \s Ticket \s Request \s System \s \(http .+? \)$ \n
|
|
}{}smx;
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 _RemoveScriptTags()
|
|
|
|
This function will remove the surrounding <script> tags of a
|
|
piece of JavaScript code, if they are present, and return the result.
|
|
|
|
my $CodeContent = $LayoutObject->_RemoveScriptTags(Code => $SomeCode);
|
|
|
|
=cut
|
|
|
|
sub _RemoveScriptTags {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $Code = $Param{Code} || '';
|
|
|
|
if ( $Code =~ m/<script/ ) {
|
|
|
|
# cut out dtl block comments of already replaced dtl blocks
|
|
$Code =~ s{
|
|
^
|
|
<!--
|
|
\/?
|
|
\w+
|
|
-->
|
|
\r?\n
|
|
}{}smxg;
|
|
|
|
# cut out opening script tags
|
|
$Code =~ s{
|
|
<script[^>]+>
|
|
(?:\s*<!--)?
|
|
(?:\s*//\s*<!\[CDATA\[)?
|
|
}
|
|
{}smxg;
|
|
|
|
# cut out closing script tags
|
|
$Code =~ s{
|
|
(?:-->\s*)?
|
|
(?://\s*\]\]>\s*)?
|
|
</script>
|
|
}{}smxg;
|
|
|
|
}
|
|
return $Code;
|
|
}
|
|
|
|
=head2 WrapPlainText()
|
|
|
|
This sub has two main functionalities:
|
|
1. Check every line and make sure that "\n" is the ending of the line.
|
|
2. If the line does _not_ start with ">" (e.g. not cited text)
|
|
wrap it after the number of "MaxCharacters" (e.g. if MaxCharacters is "80" wrap after 80 characters).
|
|
Do this _just_ if the line, that should be wrapped, contains space characters at which the line can be wrapped.
|
|
|
|
If you need more info to understand what it does, take a look at the UnitTest WrapPlainText.t to see
|
|
use cases there.
|
|
|
|
my $WrappedPlainText = $LayoutObject->WrapPlainText(
|
|
PlainText => "Some Plain text that is longer than the amount stored in MaxCharacters",
|
|
MaxCharacters => 80,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub WrapPlainText {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# Return if we did not get MaxCharacters
|
|
# or MaxCharacters doesn't contain just an int
|
|
if ( !IsPositiveInteger( $Param{MaxCharacters} ) ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Got no or invalid MaxCharacters!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# Return if we didn't get PlainText
|
|
if ( !defined $Param{PlainText} ) {
|
|
return;
|
|
}
|
|
|
|
# Return if we got no Scalar
|
|
if ( ref $Param{PlainText} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Had no string in PlainText!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# Return PlainText if we have less than MaxCharacters
|
|
if ( length $Param{PlainText} < $Param{MaxCharacters} ) {
|
|
return $Param{PlainText};
|
|
}
|
|
|
|
my $WorkString = $Param{PlainText};
|
|
|
|
# Normalize line endings to avoid problems with \r\n (bug#11078).
|
|
$WorkString =~ s/\r\n?/\n/g;
|
|
$WorkString =~ s/(^>.+|.{4,$Param{MaxCharacters}})(?:\s|\z)/$1\n/gm;
|
|
return $WorkString;
|
|
}
|
|
|
|
=head2 SetRichTextParameters()
|
|
|
|
set properties for rich text editor and send them to JS via AddJSData()
|
|
|
|
$LayoutObject->SetRichTextParameters(
|
|
Data => \%Param,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub SetRichTextParameters {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
$Param{Data} ||= {};
|
|
|
|
# get and check param Data
|
|
if ( ref $Param{Data} ne 'HASH' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need HashRef in Param Data! Got: '" . ref( $Param{Data} ) . "'!",
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
|
|
# get needed objects
|
|
my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# get needed variables
|
|
my $ScreenRichTextHeight = $Param{Data}->{RichTextHeight} || $ConfigObject->Get("Frontend::RichTextHeight");
|
|
my $ScreenRichTextWidth = $Param{Data}->{RichTextWidth} || $ConfigObject->Get("Frontend::RichTextWidth");
|
|
my $RichTextType = $Param{Data}->{RichTextType} || '';
|
|
my $PictureUploadAction = $Param{Data}->{RichTextPictureUploadAction} || '';
|
|
my $TextDir = $Self->{TextDirection} || '';
|
|
my $EditingAreaCSS = 'body.cke_editable { ' . $ConfigObject->Get("Frontend::RichText::DefaultCSS") . ' }';
|
|
|
|
# decide if we need to use the enhanced mode (with tables)
|
|
my @Toolbar;
|
|
my @ToolbarWithoutImage;
|
|
|
|
if ( $RichTextType eq 'CodeMirror' ) {
|
|
@Toolbar = @ToolbarWithoutImage = [
|
|
[ 'autoFormat', 'CommentSelectedRange', 'UncommentSelectedRange', 'AutoComplete' ],
|
|
[ 'Find', 'Replace', '-', 'SelectAll' ],
|
|
['Maximize'],
|
|
];
|
|
}
|
|
elsif ( $ConfigObject->Get("Frontend::RichText::EnhancedMode") == '1' ) {
|
|
@Toolbar = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript',
|
|
'-', 'NumberedList', 'BulletedList', 'Table', '-', 'Outdent',
|
|
'Indent', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock',
|
|
'-', 'Link', 'Unlink', 'Undo', 'Redo', 'SelectAll'
|
|
],
|
|
'/',
|
|
[
|
|
'Image', 'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote',
|
|
'-', '-', 'Find', 'Replace', 'TextColor',
|
|
'BGColor', 'RemoveFormat', '-', 'ShowBlocks', 'Source', 'SpecialChar',
|
|
'-', 'Maximize'
|
|
],
|
|
[ 'Format', 'Font', 'FontSize' ]
|
|
];
|
|
@ToolbarWithoutImage = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript',
|
|
'-', 'NumberedList', 'BulletedList', 'Table', '-', 'Outdent',
|
|
'Indent', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock',
|
|
'-', 'Link', 'Unlink', 'Undo', 'Redo', 'SelectAll'
|
|
],
|
|
'/',
|
|
[
|
|
'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote', '-',
|
|
'-', 'Find', 'Replace', 'TextColor', 'BGColor',
|
|
'RemoveFormat', '-', 'ShowBlocks', 'Source', 'SpecialChar', '-',
|
|
'Maximize'
|
|
],
|
|
[ 'Format', 'Font', 'FontSize' ]
|
|
];
|
|
}
|
|
else {
|
|
@Toolbar = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike', '-', 'NumberedList',
|
|
'BulletedList', '-', 'Outdent', 'Indent', '-', 'JustifyLeft',
|
|
'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'Link', 'Unlink',
|
|
'-', 'Image', 'HorizontalRule', '-', 'Undo', 'Redo',
|
|
'-', 'Find'
|
|
],
|
|
'/',
|
|
[
|
|
'Format', 'Font', 'FontSize', '-', 'TextColor', 'BGColor',
|
|
'RemoveFormat', '-', 'Source', 'SpecialChar', 'SplitQuote', 'RemoveQuote',
|
|
'-', 'Maximize'
|
|
]
|
|
];
|
|
@ToolbarWithoutImage = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike',
|
|
'-', 'NumberedList', 'BulletedList', '-',
|
|
'Outdent', 'Indent', '-', 'JustifyLeft',
|
|
'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-',
|
|
'Link', 'Unlink', '-', 'HorizontalRule',
|
|
'-', 'Undo', 'Redo', '-',
|
|
'Find'
|
|
],
|
|
'/',
|
|
[
|
|
'Format', 'Font', 'FontSize', '-', 'TextColor', 'BGColor',
|
|
'RemoveFormat', '-', 'Source', 'SpecialChar', 'SplitQuote', 'RemoveQuote',
|
|
'-', 'Maximize'
|
|
]
|
|
];
|
|
}
|
|
|
|
# set data with AddJSData()
|
|
$Self->AddJSData(
|
|
Key => 'RichText',
|
|
Value => {
|
|
Height => $ScreenRichTextHeight,
|
|
Width => $ScreenRichTextWidth,
|
|
TextDir => $TextDir,
|
|
EditingAreaCSS => $EditingAreaCSS,
|
|
Lang => {
|
|
SplitQuote => $LanguageObject->Translate('Split Quote'),
|
|
RemoveQuote => $LanguageObject->Translate('Remove Quote'),
|
|
},
|
|
Toolbar => $Toolbar[0],
|
|
ToolbarWithoutImage => $ToolbarWithoutImage[0],
|
|
PictureUploadAction => $PictureUploadAction,
|
|
Type => $RichTextType,
|
|
},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 CustomerSetRichTextParameters()
|
|
|
|
set properties for customer rich text editor and send them to JS via AddJSData()
|
|
|
|
$LayoutObject->CustomerSetRichTextParameters(
|
|
Data => \%Param,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub CustomerSetRichTextParameters {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
$Param{Data} ||= {};
|
|
|
|
# get and check param Data
|
|
if ( ref $Param{Data} ne 'HASH' ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need HashRef in Param Data! Got: '" . ref( $Param{Data} ) . "'!",
|
|
);
|
|
$Self->FatalError();
|
|
}
|
|
|
|
# get needed objects
|
|
my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
my $ScreenRichTextHeight = $ConfigObject->Get("Frontend::RichTextHeight");
|
|
my $ScreenRichTextWidth = $ConfigObject->Get("Frontend::RichTextWidth");
|
|
my $TextDir = $Self->{TextDirection} || '';
|
|
my $PictureUploadAction = $Param{Data}->{RichTextPictureUploadAction} || '';
|
|
my $EditingAreaCSS = 'body { ' . $ConfigObject->Get("Frontend::RichText::DefaultCSS") . ' }';
|
|
|
|
# decide if we need to use the enhanced mode (with tables)
|
|
my @Toolbar;
|
|
my @ToolbarWithoutImage;
|
|
|
|
if ( $ConfigObject->Get("Frontend::RichText::EnhancedMode::Customer") == '1' ) {
|
|
@Toolbar = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript',
|
|
'-', 'NumberedList', 'BulletedList', 'Table', '-', 'Outdent',
|
|
'Indent', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock',
|
|
'-', 'Link', 'Unlink', 'Undo', 'Redo', 'SelectAll'
|
|
],
|
|
'/',
|
|
[
|
|
'Image', 'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote',
|
|
'-', '-', 'Find', 'Replace', 'TextColor',
|
|
'BGColor', 'RemoveFormat', '-', 'ShowBlocks', 'Source', 'SpecialChar',
|
|
'-', 'Maximize'
|
|
],
|
|
[ 'Format', 'Font', 'FontSize' ]
|
|
];
|
|
@ToolbarWithoutImage = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript',
|
|
'-', 'NumberedList', 'BulletedList', 'Table', '-', 'Outdent',
|
|
'Indent', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock',
|
|
'-', 'Link', 'Unlink', 'Undo', 'Redo', 'SelectAll'
|
|
],
|
|
'/',
|
|
[
|
|
'HorizontalRule', 'PasteText', 'PasteFromWord', 'SplitQuote', 'RemoveQuote', '-',
|
|
'-', 'Find', 'Replace', 'TextColor', 'BGColor',
|
|
'RemoveFormat', '-', 'ShowBlocks', 'Source', 'SpecialChar', '-',
|
|
'Maximize'
|
|
],
|
|
[ 'Format', 'Font', 'FontSize' ]
|
|
];
|
|
}
|
|
else {
|
|
@Toolbar = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike', '-', 'NumberedList',
|
|
'BulletedList', '-', 'Outdent', 'Indent', '-', 'JustifyLeft',
|
|
'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'Link', 'Unlink',
|
|
'-', 'Image', 'HorizontalRule', '-', 'Undo', 'Redo',
|
|
'-', 'Find'
|
|
],
|
|
'/',
|
|
[
|
|
'Format', 'Font', 'FontSize', '-', 'TextColor', 'BGColor',
|
|
'RemoveFormat', '-', 'Source', 'SpecialChar', 'SplitQuote', 'RemoveQuote',
|
|
'-', 'Maximize'
|
|
]
|
|
];
|
|
@ToolbarWithoutImage = [
|
|
[
|
|
'Bold', 'Italic', 'Underline', 'Strike',
|
|
'-', 'NumberedList', 'BulletedList', '-',
|
|
'Outdent', 'Indent', '-', 'JustifyLeft',
|
|
'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-',
|
|
'Link', 'Unlink', '-', 'HorizontalRule',
|
|
'-', 'Undo', 'Redo', '-',
|
|
'Find'
|
|
],
|
|
'/',
|
|
[
|
|
'Format', 'Font', 'FontSize', '-', 'TextColor', 'BGColor',
|
|
'RemoveFormat', '-', 'Source', 'SpecialChar', 'SplitQuote', 'RemoveQuote',
|
|
'-', 'Maximize'
|
|
]
|
|
];
|
|
}
|
|
|
|
# set data with AddJSData()
|
|
$Self->AddJSData(
|
|
Key => 'RichText',
|
|
Value => {
|
|
Height => $ScreenRichTextHeight,
|
|
Width => $ScreenRichTextWidth,
|
|
TextDir => $TextDir,
|
|
EditingAreaCSS => $EditingAreaCSS,
|
|
Lang => {
|
|
SplitQuote => $LanguageObject->Translate('Split Quote'),
|
|
},
|
|
Toolbar => $Toolbar[0],
|
|
ToolbarWithoutImage => $ToolbarWithoutImage[0],
|
|
PictureUploadAction => $PictureUploadAction,
|
|
},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 UserInitialsGet()
|
|
|
|
Get initials from a full name of a user.
|
|
|
|
my $UserInitials = $LayoutObject->UserInitialsGet(
|
|
Fullname => 'John Doe',
|
|
);
|
|
|
|
Returns string of exactly two uppercase characters that represent user initials:
|
|
|
|
$UserInitials = 'JD';
|
|
|
|
Please note that this function will return 'O' if invalid name (without any word characters) was supplied.
|
|
|
|
=cut
|
|
|
|
sub UserInitialsGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# Fallback in case name is invalid.
|
|
my $UserInitials = 'O';
|
|
return $UserInitials if !$Param{Fullname};
|
|
|
|
# Remove anything found in brackets (email address, etc).
|
|
my $Fullname = $Param{Fullname} =~ s/[<[{(].*[>\]})]//r;
|
|
|
|
# Trim whitespaces.
|
|
$Fullname =~ s/^\s+|\s+$//g;
|
|
|
|
# Split full name by whitespace.
|
|
my @UserNames = split /\s+/, $Fullname;
|
|
if (@UserNames) {
|
|
|
|
# Cleanup unnecessary characters.
|
|
my $FirstName = $UserNames[0] =~ s/\W//gr;
|
|
return $UserInitials if !$FirstName;
|
|
|
|
# Get first character of first name.
|
|
$UserInitials = uc substr $FirstName, 0, 1;
|
|
|
|
if ( @UserNames > 1 ) {
|
|
|
|
# Cleanup unnecessary characters.
|
|
my $LastName = $UserNames[-1] =~ s/\W//gr;
|
|
return $UserInitials if !$LastName;
|
|
|
|
# Get first character of last name.
|
|
$UserInitials .= uc substr $LastName, 0, 1;
|
|
}
|
|
}
|
|
|
|
return $UserInitials;
|
|
}
|
|
|
|
1;
|
|
|
|
=end Internal:
|
|
|
|
=head1 TERMS AND CONDITIONS
|
|
|
|
This software is part of the OTRS project (L<https://otrs.org/>).
|
|
|
|
This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
|
the enclosed file COPYING for license information (GPL). If you
|
|
did not receive this file, see L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
|
|
|
=cut
|