Templating Mechanism

Internally, OTRS uses a templating mechanism to dynamically generate its HTML pages (and other content), while keeping the program logic (Perl) and the presentation (HTML) separate. Typically, a frontend module will use an own template file, pass some data to it and return the rendered result to the user.

The template files are located at $OTRS_HOME/Kernel/Output/HTML/Standard/*.tt.

OTRS relies on the Template::Toolkit rendering engine. The full Template::Toolkit syntax can be used in OTRS templates. This section describes some example use cases and OTRS extensions to the Template::Toolkit syntax.

Template Commands

Inserting Dynamic Data

In templates, dynamic data must be inserted, quoted etc. This section lists the relevant commands to do that.

[% Data.Name %]

If data parameters are given to the templates by the application module, these data can be output to the template. [% Data.Name %] is the most simple, but also most dangerous one. It will insert the data parameter whose name is Name into the template as it is, without further processing.

Warning

Because of the missing HTML quoting, this can result in security problems. Never output data that was input by a user without quoting in HTML context. The user could - for example - just insert a <script> tag, and it would be output on the HTML page generated by OTRS.

Whenever possible, use [% Data.Name | html %] (in HTML) or [% Data.Name | uri %] (in Links) instead.

Example: Whenever we generate HTML in the application, we need to output it to the template without HTML quoting, like <select> elements, which are generated by the function Layout::BuildSelection() in OTRS.

<label for="Dropdown">Example Dropdown</label>
[% Data.DropdownString"]

If you have data entries with complex names containing special characters, you cannot use the dot (.) notation to access this data. Use item() instead: [% Data.item('Complex-name') %].

[% Data.Name | html %]

This command has the same function as the previous one, but it performs HTML quoting on the data as it is inserted to the template.

The name of the author is [% Data.Name | html %].

It’s also possible specify a maximum length for the value. If, for example, you just want to show 8 characters of a variable (result will be SomeName[…]), use the following:

The first 20 characters of the author's name: [% Data.Name | truncate(20) | html %].

[% Data.Name | uri %]

This command performs URL encoding on the data as it is inserted to the template. This should be used to output single parameter names or values of URLs, to prevent security problems. It cannot be used for complete URLs because it will also mask =, for example.

<a href="[% Env("Baselink") %];Location=[% Data.File | uri %]">[% Data.File | truncate(110) | html %]</a>

[% Data.Name | JSON %]

This command outputs a string or another data structure as a JavaScript JSON string.

var Text = [% Data.Text | JSON %];

Please note that the filter notation will only work for simple strings. To output complex data as JSON, please use it as a function:

var TreeData = [% JSON(Data.TreeData) %];

[% Env() %]

Inserts environment variables provided by the LayoutObject. Some examples:

The current user name is: [% Env("UserFullname") %]

Some other common predefined variables are:

  • [% Env("Action") %]: the current action
  • [% Env("Baselink") %]: the baselink –> index.pl?SessionID=…
  • [% Env("CGIHandle") %]: the current CGI handle e. g. index.pl
  • [% Env("SessionID") %]: the current session id
  • [% Env("Time") %]: the current time e. g. Thu Dec 27 16:00:55 2001
  • [% Env("UserFullname") %]: e. g. Dirk Seiffert
  • [% Env("UserIsGroup[admin]") %]: Yes
  • [% Env("UserIsGroup[users]") %]: Yes –> user groups (useful for own links)
  • [% Env("UserLogin") %]: e. g. mgg@x11.org

Warning

Because of the missing HTML quoting, this can result in security problems. Never output data that was input by a user without quoting in HTML context. The user could - for example - just insert a <script> tag, and it would be output on the HTML page generated by OTRS.

Don’t forget to add the | html filter where appropriate.

[% Config() %]

Inserts config variables into the template. Let’s see an example Kernel/Config.pm:

[Kernel/Config.pm]
    # FQDN
    # (Full qualified domain name of your system.)
    $Self->{FQDN} = 'otrs.example.com';
    # AdminEmail
    # (Email of the system admin.)
    $Self->{AdminEmail} = 'admin@example.com';
[...]

To output values from it in the template, use:

The hostname is '$Config{"FQDN"}'
The admin email address is '[% Config("AdminEmail") %]'

Warning

Because of the missing HTML quoting, this can result in security problems.

Don’t forget to add the | html filter where appropriate.

Localization Commands

[% Translate() %]

Translates a string into the current user’s selected language. If no translation is found, the original string will be used.

Translate this text: [% Translate("Help") | html %]

You can also translate dynamic data by using Translate as a filter:

Translate data from the application: [% Data.Type | Translate | html %]

You can also specify one or more parameters (%s) inside of the string which should be replaced with dynamic data:

Translate this text and insert the given data: [% Translate("Change %s settings", Data.Type) | html %]

Strings in JavaScript can be translated and processed with the JSON filter.

var Text = [% Translate("Change %s settings", Data.Type) | JSON %];

[% Localize() %]

Outputs data according to the current language/locale.

In different cultural areas, different convention for date and time formatting are used. For example, what is the 01.02.2010 in Germany, would be 02/01/2010 in the USA. [% Localize() %] abstracts this away from the templates. Let’s see an example:

[% Data.CreateTime ǀ Localize("TimeLong") %]
# Result for US English locale:
06/09/2010 15:45:41

First, the data is inserted from the application module with Data. Here always an ISO UTC timestamp (2010-06-09 15:45:41) must be passed as data to [% Localize() %]. Then it will be output it according to the date/time definition of the current locale.

The data passed to [% Localize() %] must be UTC. If a time zone offset is specified for the current agent, it will be applied to the UTC timestamp before the output is generated.

There are different possible date/time output formats: TimeLong (full date/time), TimeShort (no seconds) and Date (no time).

[% Data.CreateTime ǀ Localize("TimeLong") %]
# Result for US English locale:
06/09/2010 15:45:41

[% Data.CreateTime ǀ Localize("TimeShort") %]
# Result for US English locale:
06/09/2010 15:45

[% Data.CreateTime ǀ Localize("Date") %]
# Result for US English locale:
06/09/2010

Also the output of human readable file sizes is available as an option Localize('Filesize') (just pass the raw file size in bytes).

[% Data.Filesize ǀ Localize("Filesize") %]
# Result for US English locale:
23 MB

[% ReplacePlaceholders() %]

Replaces placeholders (%s) in strings with passed parameters.

In certain cases, you might want to insert HTML code in translations, instead of placeholders. On the other hand, you also need to take care of sanitization, since translated strings should not be trusted as-is. For this, you can first translate the string, pass it through the HTML filter and finally replace placeholders with static (safe) HTML code.

[% Translate("This is %s.") | html | ReplacePlaceholders('<strong>bold text</strong>') %]

Number of parameters to ReplacePlaceholders() filter should match number of placeholders in the original string.

You can also use [% ReplacePlaceholders() %] in function format, in case you are not translating anything. In this case, first parameter is the target string, and any found placeholders in it are substituted with subsequent parameters.

[% ReplacePlaceholders("This string has both %s and %s.", '<strong>bold text</strong>, '<em>italic text</em>') %]

Template Processing Commands

Comment

Lines starting with a # at the beginning of will not be shown in the html output. This can be used both for commenting the Template code or for disabling parts of it.

# this section is temporarily disabled
# <div class="AsBlock">
#     <a href="...">link</a>
# </div>

[% RenderBlockStart %] / [% RenderBlockEnd %]

Warning

Please note that the blocks commands were added to provide better backwards compatibility to the old DTL system. They might be deprecated in a future version of OTRS and removed later. We advise you to develop any new code without using the blocks commands. You can use standard Template::Toolkit syntax like IF/ELSE, LOOP and other helpful things for conditional template output.

With this command, one can specify parts of a template file as a block. This block needs to be explicitly filled with a function call from the application, to be present in the generated output. The application can call the block 0 (it will not be present in the output), 1 or more times (each with possibly a different set of data parameters passed to the template).

One common use case is the filling of a table with dynamic data:

<table class="DataTable">
    <thead>
        <tr>
            <th>[% Translate("Name") | html %]</th>
            <th>[% Translate("Type") | html %]</th>
            <th>[% Translate("Comment") | html %]</th>
            <th>[% Translate("Validity") | html %]</th>
            <th>[% Translate("Changed") | html %]</th>
            <th>[% Translate("Created") | html %]</th>
        </tr>
    </thead>
    <tbody>
[% RenderBlockStart("NoDataFoundMsg") %]
        <tr>
            <td colspan="6">
                [% Translate("No data found.") | html %]
            </td>
        </tr>
[% RenderBlockEnd("NoDataFoundMsg") %]
[% RenderBlockStart("OverviewResultRow") %]
        <tr>
            <td><a class="AsBlock" href="[% Env("Baselink") %]Action=[% Env("Action") %];Subaction=Change;ID=[% Data.ID | uri %]">[% Data.Name | html %]</a></td>
            <td>[% Translate(Data.TypeName) | html %]</td>
            <td title="[% Data.Comment | html %]">[% Data.Comment | truncate(20) | html %]</td>
            <td>[% Translate(Data.Valid) | html %]</td>
            <td>[% Data.ChangeTime | Localize("TimeShort") %]</td>
            <td>[% Data.CreateTime | Localize("TimeShort") %]</td>
        </tr>
[% RenderBlockEnd("OverviewResultRow") %]
    </tbody>
</table>

The surrounding table with the header is always generated. If no data was found, the block NoDataFoundMsg is called once, resulting in a table with one data row with the message No data found.

If data was found, for each row there is one function call made for the block OverViewResultRow (each time passing in the data for this particular row), resulting in a table with as many data rows as results were found.

Let’s look at how the blocks are called from the application module:

my %List = $Kernel::OM->Get('Kernel::System::State)->StateList(
    UserID => 1,
    Valid  => 0,
);

# if there are any states, they are shown
if (%List) {

    # get valid list
    my %ValidList = $Kernel::OM->Get('Kernel::System::Valid')->ValidList();
    for my $ListKey ( sort { $List{$a} cmp $List{$b} } keys %List ) {

        my %Data = $Kernel::OM->Get('Kernel::System::State)->StateGet( ID => $ListKey );
        $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Block(
            Name => 'OverviewResultRow',
            Data => {
                Valid => $ValidList{ $Data{ValidID} },
                %Data,
            },
        );
    }
}

# otherwise a no data found msg is displayed
else {
    $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Block(
        Name => 'NoDataFoundMsg',
        Data => {},
    );
}

Note how the blocks have both their name and an optional set of data passed in as separate parameters to the block function call. Data inserting commands inside a block always need the data provided to the block function call of this block, not the general template rendering call.

For more information, see: http://doc.otrs.com/doc/

[% WRAPPER JSOnDocumentComplete %]...[% END %]

Marks JavaScript code which should be executed after all CSS, JavaScript and other external content has been loaded and the basic JavaScript initialization was finished. Again, let’s look at an example:

<form action="[% Env("CGIHandle") %]" method="post" enctype="multipart/form-data" name="MoveTicketToQueue" class="Validate PreventMultipleSubmits" id="MoveTicketToQueue">
    <input type="hidden" name="Action"       value="[% Env("Action") %]"/>
    <input type="hidden" name="Subaction"    value="MoveTicket"/>

    ...

    <div class="Content">
        <fieldset class="TableLike FixedLabel">
            <label class="Mandatory" for="DestQueueID"><span class="Marker">*</span> [% Translate("New Queue") | html %]:</label>
            <div class="Field">
                [% Data.MoveQueuesStrg %]
                <div id="DestQueueIDError" class="TooltipErrorMessage" ><p>[% Translate("This field is required.") | html %]</p></div>
                <div id="DestQueueIDServerError" class="TooltipErrorMessage"><p>[% Translate("This field is required.") | html %]</p></div>
[% WRAPPER JSOnDocumentComplete %]
<script type="text/javascript">
    $('#DestQueueID').bind('change', function (Event) {
        $('#NoSubmit').val('1');
        Core.AJAX.FormUpdate($('#MoveTicketToQueue'), 'AJAXUpdate', 'DestQueueID', ['NewUserID', 'OldUserID', 'NewStateID', 'NewPriorityID' [% Data.DynamicFieldNamesStrg %]]);
    });
</script>
[% END %]
                    </div>
                    <div class="Clear"></div>

This snippet creates a small form and puts an onchange handler on the <select> element which triggers an AJAX based form update.

Why is it necessary to enclose the JavaScript code in [% WRAPPER JSOnDocumentComplete %]...[% END %]? JavaScript loading was moved to the footer part of the page for performance reasons. This means that within the <body> of the page, no JavaScript libraries are loaded yet. With [% WRAPPER JSOnDocumentComplete %]...[% END %] you can make sure that this JavaScript is moved to a part of the final HTML document, where it will be executed only after the entire external JavaScript and CSS content has been successfully loaded and initialized.

Inside the [% WRAPPER JSOnDocumentComplete %]...[% END %] block, you can use <script> tags to enclose your JavaScript code, but you do not have to do so. It may be beneficial because it will enable correct syntax highlighting in IDEs which support it.

Using a Template File

Ok, but how to actually process a template file and generate the result? This is really simple:

# render AdminState.tt
$Output .= $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Output(
    TemplateFile => 'AdminState',
    Data         => \%Param,
);

In the frontend modules, the Output() function of Kernel::Output::HTML::Layout is called (after all the needed blocks have been called in this template) to generate the final output. An optional set of data parameters is passed to the template, for all data inserting commands which are not inside of a block.