init III
This commit is contained in:
372
Perl OTRS/Kernel/Output/Template/Document.pm
Normal file
372
Perl OTRS/Kernel/Output/Template/Document.pm
Normal file
@@ -0,0 +1,372 @@
|
||||
# --
|
||||
# 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
|
||||
212
Perl OTRS/Kernel/Output/Template/Plugin/OTRS.pm
Normal file
212
Perl OTRS/Kernel/Output/Template/Plugin/OTRS.pm
Normal file
@@ -0,0 +1,212 @@
|
||||
# --
|
||||
# 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::Plugin::OTRS;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Template::Plugin);
|
||||
|
||||
use Scalar::Util;
|
||||
|
||||
our $ObjectManagerDisabled = 1;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::Output::Template::Plugin::OTRS - Template Toolkit extension plugin
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 new()
|
||||
|
||||
this plugin registers a few filters and functions in Template::Toolkit.
|
||||
|
||||
These extensions have names starting with an uppercase letter so that
|
||||
you can distinguish them from the builtins of Template::Toolkit which
|
||||
are always lowercase.
|
||||
|
||||
Filters:
|
||||
|
||||
[% Data.MyData | Translate %] - Translate to user language.
|
||||
|
||||
[% Data.Created | Localize("TimeLong") %] - Format DateTime string according to user's locale.
|
||||
[% Data.Created | Localize("TimeShort") %] - Format DateTime string according to user's locale, without seconds.
|
||||
[% Data.Created | Localize("Date") %] - Format DateTime string according to user's locale, only date.
|
||||
|
||||
[% Data.Complex | Interpolate %] - Treat Data.Complex as a TT template and parse it.
|
||||
|
||||
[% Data.String | ReplacePlaceholders("one", "two") %] - Replace Data.String placeholders (i.e. %s) with supplied strings.
|
||||
|
||||
[% Data.Complex | JSON %] - Convert Data.Complex into a JSON string.
|
||||
|
||||
Functions:
|
||||
|
||||
[% Translate("Test string for %s", "Documentation") %] - Translate text, with placeholders.
|
||||
|
||||
[% Config("Home") %] - Get SysConfig configuration value.
|
||||
|
||||
[% Env("Baselink") %] - Get environment value of LayoutObject.
|
||||
|
||||
[% ReplacePlaceholders("This is %s", "<strong>bold text</strong>") %] - Replace string placeholders with supplied values.
|
||||
|
||||
=cut
|
||||
|
||||
sub new {
|
||||
my ( $Class, $Context, @Params ) = @_;
|
||||
|
||||
# Produce a weak reference to the LayoutObject and use that in the filters.
|
||||
# We do this because there could be more than one LayoutObject in the process,
|
||||
# so we don't fetch it from the ObjectManager.
|
||||
#
|
||||
# Don't use $Context in the filters as that creates a circular dependency.
|
||||
my $LayoutObject = $Context->{LayoutObject};
|
||||
Scalar::Util::weaken($LayoutObject);
|
||||
|
||||
my $ConfigFunction = sub {
|
||||
return $Kernel::OM->Get('Kernel::Config')->Get(@_);
|
||||
};
|
||||
|
||||
my $EnvFunction = sub {
|
||||
return $LayoutObject->{EnvRef}->{ $_[0] };
|
||||
};
|
||||
|
||||
my $TranslateFunction = sub {
|
||||
return $LayoutObject->{LanguageObject}->Translate(@_);
|
||||
};
|
||||
|
||||
my $TranslateFilterFactory = sub {
|
||||
my ( $FilterContext, @Parameters ) = @_;
|
||||
return sub {
|
||||
$LayoutObject->{LanguageObject}->Translate( $_[0], @Parameters );
|
||||
};
|
||||
};
|
||||
|
||||
my $LocalizeFunction = sub {
|
||||
my $Format = $_[1];
|
||||
if ( $Format eq 'TimeLong' ) {
|
||||
return $LayoutObject->{LanguageObject}->FormatTimeString( $_[0], 'DateFormat' );
|
||||
}
|
||||
elsif ( $Format eq 'TimeShort' ) {
|
||||
return $LayoutObject->{LanguageObject}->FormatTimeString( $_[0], 'DateFormat', 'NoSeconds' );
|
||||
}
|
||||
elsif ( $Format eq 'Date' ) {
|
||||
return $LayoutObject->{LanguageObject}->FormatTimeString( $_[0], 'DateFormatShort' );
|
||||
}
|
||||
elsif ( $Format eq 'Filesize' ) {
|
||||
return $LayoutObject->HumanReadableDataSize( Size => $_[0] );
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
my $LocalizeFilterFactory = sub {
|
||||
my ( $FilterContext, @Parameters ) = @_;
|
||||
my $Format = $Parameters[0] || 'TimeLong';
|
||||
|
||||
return sub {
|
||||
if ( $Format eq 'TimeLong' ) {
|
||||
return $LayoutObject->{LanguageObject}->FormatTimeString( $_[0], 'DateFormat' );
|
||||
}
|
||||
elsif ( $Format eq 'TimeShort' ) {
|
||||
return $LayoutObject->{LanguageObject}->FormatTimeString( $_[0], 'DateFormat', 'NoSeconds' );
|
||||
}
|
||||
elsif ( $Format eq 'Date' ) {
|
||||
return $LayoutObject->{LanguageObject}->FormatTimeString( $_[0], 'DateFormatShort' );
|
||||
}
|
||||
elsif ( $Format eq 'Filesize' ) {
|
||||
return $LayoutObject->HumanReadableDataSize( Size => $_[0] );
|
||||
}
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
# This filter processes the data as a template and replaces any contained TT tags.
|
||||
# This is expensive and potentially dangerous, use with caution!
|
||||
my $InterpolateFunction = sub {
|
||||
|
||||
# Don't parse if there are no TT tags present!
|
||||
if ( index( $_[0], '[%' ) == -1 ) {
|
||||
return $_[0];
|
||||
}
|
||||
return $Context->include( \$_[0] );
|
||||
};
|
||||
|
||||
my $InterpolateFilterFactory = sub {
|
||||
my ( $FilterContext, @Parameters ) = @_;
|
||||
return sub {
|
||||
|
||||
# Don't parse if there are no TT tags present!
|
||||
if ( index( $_[0], '[%' ) == -1 ) {
|
||||
return $_[0];
|
||||
}
|
||||
return $FilterContext->include( \$_[0] );
|
||||
};
|
||||
};
|
||||
|
||||
# This filter replaces any placeholder occurrences in first parameter (i.e. %s or %d), with following parameters.
|
||||
my $ReplacePlaceholdersFunction = sub {
|
||||
my ( $Text, @Parameters ) = @_;
|
||||
|
||||
$Text //= '';
|
||||
|
||||
return $Text if !@Parameters;
|
||||
|
||||
for ( 0 .. $#Parameters ) {
|
||||
return $Text if !defined $Parameters[$_];
|
||||
$Text =~ s/%(s|d)/$Parameters[$_]/;
|
||||
}
|
||||
|
||||
return $Text;
|
||||
};
|
||||
|
||||
my $ReplacePlaceholdersFilter = sub {
|
||||
my ( $FilterContext, @Parameters ) = @_;
|
||||
return sub {
|
||||
return $ReplacePlaceholdersFunction->( $_[0], @Parameters );
|
||||
};
|
||||
};
|
||||
|
||||
my $JSONFunction = sub {
|
||||
return $LayoutObject->JSONEncode( Data => $_[0] );
|
||||
};
|
||||
|
||||
my $JSONFilter = sub {
|
||||
return $LayoutObject->JSONEncode( Data => $_[0] );
|
||||
};
|
||||
|
||||
$Context->stash()->set( 'Config', $ConfigFunction );
|
||||
$Context->stash()->set( 'Env', $EnvFunction );
|
||||
$Context->stash()->set( 'Translate', $TranslateFunction );
|
||||
$Context->stash()->set( 'Localize', $LocalizeFunction );
|
||||
$Context->stash()->set( 'Interpolate', $InterpolateFunction );
|
||||
$Context->stash()->set( 'ReplacePlaceholders', $ReplacePlaceholdersFunction );
|
||||
$Context->stash()->set( 'JSON', $JSONFunction );
|
||||
|
||||
$Context->define_filter( 'Translate', [ $TranslateFilterFactory, 1 ] );
|
||||
$Context->define_filter( 'Localize', [ $LocalizeFilterFactory, 1 ] );
|
||||
$Context->define_filter( 'Interpolate', [ $InterpolateFilterFactory, 1 ] );
|
||||
$Context->define_filter( 'ReplacePlaceholders', [ $ReplacePlaceholdersFilter, 1 ] );
|
||||
$Context->define_filter( 'JSON', $JSONFilter );
|
||||
|
||||
return bless {
|
||||
_CONTEXT => $Context,
|
||||
_PARAMS => \@Params,
|
||||
}, $Class;
|
||||
}
|
||||
|
||||
=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
|
||||
|
||||
1;
|
||||
706
Perl OTRS/Kernel/Output/Template/Provider.pm
Normal file
706
Perl OTRS/Kernel/Output/Template/Provider.pm
Normal file
@@ -0,0 +1,706 @@
|
||||
# --
|
||||
# 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::Provider;
|
||||
## no critic(Perl::Critic::Policy::OTRS::RequireCamelCase)
|
||||
## nofilter(TidyAll::Plugin::OTRS::Perl::SyntaxCheck)
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw (Template::Provider);
|
||||
|
||||
use Scalar::Util qw();
|
||||
use Template::Constants;
|
||||
|
||||
use Kernel::Output::Template::Document;
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Cache',
|
||||
'Kernel::System::Encode',
|
||||
'Kernel::System::Log',
|
||||
'Kernel::System::Main',
|
||||
);
|
||||
|
||||
# Force the use of our own document class.
|
||||
$Template::Provider::DOCUMENT = 'Kernel::Output::Template::Document';
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Kernel::Output::Template::Provider - Template Toolkit custom provider
|
||||
|
||||
=head1 PUBLIC INTERFACE
|
||||
|
||||
=head2 OTRSInit()
|
||||
|
||||
performs some post-initialization and creates a bridge between Template::Toolkit
|
||||
and OTRS by adding the OTRS objects to the Provider object. This method must be
|
||||
called after instantiating the Provider object.
|
||||
|
||||
Please note that we only store a weak reference to the LayoutObject to avoid ring
|
||||
references.
|
||||
|
||||
=cut
|
||||
|
||||
sub OTRSInit {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# Don't fetch LayoutObject via ObjectManager as there might be several instances involved
|
||||
# at this point (for example in LinkObject there is an own LayoutObject to avoid block
|
||||
# name collisions).
|
||||
$Self->{LayoutObject} = $Param{LayoutObject} || die "Got no LayoutObject!";
|
||||
|
||||
#
|
||||
# Store a weak reference to the LayoutObject to avoid ring references.
|
||||
# We need it for the filters.
|
||||
#
|
||||
Scalar::Util::weaken( $Self->{LayoutObject} );
|
||||
|
||||
# define cache type
|
||||
$Self->{CacheType} = 'TemplateProvider';
|
||||
|
||||
# caching can be disabled for debugging reasons
|
||||
$Self->{CachingEnabled} = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::TemplateCache') // 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
=begin Internal:
|
||||
|
||||
=head2 _fetch()
|
||||
|
||||
try to get a compiled version of a template from the CacheObject,
|
||||
otherwise compile the template and return it.
|
||||
|
||||
Copied and slightly adapted from Template::Provider.
|
||||
|
||||
A note about caching: we have three levels of caching.
|
||||
|
||||
1. we have an in-memory cache that stores the compiled Document objects (fastest).
|
||||
2. we store the parsed data in the CacheObject to be re-used in another request.
|
||||
3. for string templates, we have an in-memory cache in the parsing method _compile().
|
||||
It will return the already parsed object if it sees the same template content again.
|
||||
|
||||
=cut
|
||||
|
||||
sub _fetch {
|
||||
my ( $self, $name, $t_name ) = @_;
|
||||
my $stat_ttl = $self->{STAT_TTL};
|
||||
|
||||
$self->debug("_fetch($name)") if $self->{DEBUG};
|
||||
|
||||
# Check in-memory template cache if we already had this template.
|
||||
$self->{_TemplateCache} //= {};
|
||||
|
||||
if ( $self->{_TemplateCache}->{$name} ) {
|
||||
return $self->{_TemplateCache}->{$name};
|
||||
}
|
||||
|
||||
# See if we already know the template is not found
|
||||
if ( $self->{NOTFOUND}->{$name} ) {
|
||||
return ( undef, Template::Constants::STATUS_DECLINED );
|
||||
}
|
||||
|
||||
# Check if the template exists, is cacheable and if a cached version exists.
|
||||
if ( -e $name && $self->{CachingEnabled} ) {
|
||||
|
||||
my $UserTheme = $self->{LayoutObject}->{EnvRef}->{UserTheme};
|
||||
my $template_mtime = $self->_template_modified($name);
|
||||
my $CacheKey = $self->_compiled_filename($name) . '::' . $template_mtime . '::' . $UserTheme;
|
||||
|
||||
# Is there an up-to-date compiled version in the cache?
|
||||
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
||||
Type => $self->{CacheType},
|
||||
Key => $CacheKey,
|
||||
);
|
||||
|
||||
if ( ref $Cache ) {
|
||||
|
||||
my $compiled_template = $Template::Provider::DOCUMENT->new($Cache);
|
||||
|
||||
# Store in-memory and return the compiled template
|
||||
if ($compiled_template) {
|
||||
|
||||
# Make sure template cache does not get too big
|
||||
if ( keys %{ $self->{_TemplateCache} } > 1000 ) {
|
||||
$self->{_TemplateCache} = {};
|
||||
}
|
||||
|
||||
$self->{_TemplateCache}->{$name} = $compiled_template;
|
||||
|
||||
return $compiled_template;
|
||||
}
|
||||
|
||||
# Problem loading compiled template: warn and continue to fetch source template
|
||||
warn( $self->error(), "\n" );
|
||||
}
|
||||
}
|
||||
|
||||
# load template from source
|
||||
my ( $template, $error ) = $self->_load( $name, $t_name );
|
||||
|
||||
if ($error) {
|
||||
|
||||
# Template could not be fetched. Add to the negative/notfound cache.
|
||||
$self->{NOTFOUND}->{$name} = time;
|
||||
return ( $template, $error );
|
||||
}
|
||||
|
||||
# compile template source
|
||||
( $template, $error ) = $self->_compile( $template, $self->_compiled_filename($name) );
|
||||
|
||||
if ($error) {
|
||||
|
||||
# return any compile time error
|
||||
return ( $template, $error );
|
||||
}
|
||||
|
||||
# Make sure template cache does not get too big
|
||||
if ( keys %{ $self->{_TemplateCache} } > 1000 ) {
|
||||
$self->{_TemplateCache} = {};
|
||||
}
|
||||
|
||||
$self->{_TemplateCache}->{$name} = $template->{data};
|
||||
|
||||
return $template->{data};
|
||||
|
||||
}
|
||||
|
||||
=head2 _load()
|
||||
|
||||
calls our pre processor when loading a template.
|
||||
|
||||
Inherited from Template::Provider.
|
||||
|
||||
=cut
|
||||
|
||||
sub _load {
|
||||
my ( $Self, $Name, $Alias ) = @_;
|
||||
|
||||
my @Result = $Self->SUPER::_load( $Name, $Alias );
|
||||
|
||||
# If there was no error, pre-process our template
|
||||
if ( ref $Result[0] ) {
|
||||
|
||||
$Result[0]->{text} = $Self->_PreProcessTemplateContent(
|
||||
Content => $Result[0]->{text},
|
||||
TemplateFile => $Result[0]->{name},
|
||||
);
|
||||
}
|
||||
|
||||
return @Result;
|
||||
}
|
||||
|
||||
=head2 _compile()
|
||||
|
||||
compiles a .tt template into a Perl package and uses the CacheObject
|
||||
to cache it.
|
||||
|
||||
Copied and slightly adapted from Template::Provider.
|
||||
|
||||
=cut
|
||||
|
||||
sub _compile {
|
||||
my ( $self, $data, $compfile ) = @_;
|
||||
my $text = $data->{text};
|
||||
my ( $parsedoc, $error );
|
||||
|
||||
if ( $self->{DEBUG} ) {
|
||||
$self->debug(
|
||||
"_compile($data, ",
|
||||
defined $compfile ? $compfile : '<no compfile>', ')'
|
||||
);
|
||||
}
|
||||
|
||||
# Check in-memory parser cache if we already had this template content
|
||||
$self->{_ParserCache} //= {};
|
||||
|
||||
if ( $self->{_ParserCache}->{$text} ) {
|
||||
return $self->{_ParserCache}->{$text};
|
||||
}
|
||||
|
||||
my $parser = $self->{PARSER}
|
||||
||= Template::Config->parser( $self->{PARAMS} )
|
||||
|| return ( Template::Config->error(), Template::Constants::STATUS_ERROR );
|
||||
|
||||
# discard the template text - we don't need it any more
|
||||
delete $data->{text};
|
||||
|
||||
# call parser to compile template into Perl code
|
||||
if ( $parsedoc = $parser->parse( $text, $data ) ) {
|
||||
|
||||
$parsedoc->{METADATA} = {
|
||||
'name' => $data->{name},
|
||||
'modtime' => $data->{time},
|
||||
%{ $parsedoc->{METADATA} },
|
||||
};
|
||||
|
||||
# write the Perl code to the file $compfile, if defined
|
||||
if ($compfile) {
|
||||
my $UserTheme = $self->{LayoutObject}->{EnvRef}->{UserTheme};
|
||||
my $CacheKey = $compfile . '::' . $data->{time} . '::' . $UserTheme;
|
||||
|
||||
if ( $self->{CachingEnabled} ) {
|
||||
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
||||
Type => $self->{CacheType},
|
||||
TTL => 60 * 60 * 24,
|
||||
Key => $CacheKey,
|
||||
Value => $parsedoc,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( $data->{data} = $Template::Provider::DOCUMENT->new($parsedoc) ) {
|
||||
|
||||
# Make sure parser cache does not get too big
|
||||
if ( keys %{ $self->{_ParserCache} } > 1000 ) {
|
||||
$self->{_ParserCache} = {};
|
||||
}
|
||||
|
||||
$self->{_ParserCache}->{$text} = $data;
|
||||
|
||||
return $data;
|
||||
}
|
||||
$error = $Template::Document::ERROR;
|
||||
}
|
||||
else {
|
||||
$error = Template::Exception->new( 'parse', "$data->{ name } " . $parser->error() );
|
||||
}
|
||||
|
||||
# return STATUS_ERROR, or STATUS_DECLINED if we're being tolerant
|
||||
return $self->{TOLERANT}
|
||||
? ( undef, Template::Constants::STATUS_DECLINED )
|
||||
: ( $error, Template::Constants::STATUS_ERROR );
|
||||
}
|
||||
|
||||
=end Internal:
|
||||
|
||||
=head2 store()
|
||||
|
||||
inherited from Template::Provider. This function override just makes sure that the original
|
||||
in-memory cache cannot be used.
|
||||
|
||||
=cut
|
||||
|
||||
sub store {
|
||||
my ( $Self, $Name, $Data ) = @_;
|
||||
|
||||
return $Data; # no-op
|
||||
}
|
||||
|
||||
=begin Internal:
|
||||
|
||||
=head2 _PreProcessTemplateContent()
|
||||
|
||||
this is our template pre processor.
|
||||
|
||||
It handles some OTRS specific tags like [% InsertTemplate("TemplateName.tt") %]
|
||||
and also performs compile-time code injection (ChallengeToken element into forms).
|
||||
|
||||
Besides that, it also makes sure the template is treated as UTF8.
|
||||
|
||||
This is run at compile time. If a template is cached, this method does not have to be executed on it
|
||||
any more.
|
||||
|
||||
=cut
|
||||
|
||||
sub _PreProcessTemplateContent {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $Content = $Param{Content};
|
||||
|
||||
# Make sure the template is treated as utf8.
|
||||
$Kernel::OM->Get('Kernel::System::Encode')->EncodeInput( \$Content );
|
||||
|
||||
my $TemplateFileWithoutTT = substr( $Param{TemplateFile}, 0, -3 );
|
||||
|
||||
#
|
||||
# Include other templates into this one before parsing.
|
||||
# [% IncludeTemplate("DatePicker.tt") %]
|
||||
#
|
||||
my ( $ReplaceCounter, $Replaced );
|
||||
do {
|
||||
$Replaced = $Content =~ s{
|
||||
\[% -? \s* InsertTemplate \( \s* ['"]? (.*?) ['"]? \s* \) \s* -? %\]\n?
|
||||
}{
|
||||
# Load the template via the provider.
|
||||
# We'll use SUPER::load here because we don't need the preprocessing twice.
|
||||
my $TemplateContent = ($Self->SUPER::load($1))[0];
|
||||
$Kernel::OM->Get('Kernel::System::Encode')->EncodeInput(\$TemplateContent);
|
||||
|
||||
# Remove commented lines already here because of problems when the InsertTemplate tag
|
||||
# is not on the beginning of the line.
|
||||
$TemplateContent =~ s/^#.*\n//gm;
|
||||
$TemplateContent;
|
||||
}esmxg;
|
||||
|
||||
} until ( !$Replaced || ++$ReplaceCounter > 100 );
|
||||
|
||||
#
|
||||
# Remove DTL-style comments (lines starting with #)
|
||||
#
|
||||
$Content =~ s/^#.*\n//gm if !$ENV{TEMPLATE_KEEP_COMMENTS};
|
||||
|
||||
#
|
||||
# Insert a BLOCK call into the template.
|
||||
# [% RenderBlock('b1') %]...[% END %]
|
||||
# becomes
|
||||
# [% PerformRenderBlock('b1') %][% BLOCK 'b1' %]...[% END %]
|
||||
# This is what we need: define the block and call it from the RenderBlock macro
|
||||
# to render it based on available block data from the frontend modules.
|
||||
#
|
||||
$Content =~ s{
|
||||
\[% -? \s* RenderBlockStart \( \s* ['"]? (.*?) ['"]? \s* \) \s* -? %\]
|
||||
}{[% PerformRenderBlock("$1") %][% BLOCK "$1" -%]}smxg;
|
||||
|
||||
$Content =~ s{
|
||||
\[% -? \s* RenderBlockEnd \( \s* ['"]? (.*?) ['"]? \s* \) \s* -? %\]
|
||||
}{[% END -%]}smxg;
|
||||
|
||||
#
|
||||
# Add challenge token field to all internal forms
|
||||
#
|
||||
# (?!...) is a negative look-ahead, so "not followed by https?:"
|
||||
# \K is a new feature in perl 5.10 which excludes anything prior
|
||||
# to it from being included in the match, which means the string
|
||||
# matched before it is not being replaced away.
|
||||
# performs better than including $1 in the substitution.
|
||||
#
|
||||
$Content =~ s{
|
||||
<form[^<>]+action="(?!https?:)[^"]*"[^<>]*>\K
|
||||
}{[% IF Env("UserChallengeToken") %]<input type="hidden" name="ChallengeToken" value="[% Env("UserChallengeToken") | html %]"/>[% END %][% IF Env("SessionID") && !Env("SessionIDCookie") %]<input type="hidden" name="[% Env("SessionName") %]" value="[% Env("SessionID") | html %]"/>[% END %]}smxig;
|
||||
|
||||
return $Content;
|
||||
|
||||
}
|
||||
|
||||
=end Internal:
|
||||
|
||||
=head2 MigrateDTLtoTT()
|
||||
|
||||
translates old C<DTL> template content to L<Template::Toolkit> syntax.
|
||||
|
||||
my $TTCode = $ProviderObject->MigrateDTLtoTT( Content => $DTLCode );
|
||||
|
||||
If an error was found, this method will C<die()>, so please use eval around it.
|
||||
|
||||
=cut
|
||||
|
||||
sub MigrateDTLtoTT {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $Content = $Param{Content};
|
||||
|
||||
my $ID = "[a-zA-Z0-9:_\-]+";
|
||||
|
||||
my $SafeArrrayAccess = sub {
|
||||
my $ID = shift;
|
||||
if ( $ID !~ m{^[a-zA-Z0-9_]+$}xms ) {
|
||||
return "item(\"$ID\")";
|
||||
}
|
||||
return $ID;
|
||||
};
|
||||
|
||||
# $Quote $Config
|
||||
$Content =~ s{\$Quote[{]"\$Config[{]"($ID)"}"}}{[% Config("$1") | html %]}smxg;
|
||||
|
||||
# $Quote $Env
|
||||
$Content =~ s{\$Quote[{]"\$Env[{]"($ID)"}"}}{[% Env("$1") | html %]}smxg;
|
||||
|
||||
# $Quote $Data
|
||||
$Content =~ s{
|
||||
\$Quote[{]"\$Data[{]"($ID)"}"}
|
||||
}
|
||||
{
|
||||
'[% Data.' . $SafeArrrayAccess->($1) . ' | html %]'
|
||||
}esmxg;
|
||||
|
||||
# $Quote with length
|
||||
$Content =~ s{
|
||||
\$Quote[{]"\$Data[{]"($ID)"}",\s*"(\d+)"}
|
||||
}
|
||||
{
|
||||
'[% Data.' . $SafeArrrayAccess->($1) . " | truncate($2) | html %]"
|
||||
}esmxg;
|
||||
|
||||
# $Quote with dynamic length
|
||||
$Content =~ s{
|
||||
\$Quote[{]"\$Data[{]"($ID)"}",\s*"\$Q?Data[{]"($ID)"}"}
|
||||
}
|
||||
{
|
||||
'[% Data.' . $SafeArrrayAccess->($1) . ' | truncate(Data.' . $SafeArrrayAccess->($2) . ') | html %]'
|
||||
}esmxg;
|
||||
|
||||
# $Quote with translated text and fixed length
|
||||
$Content =~ s{
|
||||
\$Quote[{]"\$Text[{]"\$Data[{]"($ID)"}"}",\s*"(\d+)"}
|
||||
}
|
||||
{
|
||||
'[% Data.' . $SafeArrrayAccess->($1) . " | Translate | truncate($2) | html %]"
|
||||
}esmxg;
|
||||
|
||||
# $Quote with translated text and dynamic length
|
||||
$Content =~ s{
|
||||
\$Quote[{]"\$Text[{]"\$Data[{]"($ID)"}"}",\s*"\$Q?Data[{]"($ID)"}"}
|
||||
}
|
||||
{
|
||||
'[% Data.' . $SafeArrrayAccess->($1) . ' | Translate | truncate(Data.' . $SafeArrrayAccess->($2) . ') | html %]'
|
||||
}esmxg;
|
||||
|
||||
my $MigrateTextTag = sub {
|
||||
my %Param = @_;
|
||||
my $Mode = $Param{Mode}; # HTML or JSON
|
||||
my $Text = $Param{Text}; # The translated text
|
||||
my $Dot = $Param{Dot}; # Closing dot, sometimes outside of the Tag
|
||||
my $ParamString = $Param{Parameters}; # Parameters to interpolate
|
||||
|
||||
my $Result = '[% ';
|
||||
|
||||
# Text contains a tag
|
||||
if ( $Text =~ m{\$TimeLong[{]"\$Q?Data[{]"($ID)"}"}}smx ) {
|
||||
$Result .= "Translate(Localize(Data." . $SafeArrrayAccess->($1) . ", \"TimeLong\")";
|
||||
}
|
||||
elsif ( $Text =~ m{\$TimeShort[{]"\$Q?Data[{]"($ID)"}"}}smx ) {
|
||||
$Result .= "Translate(Localize(Data." . $SafeArrrayAccess->($1) . ", \"TimeShort\")";
|
||||
}
|
||||
elsif ( $Text =~ m{\$Date[{]"\$Q?Data[{]"($ID)"}"}}smx ) {
|
||||
$Result .= "Translate(Localize(Data." . $SafeArrrayAccess->($1) . ", \"Date\")";
|
||||
}
|
||||
elsif ( $Text =~ m{\$Q?Data[{]"($ID)"}}smx ) {
|
||||
$Result .= "Translate(Data." . $SafeArrrayAccess->($1) . "";
|
||||
}
|
||||
elsif ( $Text =~ m{\$Config[{]"($ID)"}}smx ) {
|
||||
$Result .= "Translate(Config(\"$1\")";
|
||||
}
|
||||
elsif ( $Text =~ m{\$Q?Env[{]"($ID)"}}smx ) {
|
||||
$Result .= "Translate(Env(\"$1\")";
|
||||
}
|
||||
|
||||
# Plain text
|
||||
else {
|
||||
$Text =~ s{"}{\\"}smxg; # Escape " signs
|
||||
if ( $Param{Dot} ) {
|
||||
$Text .= $Param{Dot};
|
||||
}
|
||||
$Result .= "Translate(\"$Text\"";
|
||||
}
|
||||
|
||||
my @Parameters = split m{,\s*}, $ParamString;
|
||||
|
||||
PARAMETER:
|
||||
for my $Parameter (@Parameters) {
|
||||
next PARAMETER if ( !$Parameter );
|
||||
if ( $Parameter =~ m{\$TimeLong[{]"\$Q?Data[{]"($ID)"}"}}smx ) {
|
||||
$Result .= ", Localize(Data.$1, \"TimeLong\")";
|
||||
}
|
||||
elsif ( $Parameter =~ m{\$TimeShort[{]"\$Q?Data[{]"($ID)"}"}}smx ) {
|
||||
$Result .= ", Localize(Data.$1, \"TimeShort\")";
|
||||
}
|
||||
elsif ( $Parameter =~ m{\$Date[{]"\$Q?Data[{]"($ID)"}"}}smx ) {
|
||||
$Result .= ", Localize(Data.$1, \"Date\")";
|
||||
}
|
||||
elsif ( $Parameter =~ m{\$Q?Data[{]"($ID)"}}smx ) {
|
||||
$Result .= ", Data.$1";
|
||||
}
|
||||
elsif ( $Parameter =~ m{\$Config[{]"($ID)"}}smx ) {
|
||||
$Result .= ", Config(\"$1\")";
|
||||
}
|
||||
elsif ( $Parameter =~ m{\$Q?Env[{]"($ID)"}}smx ) {
|
||||
$Result .= ", Env(\"$1\")";
|
||||
}
|
||||
else {
|
||||
$Parameter =~ s{^"|"$}{}smxg; # Remove enclosing ""
|
||||
$Parameter =~ s{"}{\\"}smxg; # Escape " signs in the string
|
||||
$Result .= ", \"$Parameter\"";
|
||||
}
|
||||
}
|
||||
|
||||
if ( $Mode eq 'JSON' ) {
|
||||
$Result .= ') | JSON %]';
|
||||
}
|
||||
else {
|
||||
$Result .= ') | html %]';
|
||||
}
|
||||
|
||||
return $Result;
|
||||
};
|
||||
|
||||
my $TextOrData = "";
|
||||
|
||||
# $Text
|
||||
$Content =~ s{
|
||||
\$Text[{]
|
||||
["']
|
||||
(
|
||||
[^\$]+?
|
||||
|\$Q?Data[{]\"$ID\"}
|
||||
|\$Config[{]\"$ID\"}
|
||||
|\$Q?Env[{]\"$ID\"}
|
||||
|\$TimeLong[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$TimeShort[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$Date[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
)
|
||||
["']
|
||||
((?:
|
||||
,\s*["']
|
||||
(?:
|
||||
[^\$]+?
|
||||
|\$Q?Data[{]\"$ID\"}
|
||||
|\$Config[{]\"$ID\"}
|
||||
|\$Q?Env[{]\"$ID\"}
|
||||
|\$TimeLong[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$TimeShort[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$Date[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
)
|
||||
["'])*)
|
||||
}
|
||||
}
|
||||
{
|
||||
$MigrateTextTag->( Mode => 'HTML', Text => $1, Parameters => $2);
|
||||
}esmxg;
|
||||
|
||||
# drop empty $Text
|
||||
$Content =~ s{ \$Text [{] "" [}] }{}xmsg;
|
||||
|
||||
# $JSText
|
||||
$Content =~ s{
|
||||
["']\$JSText[{]
|
||||
["']
|
||||
(
|
||||
[^\$]+?
|
||||
|\$Q?Data[{]\"$ID\"}
|
||||
|\$Config[{]\"$ID\"}
|
||||
|\$Q?Env[{]\"$ID\"}
|
||||
|\$TimeLong[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$TimeShort[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$Date[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
)
|
||||
["']
|
||||
((?:
|
||||
,\s*["']
|
||||
(?:
|
||||
[^\$]+?
|
||||
|\$Q?Data[{]\"$ID\"}
|
||||
|\$Config[{]\"$ID\"}
|
||||
|\$Q?Env[{]\"$ID\"}
|
||||
|\$TimeLong[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$TimeShort[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
|\$Date[{]\"\$Q?Data[{]\"$ID\"}\"}
|
||||
)
|
||||
["'])*)
|
||||
}
|
||||
(.?)["']
|
||||
}
|
||||
{
|
||||
$MigrateTextTag->( Mode => 'JSON', Text => $1, Parameters => $2, Dot => $3);
|
||||
}esmxg;
|
||||
|
||||
# $TimeLong
|
||||
$Content =~ s{\$TimeLong[{]"\$Q?Data[{]"($ID)"}"}}{[% Data.$1 | Localize("TimeLong") %]}smxg;
|
||||
|
||||
# $TimeShort
|
||||
$Content =~ s{\$TimeShort[{]"\$Q?Data[{]"($ID)"}"}}{[% Data.$1 | Localize("TimeShort") %]}smxg;
|
||||
|
||||
# $Date
|
||||
$Content =~ s{\$Date[{]"\$Q?Data[{]"($ID)"}"}}{[% Data.$1 | Localize("Date") %]}smxg;
|
||||
|
||||
# $QData with length
|
||||
$Content =~ s{
|
||||
\$QData[{]"($ID)",\s*"(\d+)"}
|
||||
}
|
||||
{
|
||||
"[% Data." . $SafeArrrayAccess->($1) . " | truncate($2) | html %]"
|
||||
}esmxg;
|
||||
|
||||
# simple $QData
|
||||
$Content =~ s{
|
||||
\$QData[{]"($ID)"}
|
||||
}
|
||||
{
|
||||
"[% Data." . $SafeArrrayAccess->($1) . " | html %]"
|
||||
}esmxg;
|
||||
|
||||
# $LQData
|
||||
$Content =~ s{
|
||||
\$LQData[{]"($ID)"}
|
||||
}
|
||||
{
|
||||
"[% Data." . $SafeArrrayAccess->($1) . " | uri %]"
|
||||
}esmxg;
|
||||
|
||||
# simple $Data
|
||||
$Content =~ s{
|
||||
\$Data[{]"($ID)"}
|
||||
}
|
||||
{
|
||||
"[% Data." . $SafeArrrayAccess->($1) . " %]"
|
||||
}esmxg;
|
||||
|
||||
# $Config
|
||||
$Content =~ s{\$Config[{]"($ID)"}}{[% Config("$1") %]}smxg;
|
||||
|
||||
# $Env
|
||||
$Content =~ s{\$Env[{]"($ID)"}}{[% Env("$1") %]}smxg;
|
||||
|
||||
# $QEnv
|
||||
$Content =~ s{\$QEnv[{]"($ID)"}}{[% Env("$1") | html %]}smxg;
|
||||
|
||||
# dtl:block
|
||||
my %BlockSeen;
|
||||
$Content =~ s{<!--\s*dtl:block:($ID)\s*-->}{
|
||||
if ($BlockSeen{$1}++ % 2) {
|
||||
"[% RenderBlockEnd(\"$1\") %]";
|
||||
}
|
||||
else {
|
||||
"[% RenderBlockStart(\"$1\") %]";
|
||||
}
|
||||
}esmxg;
|
||||
|
||||
# dtl:js_on_document_complete
|
||||
$Content =~ s{
|
||||
<!--\s*dtl:js_on_document_complete\s*-->(.*?)<!--\s*dtl:js_on_document_complete\s*-->
|
||||
}
|
||||
{
|
||||
"[% WRAPPER JSOnDocumentComplete %]${1}[% END %]";
|
||||
}esmxg;
|
||||
|
||||
# dtl:js_on_document_complete_insert
|
||||
$Content
|
||||
=~ s{<!--\s*dtl:js_on_document_complete_placeholder\s*-->}{[% PROCESS JSOnDocumentCompleteInsert %]}smxg;
|
||||
|
||||
# $Include
|
||||
$Content =~ s{\$Include[{]"($ID)"}}{[% InsertTemplate("$1.tt") %]}smxg;
|
||||
|
||||
my ( $Counter, $ErrorMessage );
|
||||
LINE:
|
||||
for my $Line ( split /\n/, $Content ) {
|
||||
$Counter++;
|
||||
|
||||
# Make sure there are no more DTL tags present in the code.
|
||||
if ( $Line =~ m{\$(?:L?Q?Data|Quote|Config|Q?Env|Time|Date|Text|JSText|Include)\{}xms ) {
|
||||
$ErrorMessage .= "Line $Counter: $Line\n";
|
||||
}
|
||||
}
|
||||
|
||||
die $ErrorMessage if $ErrorMessage;
|
||||
|
||||
return $Content;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=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
|
||||
Reference in New Issue
Block a user