Files
2024-10-14 00:08:40 +02:00

373 lines
11 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::Template::Document;
## no critic(Perl::Critic::Policy::OTRS::RequireCamelCase)
use strict;
use warnings;
use parent qw (Template::Document);
our $ObjectManagerDisabled = 1;
=head1 NAME
Kernel::Output::Template::Document - Template Toolkit document extension package
=head1 PUBLIC INTERFACE
=head2 process()
process this template document. This method is inherited from
Template::Document and used to perform some up-front initialization
and processing.
=cut
sub process {
my ( $Self, $Context ) = @_;
$Self->_InstallOTRSExtensions($Context);
$Self->_PrecalculateBlockStructure($Context);
$Self->_PrecalculateBlockHookSubscriptions($Context);
return $Self->SUPER::process($Context);
}
=begin Internal:
=head2 _InstallOTRSExtensions()
adds some OTRS specific extensions to Template::Toolkit.
=cut
sub _InstallOTRSExtensions {
my ( $Self, $Context ) = @_;
# Already installed, nothing to do.
return if $Context->stash()->get('OTRS');
#
# Load the OTRS plugin. This will register some filters and functions.
#
$Context->stash()->set( 'OTRS', $Context->plugin('OTRS') );
#
# The RenderBlock macro makes it possible to use the old dtl:block-Style block calls
# that are still used by OTRS with Template::Toolkit.
#
# The block data is passed to the template, and this macro processes it and calls the relevant
# blocks.
#
$Context->stash()->set(
'PerformRenderBlock',
sub {
my $output = '';
my ( %_tt_args, $_tt_params );
$_tt_args{'BlockName'} = shift;
$_tt_params = shift;
$_tt_params = {} if ref $_tt_params ne 'HASH';
$_tt_params = { %_tt_args, %$_tt_params };
my $stash = $Context->localise($_tt_params);
eval {
my $BlockName = $stash->get('BlockName');
my $ParentBlock = $stash->get('ParentBlock') || $stash->{_BlockTree};
return if !exists $ParentBlock->{Children};
return if !exists $ParentBlock->{Children}->{$BlockName};
my $TemplateName = $stash->get('template')->{name} // '';
$TemplateName = substr( $TemplateName, 0, -3 ); # remove .tt extension
my $GenerateBlockHook =
$Context->{LayoutObject}->{_BlockHookSubscriptions}->{$TemplateName}->{$BlockName};
for my $TargetBlock ( @{ $ParentBlock->{Children}->{$BlockName} } ) {
$output .= "<!--HookStart${BlockName}-->\n" if $GenerateBlockHook;
$output .= $Context->process(
$TargetBlock->{Path},
{
'Data' => $TargetBlock->{Data},
'ParentBlock' => $TargetBlock,
},
);
$output .= "<!--HookEnd${BlockName}-->\n" if $GenerateBlockHook;
}
delete $ParentBlock->{Children}->{$BlockName};
};
$stash = $Context->delocalise();
die $@ if $@;
return $output;
}
);
#
# This block is used to cut out JavaScript code from the templates and insert it in the
# footer of the page, all in one place.
#
# Usage:
# [% WRAPPER JSOnDocumentComplete -%]
# console.log();
# [%- END %]
#
$Self->{_DEFBLOCKS}->{JSOnDocumentComplete} //= sub {
my $context = shift || die "template sub called without context\n";
my $stash = $context->stash();
my $output = '';
my $_tt_error;
eval {
if ( $stash->get( [ 'global', 0, 'KeepScriptTags', 0 ] ) ) {
$output .= $stash->get('content');
}
else {
push @{ $context->{LayoutObject}->{_JSOnDocumentComplete} }, $stash->get('content');
}
};
if ($@) {
$_tt_error = $context->catch( $@, \$output );
die $_tt_error if $_tt_error->type() ne 'return';
}
return $output;
};
#
# This block is used to insert the collected JavaScript code in the page footer.
#
$Self->{_DEFBLOCKS}->{JSOnDocumentCompleteInsert} //= sub {
my $context = shift || die "template sub called without context\n";
my $stash = $context->stash();
my $output = '';
my $_tt_error;
eval {
my $Code = join "\n", @{ $context->{LayoutObject}->{_JSOnDocumentComplete} || [] };
# remove opening script tags
$Code =~ s{ \s* <script[^>]+> (?:\s*<!--)? (?:\s*//\s*<!\[CDATA\[)? \n? }{}smxg;
# remove closing script tags
$Code =~ s{ \s* (?:-->\s*)? (?://\s*\]\]>\s*)? </script> \n? }{\n\n}smxg;
# remove additional newlines at the end of the code block
$Code =~ s{ \n+ \z }{\n}smxg;
$output .= $Code;
delete $context->{LayoutObject}->{_JSOnDocumentComplete};
};
if ($@) {
$_tt_error = $context->catch( $@, \$output );
die $_tt_error if $_tt_error->type() ne 'return';
}
return $output;
};
#
# This block is used to cut out JavaScript data that needs to be inserted to Core.Config
# from the templates and insert it in the footer of the page, all in one place.
#
# Usage:
# [% Process JSData
# Key = 'Test.Key'
# Value = { ... }
# %]
#
#
$Self->{_DEFBLOCKS}->{JSData} //= sub {
my $context = shift || die "template sub called without context\n";
my $stash = $context->stash();
my $output = '';
my $_tt_error;
eval {
my $Key = $stash->get('Key');
my $Value = $stash->get('Value');
return $output if !$Key;
$context->{LayoutObject}->{_JSData} //= {};
$context->{LayoutObject}->{_JSData}->{$Key} = $Value;
};
if ($@) {
$_tt_error = $context->catch( $@, \$output );
die $_tt_error if $_tt_error->type() ne 'return';
}
return $output;
};
#
# This block is used to insert the collected JavaScript data in the page footer.
#
$Self->{_DEFBLOCKS}->{JSDataInsert} //= sub {
my $context = shift || die "template sub called without context\n";
my $stash = $context->stash();
my $output = '';
my $_tt_error;
eval {
my %Data = %{ $context->{LayoutObject}->{_JSData} // {} };
if (%Data) {
my $JSONString = $Kernel::OM->Get('Kernel::System::JSON')->Encode(
Data => \%Data,
SortKeys => 1,
);
# Escape closing script tags in the JSON content as they will confuse the
# browser's parser.
$JSONString =~ s{ </(?<ScriptTag>script)}{<\\/$+{ScriptTag}}ismxg;
$output .= "Core.Config.AddConfig($JSONString);\n";
}
delete $context->{LayoutObject}->{_JSData};
};
if ($@) {
$_tt_error = $context->catch( $@, \$output );
die $_tt_error if $_tt_error->type() ne 'return';
}
return $output;
};
return;
}
=head2 _PrecalculateBlockStructure()
pre-calculates the tree structure of the blocks so that it
can be used by PerformRenderBlock in an efficient way.
=cut
sub _PrecalculateBlockStructure {
my ( $Self, $Context ) = @_;
my $Defblocks = $Self->{_DEFBLOCKS} || {};
my $BlockData = $Context->stash()->get( [ 'global', 0, 'BlockData', 0 ] ) || [];
return if !@{$BlockData};
my $BlockParents = {};
my $BlockPaths = {};
BLOCKPATHIDENTIFIER:
for my $BlockIdentifier ( sort keys %{$Defblocks} ) {
my @BlockPath = split( m{/}, $BlockIdentifier );
my $BlockPathLength = scalar @BlockPath;
next BLOCKPATHIDENTIFIER if !$BlockPathLength;
$BlockPaths->{ $BlockPath[-1] } = $BlockIdentifier;
$BlockParents->{ $BlockPath[-1] } = [ splice( @BlockPath, 0, $BlockPathLength - 1 ) ];
}
$Context->stash()->{_BlockTree} = {};
my $BlockIndex = 0;
BLOCK:
while ( $BlockIndex < @{$BlockData} ) {
my $Block = $BlockData->[$BlockIndex];
my $BlockName = $Block->{Name};
if ( !exists $BlockPaths->{$BlockName} ) {
$BlockIndex++;
next BLOCK;
}
my $BlockPointer = $Context->stash()->{_BlockTree};
PARENTBLOCK:
for my $ParentBlock ( @{ $BlockParents->{$BlockName} // [] } ) {
# Check if a parent node can be found
if (
!exists $BlockPointer->{Children}
|| !exists $BlockPointer->{Children}->{$ParentBlock}
)
{
# Parent node was not found. That means we dan discard this block.
splice @{$BlockData}, $BlockIndex, 1;
next BLOCK;
}
# Ok, parent block found, update block pointer to last element
$BlockPointer = $BlockPointer->{Children}->{$ParentBlock}->[-1];
}
$Block->{Path} = $BlockPaths->{$BlockName};
# Ok, the parent block pointer was apparently set correctly.
# Now append the data of our current block.
push @{ $BlockPointer->{Children}->{$BlockName} }, $Block;
# Remove block data
splice @{$BlockData}, $BlockIndex, 1;
}
return;
}
=head2 _PrecalculateBlockHookSubscriptions()
=cut
sub _PrecalculateBlockHookSubscriptions {
my ( $Self, $Context ) = @_;
# Only calculate once per LayoutObject
return if defined $Context->{LayoutObject}->{_BlockHookSubscriptions};
my $Config = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::Template::GenerateBlockHooks') // {};
my %BlockHooks;
for my $Key ( sort keys %{ $Config // {} } ) {
for my $Template ( sort keys %{ $Config->{$Key} // {} } ) {
for my $Block ( @{ $Config->{$Key}->{$Template} // [] } ) {
$BlockHooks{$Template}->{$Block} = 1;
}
}
}
$Context->{LayoutObject}->{_BlockHookSubscriptions} = \%BlockHooks;
return;
}
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