123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926 |
- # -*- Mode: perl; indent-tabs-mode: nil -*-
- #
- # The contents of this file are subject to the Mozilla Public
- # License Version 1.1 (the "License"); you may not use this file
- # except in compliance with the License. You may obtain a copy of
- # the License at http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS
- # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
- # implied. See the License for the specific language governing
- # rights and limitations under the License.
- #
- # The Original Code is the Bugzilla Bug Tracking System.
- #
- # The Initial Developer of the Original Code is Netscape Communications
- # Corporation. Portions created by Netscape are
- # Copyright (C) 1998 Netscape Communications Corporation. All
- # Rights Reserved.
- #
- # Contributor(s): Terry Weissman <terry@mozilla.org>
- # Dan Mosedale <dmose@mozilla.org>
- # Jacob Steenhagen <jake@bugzilla.org>
- # Bradley Baetz <bbaetz@student.usyd.edu.au>
- # Christopher Aillon <christopher@aillon.com>
- # Tobias Burnus <burnus@net-b.de>
- # Myk Melez <myk@mozilla.org>
- # Max Kanat-Alexander <mkanat@bugzilla.org>
- # Frédéric Buclin <LpSolit@gmail.com>
- # Greg Hendricks <ghendricks@novell.com>
- # David D. Kilzer <ddkilzer@kilzer.net>
- package Bugzilla::Template;
- use strict;
- use Bugzilla::Constants;
- use Bugzilla::Install::Requirements;
- use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
- use Bugzilla::Util;
- use Bugzilla::User;
- use Bugzilla::Error;
- use Bugzilla::Status;
- use Bugzilla::Token;
- use Bugzilla::Template::Parser;
- use Cwd qw(abs_path);
- use MIME::Base64;
- # for time2str - replace by TT Date plugin??
- use Date::Format ();
- use File::Basename qw(dirname);
- use File::Find;
- use File::Path qw(rmtree mkpath);
- use File::Spec;
- use IO::Dir;
- use base qw(Template);
- # As per the Template::Base documentation, the _init() method is being called
- # by the new() constructor. We take advantage of this in order to plug our
- # UTF-8-aware Parser object in neatly after the original _init() method has
- # happened, in particular, after having set up the constants namespace.
- # See bug 413121 for details.
- sub _init {
- my $self = shift;
- my $config = $_[0];
- $self->SUPER::_init(@_) || return undef;
- $self->{PARSER} = $config->{PARSER}
- = new Bugzilla::Template::Parser($config);
- # Now we need to re-create the default Service object, making it aware
- # of our Parser object.
- $self->{SERVICE} = $config->{SERVICE}
- = Template::Config->service($config);
- return $self;
- }
- # Convert the constants in the Bugzilla::Constants module into a hash we can
- # pass to the template object for reflection into its "constants" namespace
- # (which is like its "variables" namespace, but for constants). To do so, we
- # traverse the arrays of exported and exportable symbols and ignoring the rest
- # (which, if Constants.pm exports only constants, as it should, will be nothing else).
- sub _load_constants {
- my %constants;
- foreach my $constant (@Bugzilla::Constants::EXPORT,
- @Bugzilla::Constants::EXPORT_OK)
- {
- if (ref Bugzilla::Constants->$constant) {
- $constants{$constant} = Bugzilla::Constants->$constant;
- }
- else {
- my @list = (Bugzilla::Constants->$constant);
- $constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
- }
- }
- return \%constants;
- }
- # Returns the path to the templates based on the Accept-Language
- # settings of the user and of the available languages
- # If no Accept-Language is present it uses the defined default
- # Templates may also be found in the extensions/ tree
- sub getTemplateIncludePath {
- my $cache = Bugzilla->request_cache;
- my $lang = $cache->{'language'} || '';
- $cache->{"template_include_path_$lang"} ||= template_include_path({
- use_languages => Bugzilla->languages,
- only_language => $lang });
- return $cache->{"template_include_path_$lang"};
- }
- sub get_format {
- my $self = shift;
- my ($template, $format, $ctype) = @_;
- $ctype ||= 'html';
- $format ||= '';
- # Security - allow letters and a hyphen only
- $ctype =~ s/[^a-zA-Z\-]//g;
- $format =~ s/[^a-zA-Z\-]//g;
- trick_taint($ctype);
- trick_taint($format);
- $template .= ($format ? "-$format" : "");
- $template .= ".$ctype.tmpl";
- # Now check that the template actually exists. We only want to check
- # if the template exists; any other errors (eg parse errors) will
- # end up being detected later.
- eval {
- $self->context->template($template);
- };
- # This parsing may seem fragile, but it's OK:
- # http://lists.template-toolkit.org/pipermail/templates/2003-March/004370.html
- # Even if it is wrong, any sort of error is going to cause a failure
- # eventually, so the only issue would be an incorrect error message
- if ($@ && $@->info =~ /: not found$/) {
- ThrowUserError('format_not_found', {'format' => $format,
- 'ctype' => $ctype});
- }
- # Else, just return the info
- return
- {
- 'template' => $template,
- 'format' => $format,
- 'extension' => $ctype,
- 'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
- };
- }
- # This routine quoteUrls contains inspirations from the HTML::FromText CPAN
- # module by Gareth Rees <garethr@cre.canon.co.uk>. It has been heavily hacked,
- # all that is really recognizable from the original is bits of the regular
- # expressions.
- # This has been rewritten to be faster, mainly by substituting 'as we go'.
- # If you want to modify this routine, read the comments carefully
- sub quoteUrls {
- my ($text, $curr_bugid) = (@_);
- return $text unless $text;
- # We use /g for speed, but uris can have other things inside them
- # (http://foo/bug#3 for example). Filtering that out filters valid
- # bug refs out, so we have to do replacements.
- # mailto can't contain space or #, so we don't have to bother for that
- # Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
- # \0 is used because it's unlikely to occur in the text, so the cost of
- # doing this should be very small
- # escape the 2nd escape char we're using
- my $chr1 = chr(1);
- $text =~ s/\0/$chr1\0/g;
- # However, note that adding the title (for buglinks) can affect things
- # In particular, attachment matches go before bug titles, so that titles
- # with 'attachment 1' don't double match.
- # Dupe checks go afterwards, because that uses ^ and \Z, which won't occur
- # if it was substituted as a bug title (since that always involve leading
- # and trailing text)
- # Because of entities, it's easier (and quicker) to do this before escaping
- my @things;
- my $count = 0;
- my $tmp;
- # Provide tooltips for full bug links (Bug 74355)
- my $urlbase_re = '(' . join('|',
- map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
- Bugzilla->params->{'sslbase'})) . ')';
- $text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
- ~($things[$count++] = get_bug_link($3, $1, $5)) &&
- ("\0\0" . ($count-1) . "\0\0")
- ~egox;
- # non-mailto protocols
- my $safe_protocols = join('|', SAFE_PROTOCOLS);
- my $protocol_re = qr/($safe_protocols)/i;
- $text =~ s~\b(${protocol_re}: # The protocol:
- [^\s<>\"]+ # Any non-whitespace
- [\w\/]) # so that we end in \w or /
- ~($tmp = html_quote($1)) &&
- ($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
- ("\0\0" . ($count-1) . "\0\0")
- ~egox;
- # We have to quote now, otherwise the html itself is escaped
- # THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
- $text = html_quote($text);
- # Color quoted text
- $text =~ s~^(>.+)$~<span class="quote">$1</span >~mg;
- $text =~ s~</span >\n<span class="quote">~\n~g;
- # mailto:
- # Use |<nothing> so that $1 is defined regardless
- $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
- ~<a href=\"mailto:$2\">$1$2</a>~igx;
- # attachment links - handle both cases separately for simplicity
- $text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
- ~($things[$count++] = get_attachment_link($2, $1)) &&
- ("\0\0" . ($count-1) . "\0\0")
- ~egmx;
- $text =~ s~\b(attachment\s*\#?\s*(\d+))
- ~($things[$count++] = get_attachment_link($2, $1)) &&
- ("\0\0" . ($count-1) . "\0\0")
- ~egmxi;
- # Current bug ID this comment belongs to
- my $current_bugurl = $curr_bugid ? "show_bug.cgi?id=$curr_bugid" : "";
- # This handles bug a, comment b type stuff. Because we're using /g
- # we have to do this in one pattern, and so this is semi-messy.
- # Also, we can't use $bug_re?$comment_re? because that will match the
- # empty string
- my $bug_word = get_text('term', { term => 'bug' });
- my $bug_re = qr/\Q$bug_word\E\s*\#?\s*(\d+)/i;
- my $comment_re = qr/comment\s*\#?\s*(\d+)/i;
- $text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
- ~ # We have several choices. $1 here is the link, and $2-4 are set
- # depending on which part matched
- (defined($2) ? get_bug_link($2,$1,$3) :
- "<a href=\"$current_bugurl#c$4\">$1</a>")
- ~egox;
- # Old duplicate markers. These don't use $bug_word because they are old
- # and were never customizable.
- $text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
- (\d+)
- (?=\ \*\*\*\Z)
- ~get_bug_link($1, $1)
- ~egmx;
- # Now remove the encoding hacks
- $text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
- $text =~ s/$chr1\0/\0/g;
- return $text;
- }
- # Creates a link to an attachment, including its title.
- sub get_attachment_link {
- my ($attachid, $link_text) = @_;
- my $dbh = Bugzilla->dbh;
- detaint_natural($attachid)
- || die "get_attachment_link() called with non-integer attachment number";
- my ($bugid, $isobsolete, $desc) =
- $dbh->selectrow_array('SELECT bug_id, isobsolete, description
- FROM attachments WHERE attach_id = ?',
- undef, $attachid);
- if ($bugid) {
- my $title = "";
- my $className = "";
- if (Bugzilla->user->can_see_bug($bugid)) {
- $title = $desc;
- }
- if ($isobsolete) {
- $className = "bz_obsolete";
- }
- # Prevent code injection in the title.
- $title = html_quote(clean_text($title));
- $link_text =~ s/ \[details\]$//;
- my $linkval = "attachment.cgi?id=$attachid";
- # Whitespace matters here because these links are in <pre> tags.
- return qq|<span class="$className">|
- . qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
- . qq| <a href="${linkval}&action=edit" title="$title">[details]</a>|
- . qq|</span>|;
- }
- else {
- return qq{$link_text};
- }
- }
- # Creates a link to a bug, including its title.
- # It takes either two or three parameters:
- # - The bug number
- # - The link text, to place between the <a>..</a>
- # - An optional comment number, for linking to a particular
- # comment in the bug
- sub get_bug_link {
- my ($bug_num, $link_text, $comment_num) = @_;
- my $dbh = Bugzilla->dbh;
- if (!defined($bug_num) || ($bug_num eq "")) {
- return "<missing bug number>";
- }
- my $quote_bug_num = html_quote($bug_num);
- detaint_natural($bug_num) || return "<invalid bug number: $quote_bug_num>";
- my ($bug_state, $bug_res, $bug_desc) =
- $dbh->selectrow_array('SELECT bugs.bug_status, resolution, short_desc
- FROM bugs WHERE bugs.bug_id = ?',
- undef, $bug_num);
- if ($bug_state) {
- # Initialize these variables to be "" so that we don't get warnings
- # if we don't change them below (which is highly likely).
- my ($pre, $title, $post) = ("", "", "");
- $title = get_text('get_status', {status => $bug_state});
- if ($bug_state eq 'UNCONFIRMED') {
- $pre = "<i>";
- $post = "</i>";
- }
- elsif (!is_open_state($bug_state)) {
- $pre = '<span class="bz_closed">';
- $title .= ' ' . get_text('get_resolution', {resolution => $bug_res});
- $post = '</span>';
- }
- if (Bugzilla->user->can_see_bug($bug_num)) {
- $title .= " - $bug_desc";
- }
- # Prevent code injection in the title.
- $title = html_quote(clean_text($title));
- my $linkval = "show_bug.cgi?id=$bug_num";
- if (defined $comment_num) {
- $linkval .= "#c$comment_num";
- }
- return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
- }
- else {
- return qq{$link_text};
- }
- }
- ###############################################################################
- # Templatization Code
- # The Template Toolkit throws an error if a loop iterates >1000 times.
- # We want to raise that limit.
- # NOTE: If you change this number, you MUST RE-RUN checksetup.pl!!!
- # If you do not re-run checksetup.pl, the change you make will not apply
- $Template::Directive::WHILE_MAX = 1000000;
- # Use the Toolkit Template's Stash module to add utility pseudo-methods
- # to template variables.
- use Template::Stash;
- # Add "contains***" methods to list variables that search for one or more
- # items in a list and return boolean values representing whether or not
- # one/all/any item(s) were found.
- $Template::Stash::LIST_OPS->{ contains } =
- sub {
- my ($list, $item) = @_;
- return grep($_ eq $item, @$list);
- };
- $Template::Stash::LIST_OPS->{ containsany } =
- sub {
- my ($list, $items) = @_;
- foreach my $item (@$items) {
- return 1 if grep($_ eq $item, @$list);
- }
- return 0;
- };
- # Clone the array reference to leave the original one unaltered.
- $Template::Stash::LIST_OPS->{ clone } =
- sub {
- my $list = shift;
- return [@$list];
- };
- # Allow us to still get the scalar if we use the list operation ".0" on it,
- # as we often do for defaults in query.cgi and other places.
- $Template::Stash::SCALAR_OPS->{ 0 } =
- sub {
- return $_[0];
- };
- # Add a "substr" method to the Template Toolkit's "scalar" object
- # that returns a substring of a string.
- $Template::Stash::SCALAR_OPS->{ substr } =
- sub {
- my ($scalar, $offset, $length) = @_;
- return substr($scalar, $offset, $length);
- };
- # Add a "truncate" method to the Template Toolkit's "scalar" object
- # that truncates a string to a certain length.
- $Template::Stash::SCALAR_OPS->{ truncate } =
- sub {
- my ($string, $length, $ellipsis) = @_;
- $ellipsis ||= "";
-
- return $string if !$length || length($string) <= $length;
-
- my $strlen = $length - length($ellipsis);
- my $newstr = substr($string, 0, $strlen) . $ellipsis;
- return $newstr;
- };
- # Create the template object that processes templates and specify
- # configuration parameters that apply to all templates.
- ###############################################################################
- # Construct the Template object
- # Note that all of the failure cases here can't use templateable errors,
- # since we won't have a template to use...
- sub create {
- my $class = shift;
- my %opts = @_;
- # checksetup.pl will call us once for any template/lang directory.
- # We need a possibility to reset the cache, so that no files from
- # the previous language pollute the action.
- if ($opts{'clean_cache'}) {
- delete Bugzilla->request_cache->{template_include_path_};
- }
- # IMPORTANT - If you make any configuration changes here, make sure to
- # make them in t/004.template.t and checksetup.pl.
- return $class->new({
- # Colon-separated list of directories containing templates.
- INCLUDE_PATH => [\&getTemplateIncludePath],
- # Remove white-space before template directives (PRE_CHOMP) and at the
- # beginning and end of templates and template blocks (TRIM) for better
- # looking, more compact content. Use the plus sign at the beginning
- # of directives to maintain white space (i.e. [%+ DIRECTIVE %]).
- PRE_CHOMP => 1,
- TRIM => 1,
- COMPILE_DIR => bz_locations()->{'datadir'} . "/template",
- # Initialize templates (f.e. by loading plugins like Hook).
- PRE_PROCESS => "global/initialize.none.tmpl",
- # Functions for processing text within templates in various ways.
- # IMPORTANT! When adding a filter here that does not override a
- # built-in filter, please also add a stub filter to t/004template.t.
- FILTERS => {
- # Render text in required style.
- inactive => [
- sub {
- my($context, $isinactive) = @_;
- return sub {
- return $isinactive ? '<span class="bz_inactive">'.$_[0].'</span>' : $_[0];
- }
- }, 1
- ],
- closed => [
- sub {
- my($context, $isclosed) = @_;
- return sub {
- return $isclosed ? '<span class="bz_closed">'.$_[0].'</span>' : $_[0];
- }
- }, 1
- ],
- obsolete => [
- sub {
- my($context, $isobsolete) = @_;
- return sub {
- return $isobsolete ? '<span class="bz_obsolete">'.$_[0].'</span>' : $_[0];
- }
- }, 1
- ],
- # Returns the text with backslashes, single/double quotes,
- # and newlines/carriage returns escaped for use in JS strings.
- js => sub {
- my ($var) = @_;
- $var =~ s/([\\\'\"\/])/\\$1/g;
- $var =~ s/\n/\\n/g;
- $var =~ s/\r/\\r/g;
- $var =~ s/\@/\\x40/g; # anti-spam for email addresses
- return $var;
- },
-
- # Converts data to base64
- base64 => sub {
- my ($data) = @_;
- return encode_base64($data);
- },
-
- # HTML collapses newlines in element attributes to a single space,
- # so form elements which may have whitespace (ie comments) need
- # to be encoded using 
- # See bugs 4928, 22983 and 32000 for more details
- html_linebreak => sub {
- my ($var) = @_;
- $var =~ s/\r\n/\
/g;
- $var =~ s/\n\r/\
/g;
- $var =~ s/\r/\
/g;
- $var =~ s/\n/\
/g;
- return $var;
- },
- # Prevents line break on hyphens and whitespaces.
- no_break => sub {
- my ($var) = @_;
- $var =~ s/ /\ /g;
- $var =~ s/-/\‑/g;
- return $var;
- },
- xml => \&Bugzilla::Util::xml_quote ,
- # This filter escapes characters in a variable or value string for
- # use in a query string. It escapes all characters NOT in the
- # regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
- # a full URL that may have characters that need encoding.
- url_quote => \&Bugzilla::Util::url_quote ,
- # This filter is similar to url_quote but used a \ instead of a %
- # as prefix. In addition it replaces a ' ' by a '_'.
- css_class_quote => \&Bugzilla::Util::css_class_quote ,
- quoteUrls => [ sub {
- my ($context, $bug) = @_;
- return sub {
- my $text = shift;
- return quoteUrls($text, $bug);
- };
- },
- 1
- ],
- bug_link => [ sub {
- my ($context, $bug) = @_;
- return sub {
- my $text = shift;
- return get_bug_link($bug, $text);
- };
- },
- 1
- ],
- bug_list_link => sub
- {
- my $buglist = shift;
- return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
- },
- # In CSV, quotes are doubled, and any value containing a quote or a
- # comma is enclosed in quotes.
- csv => sub
- {
- my ($var) = @_;
- $var =~ s/\"/\"\"/g;
- if ($var !~ /^-?(\d+\.)?\d*$/) {
- $var = "\"$var\"";
- }
- return $var;
- } ,
- # Format a filesize in bytes to a human readable value
- unitconvert => sub
- {
- my ($data) = @_;
- my $retval = "";
- my %units = (
- 'KB' => 1024,
- 'MB' => 1024 * 1024,
- 'GB' => 1024 * 1024 * 1024,
- );
- if ($data < 1024) {
- return "$data bytes";
- }
- else {
- my $u;
- foreach $u ('GB', 'MB', 'KB') {
- if ($data >= $units{$u}) {
- return sprintf("%.2f %s", $data/$units{$u}, $u);
- }
- }
- }
- },
- # Format a time for display (more info in Bugzilla::Util)
- time => \&Bugzilla::Util::format_time,
- # Bug 120030: Override html filter to obscure the '@' in user
- # visible strings.
- # Bug 319331: Handle BiDi disruptions.
- html => sub {
- my ($var) = Template::Filters::html_filter(@_);
- # Obscure '@'.
- $var =~ s/\@/\@/g;
- if (Bugzilla->params->{'utf8'}) {
- # Remove the following characters because they're
- # influencing BiDi:
- # --------------------------------------------------------
- # |Code |Name |UTF-8 representation|
- # |------|--------------------------|--------------------|
- # |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
- # |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
- # |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
- # |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
- # |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
- # --------------------------------------------------------
- #
- # The following are characters influencing BiDi, too, but
- # they can be spared from filtering because they don't
- # influence more than one character right or left:
- # --------------------------------------------------------
- # |Code |Name |UTF-8 representation|
- # |------|--------------------------|--------------------|
- # |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
- # |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
- # --------------------------------------------------------
- $var =~ s/[\x{202a}-\x{202e}]//g;
- }
- return $var;
- },
- html_light => \&Bugzilla::Util::html_light_quote,
- # iCalendar contentline filter
- ics => [ sub {
- my ($context, @args) = @_;
- return sub {
- my ($var) = shift;
- my ($par) = shift @args;
- my ($output) = "";
- $var =~ s/[\r\n]/ /g;
- $var =~ s/([;\\\",])/\\$1/g;
- if ($par) {
- $output = sprintf("%s:%s", $par, $var);
- } else {
- $output = $var;
- }
-
- $output =~ s/(.{75,75})/$1\n /g;
- return $output;
- };
- },
- 1
- ],
- # Note that using this filter is even more dangerous than
- # using "none," and you should only use it when you're SURE
- # the output won't be displayed directly to a web browser.
- txt => sub {
- my ($var) = @_;
- # Trivial HTML tag remover
- $var =~ s/<[^>]*>//g;
- # And this basically reverses the html filter.
- $var =~ s/\@/@/g;
- $var =~ s/\</</g;
- $var =~ s/\>/>/g;
- $var =~ s/\"/\"/g;
- $var =~ s/\&/\&/g;
- return $var;
- },
- # Wrap a displayed comment to the appropriate length
- wrap_comment => [
- sub {
- my ($context, $cols) = @_;
- return sub { wrap_comment($_[0], $cols) }
- }, 1],
- # We force filtering of every variable in key security-critical
- # places; we have a none filter for people to use when they
- # really, really don't want a variable to be changed.
- none => sub { return $_[0]; } ,
- },
- PLUGIN_BASE => 'Bugzilla::Template::Plugin',
- CONSTANTS => _load_constants(),
- # Default variables for all templates
- VARIABLES => {
- # Function for retrieving global parameters.
- 'Param' => sub { return Bugzilla->params->{$_[0]}; },
- # Function to create date strings
- 'time2str' => \&Date::Format::time2str,
- # Generic linear search function
- 'lsearch' => \&Bugzilla::Util::lsearch,
- # Currently logged in user, if any
- # If an sudo session is in progress, this is the user we're faking
- 'user' => sub { return Bugzilla->user; },
- # If an sudo session is in progress, this is the user who
- # started the session.
- 'sudoer' => sub { return Bugzilla->sudoer; },
- # SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
- 'SendBugMail' => sub {
- my ($id, $mailrecipients) = (@_);
- require Bugzilla::BugMail;
- Bugzilla::BugMail::Send($id, $mailrecipients);
- },
- # Allow templates to access the "corect" URLBase value
- 'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
- # Allow templates to access docs url with users' preferred language
- 'docs_urlbase' => sub {
- my ($language) = include_languages();
- my $docs_urlbase = Bugzilla->params->{'docs_urlbase'};
- $docs_urlbase =~ s/\%lang\%/$language/;
- return $docs_urlbase;
- },
- # Allow templates to generate a token themselves.
- 'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
- # These don't work as normal constants.
- DB_MODULE => \&Bugzilla::Constants::DB_MODULE,
- REQUIRED_MODULES =>
- \&Bugzilla::Install::Requirements::REQUIRED_MODULES,
- OPTIONAL_MODULES => sub {
- my @optional = @{OPTIONAL_MODULES()};
- @optional = sort {$a->{feature} cmp $b->{feature}}
- @optional;
- return \@optional;
- },
- },
- }) || die("Template creation failed: " . $class->error());
- }
- # Used as part of the two subroutines below.
- our (%_templates_to_precompile, $_current_path);
- sub precompile_templates {
- my ($output) = @_;
- # Remove the compiled templates.
- my $datadir = bz_locations()->{'datadir'};
- if (-e "$datadir/template") {
- print install_string('template_removing_dir') . "\n" if $output;
- # XXX This frequently fails if the webserver made the files, because
- # then the webserver owns the directories. We could fix that by
- # doing a chmod/chown on all the directories here.
- rmtree("$datadir/template");
- # Check that the directory was really removed
- if(-e "$datadir/template") {
- print "\n\n";
- print "The directory '$datadir/template' could not be removed.\n";
- print "Please remove it manually and rerun checksetup.pl.\n\n";
- exit;
- }
- }
- print install_string('template_precompile') if $output;
- my $templatedir = bz_locations()->{'templatedir'};
- # Don't hang on templates which use the CGI library
- eval("use CGI qw(-no_debug)");
-
- my $dir_reader = new IO::Dir($templatedir) || die "$templatedir: $!";
- my @language_dirs = grep { /^[a-z-]+$/i } $dir_reader->read;
- $dir_reader->close;
- foreach my $dir (@language_dirs) {
- next if ($dir eq 'CVS');
- -d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom"
- || next;
- local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
- my $template = Bugzilla::Template->create(clean_cache => 1);
- # Precompile all the templates found in all the directories.
- %_templates_to_precompile = ();
- foreach my $subdir (qw(custom extension default), bz_locations()->{'project'}) {
- next unless $subdir; # If 'project' is empty.
- $_current_path = File::Spec->catdir($templatedir, $dir, $subdir);
- next unless -d $_current_path;
- # Traverse the template hierarchy.
- find({ wanted => \&_precompile_push, no_chdir => 1 }, $_current_path);
- }
- # The sort isn't totally necessary, but it makes debugging easier
- # by making the templates always be compiled in the same order.
- foreach my $file (sort keys %_templates_to_precompile) {
- # Compile the template but throw away the result. This has the side-
- # effect of writing the compiled version to disk.
- $template->context->template($file);
- }
- }
- # Under mod_perl, we look for templates using the absolute path of the
- # template directory, which causes Template Toolkit to look for their
- # *compiled* versions using the full absolute path under the data/template
- # directory. (Like data/template/var/www/html/mod_perl/.) To avoid
- # re-compiling templates under mod_perl, we symlink to the
- # already-compiled templates. This doesn't work on Windows.
- if (!ON_WINDOWS) {
- my $abs_root = dirname(abs_path($templatedir));
- my $todir = "$datadir/template$abs_root";
- mkpath($todir);
- # We use abs2rel so that the symlink will look like
- # "../../../../template" which works, while just
- # "data/template/template/" doesn't work.
- my $fromdir = File::Spec->abs2rel("$datadir/template/template", $todir);
- # We eval for systems that can't symlink at all, where "symlink"
- # throws a fatal error.
- eval { symlink($fromdir, "$todir/template")
- or warn "Failed to symlink from $fromdir to $todir: $!" };
- }
- # If anything created a Template object before now, clear it out.
- delete Bugzilla->request_cache->{template};
- # This is the single variable used to precompile templates,
- # which needs to be cleared as well.
- delete Bugzilla->request_cache->{template_include_path_};
- print install_string('done') . "\n" if $output;
- }
- # Helper for precompile_templates
- sub _precompile_push {
- my $name = $File::Find::name;
- return if (-d $name);
- return if ($name =~ /\/CVS\//);
- return if ($name !~ /\.tmpl$/);
-
- $name =~ s/\Q$_current_path\E\///;
- $_templates_to_precompile{$name} = 1;
- }
- 1;
- __END__
- =head1 NAME
- Bugzilla::Template - Wrapper around the Template Toolkit C<Template> object
- =head1 SYNOPSIS
- my $template = Bugzilla::Template->create;
- my $format = $template->get_format("foo/bar",
- scalar($cgi->param('format')),
- scalar($cgi->param('ctype')));
- =head1 DESCRIPTION
- This is basically a wrapper so that the correct arguments get passed into
- the C<Template> constructor.
- It should not be used directly by scripts or modules - instead, use
- C<Bugzilla-E<gt>instance-E<gt>template> to get an already created module.
- =head1 SUBROUTINES
- =over
- =item C<precompile_templates($output)>
- Description: Compiles all of Bugzilla's templates in every language.
- Used mostly by F<checksetup.pl>.
- Params: C<$output> - C<true> if you want the function to print
- out information about what it's doing.
- Returns: nothing
- =back
- =head1 METHODS
- =over
- =item C<get_format($file, $format, $ctype)>
- Description: Construct a format object from URL parameters.
- Params: $file - Name of the template to display.
- $format - When the template exists under several formats
- (e.g. table or graph), specify the one to choose.
- $ctype - Content type, see Bugzilla::Constants::contenttypes.
- Returns: A format object.
- =back
- =head1 SEE ALSO
- L<Bugzilla>, L<Template>
|