1
# -*- Mode: perl; indent-tabs-mode: nil -*-
3
# The contents of this file are subject to the Mozilla Public
4
# License Version 1.1 (the "License"); you may not use this file
5
# except in compliance with the License. You may obtain a copy of
6
# the License at http://www.mozilla.org/MPL/
8
# Software distributed under the License is distributed on an "AS
9
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
# implied. See the License for the specific language governing
11
# rights and limitations under the License.
13
# The Original Code is the Bugzilla Bug Tracking System.
15
# The Initial Developer of the Original Code is Netscape Communications
16
# Corporation. Portions created by Netscape are
17
# Copyright (C) 1998 Netscape Communications Corporation. All
20
# Contributor(s): Terry Weissman <terry@mozilla.org>
21
# Dan Mosedale <dmose@mozilla.org>
22
# Jacob Steenhagen <jake@bugzilla.org>
23
# Bradley Baetz <bbaetz@student.usyd.edu.au>
24
# Christopher Aillon <christopher@aillon.com>
25
# Tobias Burnus <burnus@net-b.de>
26
# Myk Melez <myk@mozilla.org>
27
# Max Kanat-Alexander <mkanat@bugzilla.org>
28
# Frédéric Buclin <LpSolit@gmail.com>
29
# Greg Hendricks <ghendricks@novell.com>
30
# David D. Kilzer <ddkilzer@kilzer.net>
33
package Bugzilla::Template;
37
use Bugzilla::Constants;
38
use Bugzilla::Install::Requirements;
46
# for time2str - replace by TT Date plugin??
48
use File::Basename qw(dirname);
50
use File::Path qw(rmtree mkpath);
54
use base qw(Template);
56
# Convert the constants in the Bugzilla::Constants module into a hash we can
57
# pass to the template object for reflection into its "constants" namespace
58
# (which is like its "variables" namespace, but for constants). To do so, we
59
# traverse the arrays of exported and exportable symbols, pulling out functions
60
# (which is how Perl implements constants) and ignoring the rest (which, if
61
# Constants.pm exports only constants, as it should, will be nothing else).
64
foreach my $constant (@Bugzilla::Constants::EXPORT,
65
@Bugzilla::Constants::EXPORT_OK)
67
if (defined Bugzilla::Constants->$constant) {
68
# Constants can be lists, and we can't know whether we're
69
# getting a scalar or a list in advance, since they come to us
70
# as the return value of a function call, so we have to
71
# retrieve them all in list context into anonymous arrays,
72
# then extract the scalar ones (i.e. the ones whose arrays
73
# contain a single element) from their arrays.
74
$constants{$constant} = [&{$Bugzilla::Constants::{$constant}}];
75
if (scalar(@{$constants{$constant}}) == 1) {
76
$constants{$constant} = @{$constants{$constant}}[0];
83
# Make an ordered list out of a HTTP Accept-Language header see RFC 2616, 14.4
84
# We ignore '*' and <language-range>;q=0
85
# For languages with the same priority q the order remains unchanged.
86
sub sortAcceptLanguage {
87
sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} }
88
my $accept_language = $_[0];
91
$accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g;
94
foreach(split /,/, $accept_language) {
95
if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) {
98
$qvalue = 1 if not defined $qvalue;
100
$qvalue = 1 if $qvalue > 1;
101
push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang});
105
return map($_->{'language'}, (sort sortQvalue @qlanguages));
108
# Returns the path to the templates based on the Accept-Language
109
# settings of the user and of the available languages
110
# If no Accept-Language is present it uses the defined default
111
# Templates may also be found in the extensions/ tree
112
sub getTemplateIncludePath {
113
my $lang = Bugzilla->request_cache->{'language'} || "";
114
# Return cached value if available
116
my $include_path = Bugzilla->request_cache->{"template_include_path_$lang"};
117
return $include_path if $include_path;
119
my $templatedir = bz_locations()->{'templatedir'};
120
my $project = bz_locations()->{'project'};
122
my $languages = trim(Bugzilla->params->{'languages'});
123
if (not ($languages =~ /,/)) {
126
"$templatedir/$languages/$project",
127
"$templatedir/$languages/custom",
128
"$templatedir/$languages/default"
132
"$templatedir/$languages/custom",
133
"$templatedir/$languages/default"
137
my @languages = sortAcceptLanguage($languages);
138
# If $lang is specified, only consider this language.
139
my @accept_language = ($lang) || sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "");
141
foreach my $language (@accept_language) {
142
# Per RFC 1766 and RFC 2616 any language tag matches also its
143
# primary tag. That is 'en' (accept language) matches 'en-us',
144
# 'en-uk' etc. but not the otherway round. (This is unfortunately
145
# not very clearly stated in those RFC; see comment just over 14.5
146
# in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
147
if(my @found = grep /^\Q$language\E(-.+)?$/i, @languages) {
148
push (@usedlanguages, @found);
151
push(@usedlanguages, Bugzilla->params->{'defaultlanguage'});
155
"$templatedir/$_/$project",
156
"$templatedir/$_/custom",
157
"$templatedir/$_/default"
164
"$templatedir/$_/custom",
165
"$templatedir/$_/default"
171
# add in extension template directories:
172
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
173
foreach my $extension (@extensions) {
174
trick_taint($extension); # since this comes right from the filesystem
175
# we have bigger issues if it is insecure
178
$extension."/template/".$_),
182
# remove duplicates since they keep popping up:
184
foreach my $dir (@$include_path) {
185
push(@dirs, $dir) unless grep ($dir eq $_, @dirs);
187
Bugzilla->request_cache->{"template_include_path_$lang"} = \@dirs;
189
return Bugzilla->request_cache->{"template_include_path_$lang"};
195
($vars->{'title'}, $vars->{'h1'}, $vars->{'h2'}) = (@_);
197
$self->process("global/header.html.tmpl", $vars)
198
|| ThrowTemplateError($self->error());
199
$vars->{'header_done'} = 1;
204
$self->process("global/footer.html.tmpl")
205
|| ThrowTemplateError($self->error());
210
my ($template, $format, $ctype) = @_;
215
# Security - allow letters and a hyphen only
216
$ctype =~ s/[^a-zA-Z\-]//g;
217
$format =~ s/[^a-zA-Z\-]//g;
219
trick_taint($format);
221
$template .= ($format ? "-$format" : "");
222
$template .= ".$ctype.tmpl";
224
# Now check that the template actually exists. We only want to check
225
# if the template exists; any other errors (eg parse errors) will
226
# end up being detected later.
228
$self->context->template($template);
230
# This parsing may seem fragile, but it's OK:
231
# http://lists.template-toolkit.org/pipermail/templates/2003-March/004370.html
232
# Even if it is wrong, any sort of error is going to cause a failure
233
# eventually, so the only issue would be an incorrect error message
234
if ($@ && $@->info =~ /: not found$/) {
235
ThrowUserError('format_not_found', {'format' => $format,
239
# Else, just return the info
242
'template' => $template,
243
'extension' => $ctype,
244
'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
248
# This routine quoteUrls contains inspirations from the HTML::FromText CPAN
249
# module by Gareth Rees <garethr@cre.canon.co.uk>. It has been heavily hacked,
250
# all that is really recognizable from the original is bits of the regular
252
# This has been rewritten to be faster, mainly by substituting 'as we go'.
253
# If you want to modify this routine, read the comments carefully
256
my ($text, $curr_bugid) = (@_);
257
return $text unless $text;
259
# We use /g for speed, but uris can have other things inside them
260
# (http://foo/bug#3 for example). Filtering that out filters valid
261
# bug refs out, so we have to do replacements.
262
# mailto can't contain space or #, so we don't have to bother for that
263
# Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
264
# \0 is used because it's unlikely to occur in the text, so the cost of
265
# doing this should be very small
266
# Also, \0 won't appear in the value_quote'd bug title, so we don't have
267
# to worry about bogus substitutions from there
269
# escape the 2nd escape char we're using
271
$text =~ s/\0/$chr1\0/g;
273
# However, note that adding the title (for buglinks) can affect things
274
# In particular, attachment matches go before bug titles, so that titles
275
# with 'attachment 1' don't double match.
276
# Dupe checks go afterwards, because that uses ^ and \Z, which won't occur
277
# if it was substituted as a bug title (since that always involve leading
280
# Because of entities, it's easier (and quicker) to do this before escaping
286
# Provide tooltips for full bug links (Bug 74355)
287
my $urlbase_re = '(' . join('|',
288
map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
289
Bugzilla->params->{'sslbase'})) . ')';
290
$text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
291
~($things[$count++] = get_bug_link($3, $1, $5)) &&
292
("\0\0" . ($count-1) . "\0\0")
295
# non-mailto protocols
296
my $safe_protocols = join('|', SAFE_PROTOCOLS);
297
my $protocol_re = qr/($safe_protocols)/i;
299
$text =~ s~\b(${protocol_re}: # The protocol:
300
[^\s<>\"]+ # Any non-whitespace
301
[\w\/]) # so that we end in \w or /
302
~($tmp = html_quote($1)) &&
303
($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
304
("\0\0" . ($count-1) . "\0\0")
307
# We have to quote now, otherwise the html itself is escaped
308
# THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
310
$text = html_quote($text);
313
$text =~ s~^(>.+)$~<span class="quote">$1</span >~mg;
314
$text =~ s~</span >\n<span class="quote">~\n~g;
317
# Use |<nothing> so that $1 is defined regardless
318
$text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
319
~<a href=\"mailto:$2\">$1$2</a>~igx;
321
# attachment links - handle both cases separately for simplicity
322
$text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
323
~($things[$count++] = get_attachment_link($2, $1)) &&
324
("\0\0" . ($count-1) . "\0\0")
327
$text =~ s~\b(attachment\s*\#?\s*(\d+))
328
~($things[$count++] = get_attachment_link($2, $1)) &&
329
("\0\0" . ($count-1) . "\0\0")
332
# Current bug ID this comment belongs to
333
my $current_bugurl = $curr_bugid ? "show_bug.cgi?id=$curr_bugid" : "";
335
# This handles bug a, comment b type stuff. Because we're using /g
336
# we have to do this in one pattern, and so this is semi-messy.
337
# Also, we can't use $bug_re?$comment_re? because that will match the
339
my $bug_word = get_text('term', { term => 'bug' });
340
my $bug_re = qr/\Q$bug_word\E\s*\#?\s*(\d+)/i;
341
my $comment_re = qr/comment\s*\#?\s*(\d+)/i;
342
$text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
343
~ # We have several choices. $1 here is the link, and $2-4 are set
344
# depending on which part matched
345
(defined($2) ? get_bug_link($2,$1,$3) :
346
"<a href=\"$current_bugurl#c$4\">$1</a>")
349
# Old duplicate markers. These don't use $bug_word because they are old
350
# and were never customizable.
351
$text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
354
~get_bug_link($1, $1)
357
# Now remove the encoding hacks
358
$text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
359
$text =~ s/$chr1\0/\0/g;
364
# Creates a link to an attachment, including its title.
365
sub get_attachment_link {
366
my ($attachid, $link_text) = @_;
367
my $dbh = Bugzilla->dbh;
369
detaint_natural($attachid)
370
|| die "get_attachment_link() called with non-integer attachment number";
372
my ($bugid, $isobsolete, $desc) =
373
$dbh->selectrow_array('SELECT bug_id, isobsolete, description
374
FROM attachments WHERE attach_id = ?',
380
if (Bugzilla->user->can_see_bug($bugid)) {
384
$className = "bz_obsolete";
386
# Prevent code injection in the title.
387
$title = value_quote($title);
389
$link_text =~ s/ \[details\]$//;
390
my $linkval = "attachment.cgi?id=$attachid";
391
# Whitespace matters here because these links are in <pre> tags.
392
return qq|<span class="$className">|
393
. qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
394
. qq| <a href="${linkval}&action=edit" title="$title">[details]</a>|
398
return qq{$link_text};
402
# Creates a link to a bug, including its title.
403
# It takes either two or three parameters:
405
# - The link text, to place between the <a>..</a>
406
# - An optional comment number, for linking to a particular
410
my ($bug_num, $link_text, $comment_num) = @_;
411
my $dbh = Bugzilla->dbh;
413
if (!defined($bug_num) || ($bug_num eq "")) {
414
return "<missing bug number>";
416
my $quote_bug_num = html_quote($bug_num);
417
detaint_natural($bug_num) || return "<invalid bug number: $quote_bug_num>";
419
my ($bug_state, $bug_res, $bug_desc) =
420
$dbh->selectrow_array('SELECT bugs.bug_status, resolution, short_desc
421
FROM bugs WHERE bugs.bug_id = ?',
425
# Initialize these variables to be "" so that we don't get warnings
426
# if we don't change them below (which is highly likely).
427
my ($pre, $title, $post) = ("", "", "");
430
if ($bug_state eq 'UNCONFIRMED') {
434
elsif (!is_open_state($bug_state)) {
435
$pre = '<span class="bz_closed">';
436
$title .= " $bug_res";
439
if (Bugzilla->user->can_see_bug($bug_num)) {
440
$title .= " - $bug_desc";
442
# Prevent code injection in the title.
443
$title = value_quote($title);
445
my $linkval = "show_bug.cgi?id=$bug_num";
446
if (defined $comment_num) {
447
$linkval .= "#c$comment_num";
449
return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
452
return qq{$link_text};
456
###############################################################################
457
# Templatization Code
459
# The Template Toolkit throws an error if a loop iterates >1000 times.
460
# We want to raise that limit.
461
# NOTE: If you change this number, you MUST RE-RUN checksetup.pl!!!
462
# If you do not re-run checksetup.pl, the change you make will not apply
463
$Template::Directive::WHILE_MAX = 1000000;
465
# Use the Toolkit Template's Stash module to add utility pseudo-methods
466
# to template variables.
469
# Add "contains***" methods to list variables that search for one or more
470
# items in a list and return boolean values representing whether or not
471
# one/all/any item(s) were found.
472
$Template::Stash::LIST_OPS->{ contains } =
474
my ($list, $item) = @_;
475
return grep($_ eq $item, @$list);
478
$Template::Stash::LIST_OPS->{ containsany } =
480
my ($list, $items) = @_;
481
foreach my $item (@$items) {
482
return 1 if grep($_ eq $item, @$list);
487
# Allow us to still get the scalar if we use the list operation ".0" on it,
488
# as we often do for defaults in query.cgi and other places.
489
$Template::Stash::SCALAR_OPS->{ 0 } =
494
# Add a "substr" method to the Template Toolkit's "scalar" object
495
# that returns a substring of a string.
496
$Template::Stash::SCALAR_OPS->{ substr } =
498
my ($scalar, $offset, $length) = @_;
499
return substr($scalar, $offset, $length);
502
# Add a "truncate" method to the Template Toolkit's "scalar" object
503
# that truncates a string to a certain length.
504
$Template::Stash::SCALAR_OPS->{ truncate } =
506
my ($string, $length, $ellipsis) = @_;
509
return $string if !$length || length($string) <= $length;
511
my $strlen = $length - length($ellipsis);
512
my $newstr = substr($string, 0, $strlen) . $ellipsis;
516
# Create the template object that processes templates and specify
517
# configuration parameters that apply to all templates.
519
###############################################################################
521
# Construct the Template object
523
# Note that all of the failure cases here can't use templateable errors,
524
# since we won't have a template to use...
530
# checksetup.pl will call us once for any template/lang directory.
531
# We need a possibility to reset the cache, so that no files from
532
# the previous language pollute the action.
533
if ($opts{'clean_cache'}) {
534
delete Bugzilla->request_cache->{template_include_path_};
537
# IMPORTANT - If you make any configuration changes here, make sure to
538
# make them in t/004.template.t and checksetup.pl.
541
# Colon-separated list of directories containing templates.
542
INCLUDE_PATH => [\&getTemplateIncludePath],
544
# Remove white-space before template directives (PRE_CHOMP) and at the
545
# beginning and end of templates and template blocks (TRIM) for better
546
# looking, more compact content. Use the plus sign at the beginning
547
# of directives to maintain white space (i.e. [%+ DIRECTIVE %]).
551
COMPILE_DIR => bz_locations()->{'datadir'} . "/template",
553
# Initialize templates (f.e. by loading plugins like Hook).
554
PRE_PROCESS => "global/initialize.none.tmpl",
556
# Functions for processing text within templates in various ways.
557
# IMPORTANT! When adding a filter here that does not override a
558
# built-in filter, please also add a stub filter to t/004template.t.
561
# Render text in required style.
565
my($context, $isinactive) = @_;
567
return $isinactive ? '<span class="bz_inactive">'.$_[0].'</span>' : $_[0];
574
my($context, $isclosed) = @_;
576
return $isclosed ? '<span class="bz_closed">'.$_[0].'</span>' : $_[0];
583
my($context, $isobsolete) = @_;
585
return $isobsolete ? '<span class="bz_obsolete">'.$_[0].'</span>' : $_[0];
590
# Returns the text with backslashes, single/double quotes,
591
# and newlines/carriage returns escaped for use in JS strings.
594
$var =~ s/([\\\'\"\/])/\\$1/g;
597
$var =~ s/\@/\\x40/g; # anti-spam for email addresses
601
# Converts data to base64
604
return encode_base64($data);
607
# HTML collapses newlines in element attributes to a single space,
608
# so form elements which may have whitespace (ie comments) need
609
# to be encoded using 
610
# See bugs 4928, 22983 and 32000 for more details
611
html_linebreak => sub {
613
$var =~ s/\r\n/\
/g;
614
$var =~ s/\n\r/\
/g;
615
$var =~ s/\r/\
/g;
616
$var =~ s/\n/\
/g;
620
# Prevents line break on hyphens and whitespaces.
623
$var =~ s/ /\ /g;
624
$var =~ s/-/\‑/g;
628
xml => \&Bugzilla::Util::xml_quote ,
630
# This filter escapes characters in a variable or value string for
631
# use in a query string. It escapes all characters NOT in the
632
# regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
633
# a full URL that may have characters that need encoding.
634
url_quote => \&Bugzilla::Util::url_quote ,
636
# This filter is similar to url_quote but used a \ instead of a %
637
# as prefix. In addition it replaces a ' ' by a '_'.
638
css_class_quote => \&Bugzilla::Util::css_class_quote ,
641
my ($context, $bug) = @_;
644
return quoteUrls($text, $bug);
651
my ($context, $bug) = @_;
654
return get_bug_link($bug, $text);
663
return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
666
# In CSV, quotes are doubled, and any value containing a quote or a
667
# comma is enclosed in quotes.
672
if ($var !~ /^-?(\d+\.)?\d*$/) {
678
# Format a filesize in bytes to a human readable value
686
'GB' => 1024 * 1024 * 1024,
690
return "$data bytes";
694
foreach $u ('GB', 'MB', 'KB') {
695
if ($data >= $units{$u}) {
696
return sprintf("%.2f %s", $data/$units{$u}, $u);
702
# Format a time for display (more info in Bugzilla::Util)
703
time => \&Bugzilla::Util::format_time,
705
# Bug 120030: Override html filter to obscure the '@' in user
707
# Bug 319331: Handle BiDi disruptions.
709
my ($var) = Template::Filters::html_filter(@_);
711
$var =~ s/\@/\@/g;
712
if (Bugzilla->params->{'utf8'}) {
713
# Remove the following characters because they're
715
# --------------------------------------------------------
716
# |Code |Name |UTF-8 representation|
717
# |------|--------------------------|--------------------|
718
# |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
719
# |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
720
# |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
721
# |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
722
# |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
723
# --------------------------------------------------------
725
# The following are characters influencing BiDi, too, but
726
# they can be spared from filtering because they don't
727
# influence more than one character right or left:
728
# --------------------------------------------------------
729
# |Code |Name |UTF-8 representation|
730
# |------|--------------------------|--------------------|
731
# |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
732
# |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
733
# --------------------------------------------------------
735
# Do the replacing in a loop so that we don't get tricked
736
# by stuff like 0xe2 0xe2 0x80 0xae 0x80 0xae.
737
while ($var =~ s/\xe2\x80(\xaa|\xab|\xac|\xad|\xae)//g) {
743
html_light => \&Bugzilla::Util::html_light_quote,
745
# iCalendar contentline filter
747
my ($context, @args) = @_;
750
my ($par) = shift @args;
753
$var =~ s/[\r\n]/ /g;
754
$var =~ s/([;\\\",])/\\$1/g;
757
$output = sprintf("%s:%s", $par, $var);
762
$output =~ s/(.{75,75})/$1\n /g;
770
# Note that using this filter is even more dangerous than
771
# using "none," and you should only use it when you're SURE
772
# the output won't be displayed directly to a web browser.
775
# Trivial HTML tag remover
776
$var =~ s/<[^>]*>//g;
777
# And this basically reverses the html filter.
778
$var =~ s/\@/@/g;
781
$var =~ s/\"/\"/g;
782
$var =~ s/\&/\&/g;
786
# Wrap a displayed comment to the appropriate length
787
wrap_comment => \&Bugzilla::Util::wrap_comment,
789
# We force filtering of every variable in key security-critical
790
# places; we have a none filter for people to use when they
791
# really, really don't want a variable to be changed.
792
none => sub { return $_[0]; } ,
795
PLUGIN_BASE => 'Bugzilla::Template::Plugin',
797
CONSTANTS => _load_constants(),
799
# Default variables for all templates
801
# Function for retrieving global parameters.
802
'Param' => sub { return Bugzilla->params->{$_[0]}; },
804
# Function to create date strings
805
'time2str' => \&Date::Format::time2str,
807
# Generic linear search function
808
'lsearch' => \&Bugzilla::Util::lsearch,
810
# Currently logged in user, if any
811
# If an sudo session is in progress, this is the user we're faking
812
'user' => sub { return Bugzilla->user; },
814
# If an sudo session is in progress, this is the user who
815
# started the session.
816
'sudoer' => sub { return Bugzilla->sudoer; },
818
# SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
819
'SendBugMail' => sub {
820
my ($id, $mailrecipients) = (@_);
821
require Bugzilla::BugMail;
822
Bugzilla::BugMail::Send($id, $mailrecipients);
825
# These don't work as normal constants.
826
DB_MODULE => \&Bugzilla::Constants::DB_MODULE,
828
\&Bugzilla::Install::Requirements::REQUIRED_MODULES,
829
OPTIONAL_MODULES => sub {
830
my @optional = @{OPTIONAL_MODULES()};
831
@optional = sort {$a->{feature} cmp $b->{feature}}
837
}) || die("Template creation failed: " . $class->error());
840
# Used as part of the two subroutines below.
841
our (%_templates_to_precompile, $_current_path);
843
sub precompile_templates {
846
# Remove the compiled templates.
847
my $datadir = bz_locations()->{'datadir'};
848
if (-e "$datadir/template") {
849
print "Removing existing compiled templates ...\n" if $output;
851
# XXX This frequently fails if the webserver made the files, because
852
# then the webserver owns the directories. We could fix that by
853
# doing a chmod/chown on all the directories here.
854
rmtree("$datadir/template");
856
# Check that the directory was really removed
857
if(-e "$datadir/template") {
859
print "The directory '$datadir/template' could not be removed.\n";
860
print "Please remove it manually and rerun checksetup.pl.\n\n";
865
print "Precompiling templates...\n" if $output;
867
my $templatedir = bz_locations()->{'templatedir'};
868
# Don't hang on templates which use the CGI library
869
eval("use CGI qw(-no_debug)");
871
my $dir_reader = new IO::Dir($templatedir) || die "$templatedir: $!";
872
my @language_dirs = grep { /^[a-z-]+$/i } $dir_reader->read;
875
foreach my $dir (@language_dirs) {
876
next if ($dir eq 'CVS');
877
-d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom"
879
local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
880
# We locally hack this parameter so that Bugzilla::Template
881
# accepts this language no matter what.
882
local Bugzilla->params->{'languages'} = "$dir,en";
883
my $template = Bugzilla::Template->create(clean_cache => 1);
885
# Precompile all the templates found in all the directories.
886
%_templates_to_precompile = ();
887
foreach my $subdir (qw(custom extension default), bz_locations()->{'project'}) {
888
next unless $subdir; # If 'project' is empty.
889
$_current_path = File::Spec->catdir($templatedir, $dir, $subdir);
890
next unless -d $_current_path;
891
# Traverse the template hierarchy.
892
find({ wanted => \&_precompile_push, no_chdir => 1 }, $_current_path);
894
# The sort isn't totally necessary, but it makes debugging easier
895
# by making the templates always be compiled in the same order.
896
foreach my $file (sort keys %_templates_to_precompile) {
897
# Compile the template but throw away the result. This has the side-
898
# effect of writing the compiled version to disk.
899
$template->context->template($file);
903
# Under mod_perl, we look for templates using the absolute path of the
904
# template directory, which causes Template Toolkit to look for their
905
# *compiled* versions using the full absolute path under the data/template
906
# directory. (Like data/template/var/www/html/mod_perl/.) To avoid
907
# re-compiling templates under mod_perl, we symlink to the
908
# already-compiled templates. This doesn't work on Windows.
910
my $abs_root = dirname(abs_path($templatedir));
911
my $todir = "$datadir/template$abs_root";
913
# We use abs2rel so that the symlink will look like
914
# "../../../../template" which works, while just
915
# "data/template/template/" doesn't work.
916
my $fromdir = File::Spec->abs2rel("$datadir/template/template", $todir);
917
# We eval for systems that can't symlink at all, where "symlink"
918
# throws a fatal error.
919
eval { symlink($fromdir, "$todir/template")
920
or warn "Failed to symlink from $fromdir to $todir: $!" };
923
# If anything created a Template object before now, clear it out.
924
delete Bugzilla->request_cache->{template};
925
# This is the single variable used to precompile templates,
926
# which needs to be cleared as well.
927
delete Bugzilla->request_cache->{template_include_path_};
930
# Helper for precompile_templates
931
sub _precompile_push {
932
my $name = $File::Find::name;
933
return if (-d $name);
934
return if ($name =~ /\/CVS\//);
935
return if ($name !~ /\.tmpl$/);
937
$name =~ s/\Q$_current_path\E\///;
938
$_templates_to_precompile{$name} = 1;
947
Bugzilla::Template - Wrapper around the Template Toolkit C<Template> object
951
my $template = Bugzilla::Template->create;
953
$template->put_header($title, $h1, $h2);
954
$template->put_footer();
956
my $format = $template->get_format("foo/bar",
957
scalar($cgi->param('format')),
958
scalar($cgi->param('ctype')));
962
This is basically a wrapper so that the correct arguments get passed into
963
the C<Template> constructor.
965
It should not be used directly by scripts or modules - instead, use
966
C<Bugzilla-E<gt>instance-E<gt>template> to get an already created module.
972
=item C<precompile_templates($output)>
974
Description: Compiles all of Bugzilla's templates in every language.
975
Used mostly by F<checksetup.pl>.
977
Params: C<$output> - C<true> if you want the function to print
978
out information about what it's doing.
988
=item C<put_header($title, $h1, $h2)>
990
Description: Display the header of the page for non yet templatized .cgi files.
992
Params: $title - Page title.
993
$h1 - Main page header.
994
$h2 - Page subheader.
998
=item C<put_footer()>
1000
Description: Display the footer of the page for non yet templatized .cgi files.
1006
=item C<get_format($file, $format, $ctype)>
1008
Description: Construct a format object from URL parameters.
1010
Params: $file - Name of the template to display.
1011
$format - When the template exists under several formats
1012
(e.g. table or graph), specify the one to choose.
1013
$ctype - Content type, see Bugzilla::Constants::contenttypes.
1015
Returns: A format object.
1021
L<Bugzilla>, L<Template>