# -- # 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::Template; use strict; use warnings; use Scalar::Util qw(); use Template; use Template::Stash::XS; use Template::Context; use Template::Plugins; use Kernel::Output::Template::Provider; our $ObjectManagerDisabled = 1; =head1 NAME Kernel::Output::HTML::Layout::Template - template rendering engine based on Template::Toolkit =head1 PUBLIC INTERFACE =head2 Output() generates HTML output based on a template file. Using a template file: my $HTML = $LayoutObject->Output( TemplateFile => 'AdminLog.tt', Data => \%Param, ); Using a template string: my $HTML = $LayoutObject->Output( Template => '[% Data.SomeKey | html %]', Data => \%Param, ); Additional parameters: AJAX - AJAX-specific adjustements: this causes [% WRAPPER JSOnDocumentComplete %] blocks NOT to be replaced. This is important to be able to generate snippets which can be cached. Also, JS data added with AddJSData() calls is appended to the output here. my $HTML = $LayoutObject->Output( TemplateFile => 'AdminLog.tt', Data => \%Param, AJAX => 1, ); KeepScriptTags - DEPRECATED, please use the parameter "AJAX" instead =cut sub Output { 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(); } # asure compatibility with old KeepScriptTags parameter if ( $Param{KeepScriptTags} && !$Param{AJAX} ) { $Param{AJAX} = $Param{KeepScriptTags}; } # fill init Env if ( !$Self->{EnvRef} ) { %{ $Self->{EnvRef} } = %ENV; # all $Self->{*} for ( sort keys %{$Self} ) { if ( defined $Self->{$_} && !ref $Self->{$_} ) { $Self->{EnvRef}->{$_} = $Self->{$_}; } } } # add new env if ( $Self->{EnvNewRef} ) { for my $Key ( sort keys %{ $Self->{EnvNewRef} } ) { $Self->{EnvRef}->{$Key} = $Self->{EnvNewRef}->{$Key}; } undef $Self->{EnvNewRef}; } # if we use the HTML5 input type 'email' jQuery Validate will always validate # we do not want that if CheckEmailAddresses is set to 'no' in SysConfig $Self->{EnvRef}->{EmailFieldType} = $Kernel::OM->Get('Kernel::Config')->Get('CheckEmailAddresses') ? 'email' : 'text'; my @TemplateFolders = ( "$Self->{CustomTemplateDir}", "$Self->{CustomStandardTemplateDir}", "$Self->{TemplateDir}", "$Self->{StandardTemplateDir}", ); my $TemplateString; if ( $Param{TemplateFile} ) { $Param{TemplateFileTT} .= "$Param{TemplateFile}.tt"; } # take templates from string/array elsif ( defined $Param{Template} && ref $Param{Template} eq 'ARRAY' ) { for ( @{ $Param{Template} } ) { $TemplateString .= $_; } } elsif ( defined $Param{Template} ) { $TemplateString = $Param{Template}; } else { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => 'Need Template or TemplateFile Param!', ); $Self->FatalError(); } if ( !$Self->{TemplateObject} ) { $Self->{TemplateProviderObject} = Kernel::Output::Template::Provider->new( { INCLUDE_PATH => \@TemplateFolders, EVAL_PERL => 1, COMPILE_EXT => '.ttc', } ); $Self->{TemplateProviderObject}->OTRSInit( LayoutObject => $Self, ); my $Plugins = Template::Plugins->new( { PLUGIN_BASE => 'Kernel::Output::Template::Plugin', } ); my $Context = Template::Context->new( { EVAL_PERL => 1, STASH => Template::Stash::XS->new(), LOAD_TEMPLATES => [ $Self->{TemplateProviderObject} ], LOAD_PLUGINS => [$Plugins], } ); # Store a weak reference to the LayoutObject in the context # to avoid ring references. We need it for the plugins. $Context->{LayoutObject} = $Self; Scalar::Util::weaken( $Context->{LayoutObject} ); my $Success = $Self->{TemplateObject} = Template->new( { CONTEXT => $Context, #DEBUG => Template::Constants::DEBUG_ALL, } ); if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "$Template::ERROR;", ); # $Self->FatalError(); # Don't use FatalError here, might cause infinite recursion die "$Template::ERROR\n"; } } my $Output; my $Success = $Self->{TemplateObject}->process( $Param{TemplateFileTT} // \$TemplateString, { Data => $Param{Data} // {}, global => { BlockData => $Self->{BlockData} // [], KeepScriptTags => $Param{AJAX} // 0, }, }, \$Output, ); if ( !$Success ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => $Self->{TemplateObject}->error(), ); $Self->FatalError(); } # If the browser does not send the session cookie, we need to append it to all links and image urls. # We cannot do this in the template preprocessor because links are often dynamically generated. if ( $Self->{SessionID} && !$Self->{SessionIDCookie} ) { # rewrite a hrefs $Output =~ s{ () } { my $AHref = $1; my $Target = $2; my $End = $3; my $RealEnd = $4; if ( lc($Target) =~ /^(http:|https:|#|ftp:)/ || $Target !~ /\.(pl|php|cgi|fcg|fcgi|fpl)(\?|$)/ || $Target =~ /(\?|&|;)\Q$Self->{SessionName}\E=/) { $AHref.$Target.$End.$RealEnd; } else { $AHref.$Target.';'.$Self->{SessionName}.'='.$Self->{SessionID}.$End.$RealEnd; } }iegxs; # rewrite img and iframe src $Output =~ s{ (<(?:img|iframe).+?src=")(.+?)(".+?>) } { my $AHref = $1; my $Target = $2; my $End = $3; if (lc($Target) =~ m{^http s? :}smx || !$Self->{SessionID} || $Target !~ /\.(pl|php|cgi|fcg|fcgi|fpl)(\?|$)/ || $Target =~ /\Q$Self->{SessionName}\E=/) { $AHref.$Target.$End; } else { $AHref.$Target.'&'.$Self->{SessionName}.'='.$Self->{SessionID}.$End; } }iegxs; } # # "Post" Output filter handling # if ( $Self->{FilterElementPost} && ref $Self->{FilterElementPost} eq 'HASH' ) { # extract filter list my %FilterList = %{ $Self->{FilterElementPost} }; my $MainObject = $Kernel::OM->Get('Kernel::System::Main'); FILTER: for my $Filter ( sort keys %FilterList ) { # extract filter config my $FilterConfig = $FilterList{$Filter}; # extract template list my %TemplateList = %{ $FilterConfig->{Templates} || {} }; next FILTER if !$Param{TemplateFile}; next FILTER if !$TemplateList{ $Param{TemplateFile} }; next FILTER if !$Kernel::OM->Get('Kernel::System::Main')->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 => \$Output, TemplateFile => $Param{TemplateFile} || '', ); } } # # AddJSData() handling # if ( $Param{AJAX} ) { my %Data = %{ $Self->{_JSData} // {} }; if (%Data) { my $JSONString = $Kernel::OM->Get('Kernel::System::JSON')->Encode( Data => \%Data, SortKeys => 1, ); $Output .= "\n"; } delete $Self->{_JSData}; } return $Output; } =head2 AddJSOnDocumentComplete() dynamically add JavaScript code that should be executed in Core.App.Ready(). Call this for any dynamically generated code that is not in a template. $LayoutObject->AddJSOnDocumentComplete( Code => $MyCode, ); =cut sub AddJSOnDocumentComplete { my ( $Self, %Param ) = @_; $Self->{_JSOnDocumentComplete} //= []; push @{ $Self->{_JSOnDocumentComplete} }, $Param{Code}; return; } =head2 AddJSData() dynamically add JavaScript data that should be handed over to JavaScript via Core.Config. $LayoutObject->AddJSData( Key => 'Key1', # the key to store this data Value => { ... } # simple or complex data ); =cut sub AddJSData { my ( $Self, %Param ) = @_; return if !$Param{Key}; $Self->{_JSData} //= {}; $Self->{_JSData}->{ $Param{Key} } = $Param{Value}; return; } 1; =head1 TERMS AND CONDITIONS This software is part of the OTRS project (L). 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. =cut