Template.pm 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. # -*- Mode: perl; indent-tabs-mode: nil -*-
  2. #
  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/
  7. #
  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.
  12. #
  13. # The Original Code is the Bugzilla Bug Tracking System.
  14. #
  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
  18. # Rights Reserved.
  19. #
  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>
  31. package Bugzilla::Template;
  32. use strict;
  33. use Bugzilla::Constants;
  34. use Bugzilla::Install::Requirements;
  35. use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
  36. use Bugzilla::Util;
  37. use Bugzilla::User;
  38. use Bugzilla::Error;
  39. use Bugzilla::Status;
  40. use Bugzilla::Token;
  41. use Bugzilla::Template::Parser;
  42. use Cwd qw(abs_path);
  43. use MIME::Base64;
  44. # for time2str - replace by TT Date plugin??
  45. use Date::Format ();
  46. use File::Basename qw(dirname);
  47. use File::Find;
  48. use File::Path qw(rmtree mkpath);
  49. use File::Spec;
  50. use IO::Dir;
  51. use base qw(Template);
  52. # As per the Template::Base documentation, the _init() method is being called
  53. # by the new() constructor. We take advantage of this in order to plug our
  54. # UTF-8-aware Parser object in neatly after the original _init() method has
  55. # happened, in particular, after having set up the constants namespace.
  56. # See bug 413121 for details.
  57. sub _init {
  58. my $self = shift;
  59. my $config = $_[0];
  60. $self->SUPER::_init(@_) || return undef;
  61. $self->{PARSER} = $config->{PARSER}
  62. = new Bugzilla::Template::Parser($config);
  63. # Now we need to re-create the default Service object, making it aware
  64. # of our Parser object.
  65. $self->{SERVICE} = $config->{SERVICE}
  66. = Template::Config->service($config);
  67. return $self;
  68. }
  69. # Convert the constants in the Bugzilla::Constants module into a hash we can
  70. # pass to the template object for reflection into its "constants" namespace
  71. # (which is like its "variables" namespace, but for constants). To do so, we
  72. # traverse the arrays of exported and exportable symbols and ignoring the rest
  73. # (which, if Constants.pm exports only constants, as it should, will be nothing else).
  74. sub _load_constants {
  75. my %constants;
  76. foreach my $constant (@Bugzilla::Constants::EXPORT,
  77. @Bugzilla::Constants::EXPORT_OK)
  78. {
  79. if (ref Bugzilla::Constants->$constant) {
  80. $constants{$constant} = Bugzilla::Constants->$constant;
  81. }
  82. else {
  83. my @list = (Bugzilla::Constants->$constant);
  84. $constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
  85. }
  86. }
  87. return \%constants;
  88. }
  89. # Returns the path to the templates based on the Accept-Language
  90. # settings of the user and of the available languages
  91. # If no Accept-Language is present it uses the defined default
  92. # Templates may also be found in the extensions/ tree
  93. sub getTemplateIncludePath {
  94. my $cache = Bugzilla->request_cache;
  95. my $lang = $cache->{'language'} || '';
  96. $cache->{"template_include_path_$lang"} ||= template_include_path({
  97. use_languages => Bugzilla->languages,
  98. only_language => $lang });
  99. return $cache->{"template_include_path_$lang"};
  100. }
  101. sub get_format {
  102. my $self = shift;
  103. my ($template, $format, $ctype) = @_;
  104. $ctype ||= 'html';
  105. $format ||= '';
  106. # Security - allow letters and a hyphen only
  107. $ctype =~ s/[^a-zA-Z\-]//g;
  108. $format =~ s/[^a-zA-Z\-]//g;
  109. trick_taint($ctype);
  110. trick_taint($format);
  111. $template .= ($format ? "-$format" : "");
  112. $template .= ".$ctype.tmpl";
  113. # Now check that the template actually exists. We only want to check
  114. # if the template exists; any other errors (eg parse errors) will
  115. # end up being detected later.
  116. eval {
  117. $self->context->template($template);
  118. };
  119. # This parsing may seem fragile, but it's OK:
  120. # http://lists.template-toolkit.org/pipermail/templates/2003-March/004370.html
  121. # Even if it is wrong, any sort of error is going to cause a failure
  122. # eventually, so the only issue would be an incorrect error message
  123. if ($@ && $@->info =~ /: not found$/) {
  124. ThrowUserError('format_not_found', {'format' => $format,
  125. 'ctype' => $ctype});
  126. }
  127. # Else, just return the info
  128. return
  129. {
  130. 'template' => $template,
  131. 'format' => $format,
  132. 'extension' => $ctype,
  133. 'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
  134. };
  135. }
  136. # This routine quoteUrls contains inspirations from the HTML::FromText CPAN
  137. # module by Gareth Rees <garethr@cre.canon.co.uk>. It has been heavily hacked,
  138. # all that is really recognizable from the original is bits of the regular
  139. # expressions.
  140. # This has been rewritten to be faster, mainly by substituting 'as we go'.
  141. # If you want to modify this routine, read the comments carefully
  142. sub quoteUrls {
  143. my ($text, $curr_bugid) = (@_);
  144. return $text unless $text;
  145. # We use /g for speed, but uris can have other things inside them
  146. # (http://foo/bug#3 for example). Filtering that out filters valid
  147. # bug refs out, so we have to do replacements.
  148. # mailto can't contain space or #, so we don't have to bother for that
  149. # Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
  150. # \0 is used because it's unlikely to occur in the text, so the cost of
  151. # doing this should be very small
  152. # escape the 2nd escape char we're using
  153. my $chr1 = chr(1);
  154. $text =~ s/\0/$chr1\0/g;
  155. # However, note that adding the title (for buglinks) can affect things
  156. # In particular, attachment matches go before bug titles, so that titles
  157. # with 'attachment 1' don't double match.
  158. # Dupe checks go afterwards, because that uses ^ and \Z, which won't occur
  159. # if it was substituted as a bug title (since that always involve leading
  160. # and trailing text)
  161. # Because of entities, it's easier (and quicker) to do this before escaping
  162. my @things;
  163. my $count = 0;
  164. my $tmp;
  165. # Provide tooltips for full bug links (Bug 74355)
  166. my $urlbase_re = '(' . join('|',
  167. map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
  168. Bugzilla->params->{'sslbase'})) . ')';
  169. $text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
  170. ~($things[$count++] = get_bug_link($3, $1, $5)) &&
  171. ("\0\0" . ($count-1) . "\0\0")
  172. ~egox;
  173. # non-mailto protocols
  174. my $safe_protocols = join('|', SAFE_PROTOCOLS);
  175. my $protocol_re = qr/($safe_protocols)/i;
  176. $text =~ s~\b(${protocol_re}: # The protocol:
  177. [^\s<>\"]+ # Any non-whitespace
  178. [\w\/]) # so that we end in \w or /
  179. ~($tmp = html_quote($1)) &&
  180. ($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
  181. ("\0\0" . ($count-1) . "\0\0")
  182. ~egox;
  183. # We have to quote now, otherwise the html itself is escaped
  184. # THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
  185. $text = html_quote($text);
  186. # Color quoted text
  187. $text =~ s~^(&gt;.+)$~<span class="quote">$1</span >~mg;
  188. $text =~ s~</span >\n<span class="quote">~\n~g;
  189. # mailto:
  190. # Use |<nothing> so that $1 is defined regardless
  191. $text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
  192. ~<a href=\"mailto:$2\">$1$2</a>~igx;
  193. # attachment links - handle both cases separately for simplicity
  194. $text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
  195. ~($things[$count++] = get_attachment_link($2, $1)) &&
  196. ("\0\0" . ($count-1) . "\0\0")
  197. ~egmx;
  198. $text =~ s~\b(attachment\s*\#?\s*(\d+))
  199. ~($things[$count++] = get_attachment_link($2, $1)) &&
  200. ("\0\0" . ($count-1) . "\0\0")
  201. ~egmxi;
  202. # Current bug ID this comment belongs to
  203. my $current_bugurl = $curr_bugid ? "show_bug.cgi?id=$curr_bugid" : "";
  204. # This handles bug a, comment b type stuff. Because we're using /g
  205. # we have to do this in one pattern, and so this is semi-messy.
  206. # Also, we can't use $bug_re?$comment_re? because that will match the
  207. # empty string
  208. my $bug_word = get_text('term', { term => 'bug' });
  209. my $bug_re = qr/\Q$bug_word\E\s*\#?\s*(\d+)/i;
  210. my $comment_re = qr/comment\s*\#?\s*(\d+)/i;
  211. $text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
  212. ~ # We have several choices. $1 here is the link, and $2-4 are set
  213. # depending on which part matched
  214. (defined($2) ? get_bug_link($2,$1,$3) :
  215. "<a href=\"$current_bugurl#c$4\">$1</a>")
  216. ~egox;
  217. # Old duplicate markers. These don't use $bug_word because they are old
  218. # and were never customizable.
  219. $text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
  220. (\d+)
  221. (?=\ \*\*\*\Z)
  222. ~get_bug_link($1, $1)
  223. ~egmx;
  224. # Now remove the encoding hacks
  225. $text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
  226. $text =~ s/$chr1\0/\0/g;
  227. return $text;
  228. }
  229. # Creates a link to an attachment, including its title.
  230. sub get_attachment_link {
  231. my ($attachid, $link_text) = @_;
  232. my $dbh = Bugzilla->dbh;
  233. detaint_natural($attachid)
  234. || die "get_attachment_link() called with non-integer attachment number";
  235. my ($bugid, $isobsolete, $desc) =
  236. $dbh->selectrow_array('SELECT bug_id, isobsolete, description
  237. FROM attachments WHERE attach_id = ?',
  238. undef, $attachid);
  239. if ($bugid) {
  240. my $title = "";
  241. my $className = "";
  242. if (Bugzilla->user->can_see_bug($bugid)) {
  243. $title = $desc;
  244. }
  245. if ($isobsolete) {
  246. $className = "bz_obsolete";
  247. }
  248. # Prevent code injection in the title.
  249. $title = html_quote(clean_text($title));
  250. $link_text =~ s/ \[details\]$//;
  251. my $linkval = "attachment.cgi?id=$attachid";
  252. # Whitespace matters here because these links are in <pre> tags.
  253. return qq|<span class="$className">|
  254. . qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
  255. . qq| <a href="${linkval}&amp;action=edit" title="$title">[details]</a>|
  256. . qq|</span>|;
  257. }
  258. else {
  259. return qq{$link_text};
  260. }
  261. }
  262. # Creates a link to a bug, including its title.
  263. # It takes either two or three parameters:
  264. # - The bug number
  265. # - The link text, to place between the <a>..</a>
  266. # - An optional comment number, for linking to a particular
  267. # comment in the bug
  268. sub get_bug_link {
  269. my ($bug_num, $link_text, $comment_num) = @_;
  270. my $dbh = Bugzilla->dbh;
  271. if (!defined($bug_num) || ($bug_num eq "")) {
  272. return "&lt;missing bug number&gt;";
  273. }
  274. my $quote_bug_num = html_quote($bug_num);
  275. detaint_natural($bug_num) || return "&lt;invalid bug number: $quote_bug_num&gt;";
  276. my ($bug_state, $bug_res, $bug_desc) =
  277. $dbh->selectrow_array('SELECT bugs.bug_status, resolution, short_desc
  278. FROM bugs WHERE bugs.bug_id = ?',
  279. undef, $bug_num);
  280. if ($bug_state) {
  281. # Initialize these variables to be "" so that we don't get warnings
  282. # if we don't change them below (which is highly likely).
  283. my ($pre, $title, $post) = ("", "", "");
  284. $title = get_text('get_status', {status => $bug_state});
  285. if ($bug_state eq 'UNCONFIRMED') {
  286. $pre = "<i>";
  287. $post = "</i>";
  288. }
  289. elsif (!is_open_state($bug_state)) {
  290. $pre = '<span class="bz_closed">';
  291. $title .= ' ' . get_text('get_resolution', {resolution => $bug_res});
  292. $post = '</span>';
  293. }
  294. if (Bugzilla->user->can_see_bug($bug_num)) {
  295. $title .= " - $bug_desc";
  296. }
  297. # Prevent code injection in the title.
  298. $title = html_quote(clean_text($title));
  299. my $linkval = "show_bug.cgi?id=$bug_num";
  300. if (defined $comment_num) {
  301. $linkval .= "#c$comment_num";
  302. }
  303. return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
  304. }
  305. else {
  306. return qq{$link_text};
  307. }
  308. }
  309. ###############################################################################
  310. # Templatization Code
  311. # The Template Toolkit throws an error if a loop iterates >1000 times.
  312. # We want to raise that limit.
  313. # NOTE: If you change this number, you MUST RE-RUN checksetup.pl!!!
  314. # If you do not re-run checksetup.pl, the change you make will not apply
  315. $Template::Directive::WHILE_MAX = 1000000;
  316. # Use the Toolkit Template's Stash module to add utility pseudo-methods
  317. # to template variables.
  318. use Template::Stash;
  319. # Add "contains***" methods to list variables that search for one or more
  320. # items in a list and return boolean values representing whether or not
  321. # one/all/any item(s) were found.
  322. $Template::Stash::LIST_OPS->{ contains } =
  323. sub {
  324. my ($list, $item) = @_;
  325. return grep($_ eq $item, @$list);
  326. };
  327. $Template::Stash::LIST_OPS->{ containsany } =
  328. sub {
  329. my ($list, $items) = @_;
  330. foreach my $item (@$items) {
  331. return 1 if grep($_ eq $item, @$list);
  332. }
  333. return 0;
  334. };
  335. # Clone the array reference to leave the original one unaltered.
  336. $Template::Stash::LIST_OPS->{ clone } =
  337. sub {
  338. my $list = shift;
  339. return [@$list];
  340. };
  341. # Allow us to still get the scalar if we use the list operation ".0" on it,
  342. # as we often do for defaults in query.cgi and other places.
  343. $Template::Stash::SCALAR_OPS->{ 0 } =
  344. sub {
  345. return $_[0];
  346. };
  347. # Add a "substr" method to the Template Toolkit's "scalar" object
  348. # that returns a substring of a string.
  349. $Template::Stash::SCALAR_OPS->{ substr } =
  350. sub {
  351. my ($scalar, $offset, $length) = @_;
  352. return substr($scalar, $offset, $length);
  353. };
  354. # Add a "truncate" method to the Template Toolkit's "scalar" object
  355. # that truncates a string to a certain length.
  356. $Template::Stash::SCALAR_OPS->{ truncate } =
  357. sub {
  358. my ($string, $length, $ellipsis) = @_;
  359. $ellipsis ||= "";
  360. return $string if !$length || length($string) <= $length;
  361. my $strlen = $length - length($ellipsis);
  362. my $newstr = substr($string, 0, $strlen) . $ellipsis;
  363. return $newstr;
  364. };
  365. # Create the template object that processes templates and specify
  366. # configuration parameters that apply to all templates.
  367. ###############################################################################
  368. # Construct the Template object
  369. # Note that all of the failure cases here can't use templateable errors,
  370. # since we won't have a template to use...
  371. sub create {
  372. my $class = shift;
  373. my %opts = @_;
  374. # checksetup.pl will call us once for any template/lang directory.
  375. # We need a possibility to reset the cache, so that no files from
  376. # the previous language pollute the action.
  377. if ($opts{'clean_cache'}) {
  378. delete Bugzilla->request_cache->{template_include_path_};
  379. }
  380. # IMPORTANT - If you make any configuration changes here, make sure to
  381. # make them in t/004.template.t and checksetup.pl.
  382. return $class->new({
  383. # Colon-separated list of directories containing templates.
  384. INCLUDE_PATH => [\&getTemplateIncludePath],
  385. # Remove white-space before template directives (PRE_CHOMP) and at the
  386. # beginning and end of templates and template blocks (TRIM) for better
  387. # looking, more compact content. Use the plus sign at the beginning
  388. # of directives to maintain white space (i.e. [%+ DIRECTIVE %]).
  389. PRE_CHOMP => 1,
  390. TRIM => 1,
  391. COMPILE_DIR => bz_locations()->{'datadir'} . "/template",
  392. # Initialize templates (f.e. by loading plugins like Hook).
  393. PRE_PROCESS => "global/initialize.none.tmpl",
  394. # Functions for processing text within templates in various ways.
  395. # IMPORTANT! When adding a filter here that does not override a
  396. # built-in filter, please also add a stub filter to t/004template.t.
  397. FILTERS => {
  398. # Render text in required style.
  399. inactive => [
  400. sub {
  401. my($context, $isinactive) = @_;
  402. return sub {
  403. return $isinactive ? '<span class="bz_inactive">'.$_[0].'</span>' : $_[0];
  404. }
  405. }, 1
  406. ],
  407. closed => [
  408. sub {
  409. my($context, $isclosed) = @_;
  410. return sub {
  411. return $isclosed ? '<span class="bz_closed">'.$_[0].'</span>' : $_[0];
  412. }
  413. }, 1
  414. ],
  415. obsolete => [
  416. sub {
  417. my($context, $isobsolete) = @_;
  418. return sub {
  419. return $isobsolete ? '<span class="bz_obsolete">'.$_[0].'</span>' : $_[0];
  420. }
  421. }, 1
  422. ],
  423. # Returns the text with backslashes, single/double quotes,
  424. # and newlines/carriage returns escaped for use in JS strings.
  425. js => sub {
  426. my ($var) = @_;
  427. $var =~ s/([\\\'\"\/])/\\$1/g;
  428. $var =~ s/\n/\\n/g;
  429. $var =~ s/\r/\\r/g;
  430. $var =~ s/\@/\\x40/g; # anti-spam for email addresses
  431. return $var;
  432. },
  433. # Converts data to base64
  434. base64 => sub {
  435. my ($data) = @_;
  436. return encode_base64($data);
  437. },
  438. # HTML collapses newlines in element attributes to a single space,
  439. # so form elements which may have whitespace (ie comments) need
  440. # to be encoded using &#013;
  441. # See bugs 4928, 22983 and 32000 for more details
  442. html_linebreak => sub {
  443. my ($var) = @_;
  444. $var =~ s/\r\n/\&#013;/g;
  445. $var =~ s/\n\r/\&#013;/g;
  446. $var =~ s/\r/\&#013;/g;
  447. $var =~ s/\n/\&#013;/g;
  448. return $var;
  449. },
  450. # Prevents line break on hyphens and whitespaces.
  451. no_break => sub {
  452. my ($var) = @_;
  453. $var =~ s/ /\&nbsp;/g;
  454. $var =~ s/-/\&#8209;/g;
  455. return $var;
  456. },
  457. xml => \&Bugzilla::Util::xml_quote ,
  458. # This filter escapes characters in a variable or value string for
  459. # use in a query string. It escapes all characters NOT in the
  460. # regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
  461. # a full URL that may have characters that need encoding.
  462. url_quote => \&Bugzilla::Util::url_quote ,
  463. # This filter is similar to url_quote but used a \ instead of a %
  464. # as prefix. In addition it replaces a ' ' by a '_'.
  465. css_class_quote => \&Bugzilla::Util::css_class_quote ,
  466. quoteUrls => [ sub {
  467. my ($context, $bug) = @_;
  468. return sub {
  469. my $text = shift;
  470. return quoteUrls($text, $bug);
  471. };
  472. },
  473. 1
  474. ],
  475. bug_link => [ sub {
  476. my ($context, $bug) = @_;
  477. return sub {
  478. my $text = shift;
  479. return get_bug_link($bug, $text);
  480. };
  481. },
  482. 1
  483. ],
  484. bug_list_link => sub
  485. {
  486. my $buglist = shift;
  487. return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
  488. },
  489. # In CSV, quotes are doubled, and any value containing a quote or a
  490. # comma is enclosed in quotes.
  491. csv => sub
  492. {
  493. my ($var) = @_;
  494. $var =~ s/\"/\"\"/g;
  495. if ($var !~ /^-?(\d+\.)?\d*$/) {
  496. $var = "\"$var\"";
  497. }
  498. return $var;
  499. } ,
  500. # Format a filesize in bytes to a human readable value
  501. unitconvert => sub
  502. {
  503. my ($data) = @_;
  504. my $retval = "";
  505. my %units = (
  506. 'KB' => 1024,
  507. 'MB' => 1024 * 1024,
  508. 'GB' => 1024 * 1024 * 1024,
  509. );
  510. if ($data < 1024) {
  511. return "$data bytes";
  512. }
  513. else {
  514. my $u;
  515. foreach $u ('GB', 'MB', 'KB') {
  516. if ($data >= $units{$u}) {
  517. return sprintf("%.2f %s", $data/$units{$u}, $u);
  518. }
  519. }
  520. }
  521. },
  522. # Format a time for display (more info in Bugzilla::Util)
  523. time => \&Bugzilla::Util::format_time,
  524. # Bug 120030: Override html filter to obscure the '@' in user
  525. # visible strings.
  526. # Bug 319331: Handle BiDi disruptions.
  527. html => sub {
  528. my ($var) = Template::Filters::html_filter(@_);
  529. # Obscure '@'.
  530. $var =~ s/\@/\&#64;/g;
  531. if (Bugzilla->params->{'utf8'}) {
  532. # Remove the following characters because they're
  533. # influencing BiDi:
  534. # --------------------------------------------------------
  535. # |Code |Name |UTF-8 representation|
  536. # |------|--------------------------|--------------------|
  537. # |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
  538. # |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
  539. # |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
  540. # |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
  541. # |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
  542. # --------------------------------------------------------
  543. #
  544. # The following are characters influencing BiDi, too, but
  545. # they can be spared from filtering because they don't
  546. # influence more than one character right or left:
  547. # --------------------------------------------------------
  548. # |Code |Name |UTF-8 representation|
  549. # |------|--------------------------|--------------------|
  550. # |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
  551. # |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
  552. # --------------------------------------------------------
  553. $var =~ s/[\x{202a}-\x{202e}]//g;
  554. }
  555. return $var;
  556. },
  557. html_light => \&Bugzilla::Util::html_light_quote,
  558. # iCalendar contentline filter
  559. ics => [ sub {
  560. my ($context, @args) = @_;
  561. return sub {
  562. my ($var) = shift;
  563. my ($par) = shift @args;
  564. my ($output) = "";
  565. $var =~ s/[\r\n]/ /g;
  566. $var =~ s/([;\\\",])/\\$1/g;
  567. if ($par) {
  568. $output = sprintf("%s:%s", $par, $var);
  569. } else {
  570. $output = $var;
  571. }
  572. $output =~ s/(.{75,75})/$1\n /g;
  573. return $output;
  574. };
  575. },
  576. 1
  577. ],
  578. # Note that using this filter is even more dangerous than
  579. # using "none," and you should only use it when you're SURE
  580. # the output won't be displayed directly to a web browser.
  581. txt => sub {
  582. my ($var) = @_;
  583. # Trivial HTML tag remover
  584. $var =~ s/<[^>]*>//g;
  585. # And this basically reverses the html filter.
  586. $var =~ s/\&#64;/@/g;
  587. $var =~ s/\&lt;/</g;
  588. $var =~ s/\&gt;/>/g;
  589. $var =~ s/\&quot;/\"/g;
  590. $var =~ s/\&amp;/\&/g;
  591. return $var;
  592. },
  593. # Wrap a displayed comment to the appropriate length
  594. wrap_comment => [
  595. sub {
  596. my ($context, $cols) = @_;
  597. return sub { wrap_comment($_[0], $cols) }
  598. }, 1],
  599. # We force filtering of every variable in key security-critical
  600. # places; we have a none filter for people to use when they
  601. # really, really don't want a variable to be changed.
  602. none => sub { return $_[0]; } ,
  603. },
  604. PLUGIN_BASE => 'Bugzilla::Template::Plugin',
  605. CONSTANTS => _load_constants(),
  606. # Default variables for all templates
  607. VARIABLES => {
  608. # Function for retrieving global parameters.
  609. 'Param' => sub { return Bugzilla->params->{$_[0]}; },
  610. # Function to create date strings
  611. 'time2str' => \&Date::Format::time2str,
  612. # Generic linear search function
  613. 'lsearch' => \&Bugzilla::Util::lsearch,
  614. # Currently logged in user, if any
  615. # If an sudo session is in progress, this is the user we're faking
  616. 'user' => sub { return Bugzilla->user; },
  617. # If an sudo session is in progress, this is the user who
  618. # started the session.
  619. 'sudoer' => sub { return Bugzilla->sudoer; },
  620. # SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
  621. 'SendBugMail' => sub {
  622. my ($id, $mailrecipients) = (@_);
  623. require Bugzilla::BugMail;
  624. Bugzilla::BugMail::Send($id, $mailrecipients);
  625. },
  626. # Allow templates to access the "corect" URLBase value
  627. 'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
  628. # Allow templates to access docs url with users' preferred language
  629. 'docs_urlbase' => sub {
  630. my ($language) = include_languages();
  631. my $docs_urlbase = Bugzilla->params->{'docs_urlbase'};
  632. $docs_urlbase =~ s/\%lang\%/$language/;
  633. return $docs_urlbase;
  634. },
  635. # Allow templates to generate a token themselves.
  636. 'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
  637. # These don't work as normal constants.
  638. DB_MODULE => \&Bugzilla::Constants::DB_MODULE,
  639. REQUIRED_MODULES =>
  640. \&Bugzilla::Install::Requirements::REQUIRED_MODULES,
  641. OPTIONAL_MODULES => sub {
  642. my @optional = @{OPTIONAL_MODULES()};
  643. @optional = sort {$a->{feature} cmp $b->{feature}}
  644. @optional;
  645. return \@optional;
  646. },
  647. },
  648. }) || die("Template creation failed: " . $class->error());
  649. }
  650. # Used as part of the two subroutines below.
  651. our (%_templates_to_precompile, $_current_path);
  652. sub precompile_templates {
  653. my ($output) = @_;
  654. # Remove the compiled templates.
  655. my $datadir = bz_locations()->{'datadir'};
  656. if (-e "$datadir/template") {
  657. print install_string('template_removing_dir') . "\n" if $output;
  658. # XXX This frequently fails if the webserver made the files, because
  659. # then the webserver owns the directories. We could fix that by
  660. # doing a chmod/chown on all the directories here.
  661. rmtree("$datadir/template");
  662. # Check that the directory was really removed
  663. if(-e "$datadir/template") {
  664. print "\n\n";
  665. print "The directory '$datadir/template' could not be removed.\n";
  666. print "Please remove it manually and rerun checksetup.pl.\n\n";
  667. exit;
  668. }
  669. }
  670. print install_string('template_precompile') if $output;
  671. my $templatedir = bz_locations()->{'templatedir'};
  672. # Don't hang on templates which use the CGI library
  673. eval("use CGI qw(-no_debug)");
  674. my $dir_reader = new IO::Dir($templatedir) || die "$templatedir: $!";
  675. my @language_dirs = grep { /^[a-z-]+$/i } $dir_reader->read;
  676. $dir_reader->close;
  677. foreach my $dir (@language_dirs) {
  678. next if ($dir eq 'CVS');
  679. -d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom"
  680. || next;
  681. local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
  682. my $template = Bugzilla::Template->create(clean_cache => 1);
  683. # Precompile all the templates found in all the directories.
  684. %_templates_to_precompile = ();
  685. foreach my $subdir (qw(custom extension default), bz_locations()->{'project'}) {
  686. next unless $subdir; # If 'project' is empty.
  687. $_current_path = File::Spec->catdir($templatedir, $dir, $subdir);
  688. next unless -d $_current_path;
  689. # Traverse the template hierarchy.
  690. find({ wanted => \&_precompile_push, no_chdir => 1 }, $_current_path);
  691. }
  692. # The sort isn't totally necessary, but it makes debugging easier
  693. # by making the templates always be compiled in the same order.
  694. foreach my $file (sort keys %_templates_to_precompile) {
  695. # Compile the template but throw away the result. This has the side-
  696. # effect of writing the compiled version to disk.
  697. $template->context->template($file);
  698. }
  699. }
  700. # Under mod_perl, we look for templates using the absolute path of the
  701. # template directory, which causes Template Toolkit to look for their
  702. # *compiled* versions using the full absolute path under the data/template
  703. # directory. (Like data/template/var/www/html/mod_perl/.) To avoid
  704. # re-compiling templates under mod_perl, we symlink to the
  705. # already-compiled templates. This doesn't work on Windows.
  706. if (!ON_WINDOWS) {
  707. my $abs_root = dirname(abs_path($templatedir));
  708. my $todir = "$datadir/template$abs_root";
  709. mkpath($todir);
  710. # We use abs2rel so that the symlink will look like
  711. # "../../../../template" which works, while just
  712. # "data/template/template/" doesn't work.
  713. my $fromdir = File::Spec->abs2rel("$datadir/template/template", $todir);
  714. # We eval for systems that can't symlink at all, where "symlink"
  715. # throws a fatal error.
  716. eval { symlink($fromdir, "$todir/template")
  717. or warn "Failed to symlink from $fromdir to $todir: $!" };
  718. }
  719. # If anything created a Template object before now, clear it out.
  720. delete Bugzilla->request_cache->{template};
  721. # This is the single variable used to precompile templates,
  722. # which needs to be cleared as well.
  723. delete Bugzilla->request_cache->{template_include_path_};
  724. print install_string('done') . "\n" if $output;
  725. }
  726. # Helper for precompile_templates
  727. sub _precompile_push {
  728. my $name = $File::Find::name;
  729. return if (-d $name);
  730. return if ($name =~ /\/CVS\//);
  731. return if ($name !~ /\.tmpl$/);
  732. $name =~ s/\Q$_current_path\E\///;
  733. $_templates_to_precompile{$name} = 1;
  734. }
  735. 1;
  736. __END__
  737. =head1 NAME
  738. Bugzilla::Template - Wrapper around the Template Toolkit C<Template> object
  739. =head1 SYNOPSIS
  740. my $template = Bugzilla::Template->create;
  741. my $format = $template->get_format("foo/bar",
  742. scalar($cgi->param('format')),
  743. scalar($cgi->param('ctype')));
  744. =head1 DESCRIPTION
  745. This is basically a wrapper so that the correct arguments get passed into
  746. the C<Template> constructor.
  747. It should not be used directly by scripts or modules - instead, use
  748. C<Bugzilla-E<gt>instance-E<gt>template> to get an already created module.
  749. =head1 SUBROUTINES
  750. =over
  751. =item C<precompile_templates($output)>
  752. Description: Compiles all of Bugzilla's templates in every language.
  753. Used mostly by F<checksetup.pl>.
  754. Params: C<$output> - C<true> if you want the function to print
  755. out information about what it's doing.
  756. Returns: nothing
  757. =back
  758. =head1 METHODS
  759. =over
  760. =item C<get_format($file, $format, $ctype)>
  761. Description: Construct a format object from URL parameters.
  762. Params: $file - Name of the template to display.
  763. $format - When the template exists under several formats
  764. (e.g. table or graph), specify the one to choose.
  765. $ctype - Content type, see Bugzilla::Constants::contenttypes.
  766. Returns: A format object.
  767. =back
  768. =head1 SEE ALSO
  769. L<Bugzilla>, L<Template>