summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authormkanat%bugzilla.org <>2007-03-17 22:13:34 +0100
committermkanat%bugzilla.org <>2007-03-17 22:13:34 +0100
commitba5a5c404b8ef0b71efd873e604184ab90ebff94 (patch)
tree1e6369d1daf4649bec1e44e8917978a071764cd7 /Bugzilla
parentaf5161076a754d2d66ba8178d4fcce331eedd588 (diff)
downloadbugzilla-ba5a5c404b8ef0b71efd873e604184ab90ebff94.tar.gz
bugzilla-ba5a5c404b8ef0b71efd873e604184ab90ebff94.tar.xz
Bug 374227: Create a system for localizing basic installation strings
Patch By Max Kanat-Alexander <mkanat@bugzilla.org> (module owner) a=mkanat
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Install/Util.pm253
1 files changed, 250 insertions, 3 deletions
diff --git a/Bugzilla/Install/Util.pm b/Bugzilla/Install/Util.pm
index 16f8f3e04..b02c435e5 100644
--- a/Bugzilla/Install/Util.pm
+++ b/Bugzilla/Install/Util.pm
@@ -28,18 +28,20 @@ use strict;
use Bugzilla::Constants;
+use File::Basename;
use POSIX ();
+use Safe;
use base qw(Exporter);
our @EXPORT_OK = qw(
display_version_and_os
indicate_progress
+ install_string
vers_cmp
);
sub display_version_and_os {
# Display version information
- printf "\n* This is Bugzilla " . BUGZILLA_VERSION . " on perl %vd\n", $^V;
my @os_details = POSIX::uname;
# 0 is the name of the OS, 2 is the major version,
my $os_name = $os_details[0] . ' ' . $os_details[2];
@@ -47,8 +49,12 @@ sub display_version_and_os {
require Win32;
$os_name = Win32::GetOSName();
}
- # 3 is the minor version.
- print "* Running on $os_name $os_details[3]\n"
+ # $os_details[3] is the minor version.
+ print install_string('version_and_os', { bz_ver => BUGZILLA_VERSION,
+ perl_ver => sprintf('%vd', $^V),
+ os_name => $os_name,
+ os_ver => $os_details[3] })
+ . "\n";
}
sub indicate_progress {
@@ -63,6 +69,126 @@ sub indicate_progress {
}
}
+sub install_string {
+ my ($string_id, $vars) = @_;
+ _cache()->{template_include_path} ||= template_include_path();
+ my $path = _cache()->{template_include_path};
+
+ my $string_template;
+ # Find the first set of templates that defines this string.
+ foreach my $dir (@$path) {
+ my $file = "$dir/setup/strings.txt.pl";
+ next unless -e $file;
+ my $safe = new Safe;
+ $safe->rdo($file);
+ my %strings = %{$safe->varglob('strings')};
+ $string_template = $strings{$string_id};
+ last if $string_template;
+ }
+
+ die "No language defines the string '$string_id'" if !$string_template;
+
+ $vars ||= {};
+ my @replace_keys = keys %$vars;
+ foreach my $key (@replace_keys) {
+ my $replacement = $vars->{$key};
+ die "'$key' in '$string_id' is tainted: '$replacement'"
+ if is_tainted($replacement);
+ # We don't want people to start getting clever and inserting
+ # ##variable## into their values. So we check if any other
+ # key is listed in the *replacement* string, before doing
+ # the replacement. This is mostly to protect programmers from
+ # making mistakes.
+ if (grep($replacement =~ /##$key##/, @replace_keys)) {
+ die "Unsafe replacement for '$key' in '$string_id': '$replacement'";
+ }
+ $string_template =~ s/\Q##$key##\E/$replacement/g;
+ }
+
+ return $string_template;
+}
+
+sub template_include_path {
+ my ($params) = @_;
+ $params ||= {};
+
+ # Basically, the way this works is that we have a list of languages
+ # that we *want*, and a list of languages that Bugzilla actually
+ # supports. The caller tells us what languages they want, by setting
+ # $ENV{HTTP_ACCEPT_LANGUAGE} or $params->{only_language}. The languages
+ # we support are those specified in $params->{use_languages}. Otherwise
+ # we support every language installed in the template/ directory.
+
+ my @wanted;
+ if (defined $params->{only_language}) {
+ @wanted = ($params->{only_language});
+ }
+ else {
+ @wanted = _sort_accept_language($ENV{'HTTP_ACCEPT_LANGUAGE'} || '');
+ }
+
+ my @supported;
+ if (defined $params->{use_languages}) {
+ @supported = $params->{use_languages};
+ }
+ else {
+ my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
+ @dirs = map(basename($_), @dirs);
+ @supported = grep($_ ne 'CVS', @dirs);
+ }
+
+ my @usedlanguages;
+ foreach my $wanted (@wanted) {
+ # If we support the language we want, or *any version* of
+ # the language we want, it gets pushed into @usedlanguages.
+ #
+ # Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and
+ # 'en-uk', but not the other way around. (This is unfortunately
+ # not very clearly stated in those RFC; see comment just over 14.5
+ # in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
+ if(my @found = grep /^\Q$wanted\E(-.+)?$/i, @supported) {
+ push (@usedlanguages, @found);
+ }
+ }
+
+ # If we didn't want *any* of the languages we support, just use all
+ # of the languages we said we support, in the order they were specified.
+ # This is only done when you ask for a certain set of languages, because
+ # otherwise @supported just came off the disk in alphabetical order,
+ # and it could give you de (German) when you speak English.
+ # (If @supported came off the disk, we fall back on English if no language
+ # is available--that happens below.)
+ if (!@usedlanguages && $params->{use_languages}) {
+ @usedlanguages = @supported;
+ }
+
+ # We always include English at the bottom if it's not there, even if
+ # somebody removed it from use_languages.
+ if (!grep($_ eq 'en', @usedlanguages)) {
+ push(@usedlanguages, 'en');
+ }
+
+ # Now, we add template directories in the order they will be searched:
+
+ # First, we add extension template directories, because extension templates
+ # override standard templates. Extensions may be localized in the same way
+ # that Bugzilla templates are localized.
+ my @include_path;
+ my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
+ foreach my $extension (@extensions) {
+ foreach my $lang (@usedlanguages) {
+ _add_language_set(\@include_path, $lang, "$extension/template");
+ }
+ }
+
+ # Then, we add normal template directories, sorted by language.
+ foreach my $lang (@usedlanguages) {
+ _add_language_set(\@include_path, $lang);
+ }
+
+ return \@include_path;
+}
+
# This is taken straight from Sort::Versions 1.5, which is not included
# with perl by default.
sub vers_cmp {
@@ -106,6 +232,81 @@ sub vers_cmp {
@A <=> @B;
}
+######################
+# Helper Subroutines #
+######################
+
+# Used by template_include_path.
+sub _add_language_set {
+ my ($array, $lang, $templatedir) = @_;
+
+ $templatedir ||= bz_locations()->{'templatedir'};
+ my @add = ("$templatedir/$lang/custom", "$templatedir/$lang/default");
+
+ my $project = bz_locations->{'project'};
+ push(@add, "$templatedir/$lang/$project") if $project;
+
+ foreach my $dir (@add) {
+ #if (-d $dir) {
+ trick_taint($dir);
+ push(@$array, $dir);
+ #}
+ }
+}
+
+# Make an ordered list out of a HTTP Accept-Language header (see RFC 2616, 14.4)
+# We ignore '*' and <language-range>;q=0
+# For languages with the same priority q the order remains unchanged.
+sub _sort_accept_language {
+ sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} }
+ my $accept_language = $_[0];
+
+ # clean up string.
+ $accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g;
+ my @qlanguages;
+ my @languages;
+ foreach(split /,/, $accept_language) {
+ if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) {
+ my $lang = $1;
+ my $qvalue = $2;
+ $qvalue = 1 if not defined $qvalue;
+ next if $qvalue == 0;
+ $qvalue = 1 if $qvalue > 1;
+ push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang});
+ }
+ }
+
+ return map($_->{'language'}, (sort sortQvalue @qlanguages));
+}
+
+
+# This is like request_cache, but it's used only by installation code
+# for setup.cgi and things like that.
+our $_cache = {};
+sub _cache {
+ if ($ENV{MOD_PERL}) {
+ require Apache2::RequestUtil;
+ return Apache2::RequestUtil->request->pnotes();
+ }
+ return $_cache;
+}
+
+###############################
+# Copied from Bugzilla::Util #
+##############################
+
+sub trick_taint {
+ require Carp;
+ Carp::confess("Undef to trick_taint") unless defined $_[0];
+ my $match = $_[0] =~ /^(.*)$/s;
+ $_[0] = $match ? $1 : undef;
+ return (defined($_[0]));
+}
+
+sub is_tainted {
+ return not eval { my $foo = join('',@_), kill 0; 1; };
+}
+
__END__
=head1 NAME
@@ -173,6 +374,52 @@ ten items. Defaults to 1 if not specified.
=back
+=item C<install_string>
+
+=over
+
+=item B<Description>
+
+This is a very simple method of templating strings for installation.
+It should only be used by code that has to run before the Template Toolkit
+can be used. (See the comments at the top of the various L<Bugzilla::Install>
+modules to find out when it's safe to use Template Toolkit.)
+
+It pulls strings out of the F<strings.txt.pl> "template" and replaces
+any variable surrounded by double-hashes (##) with a value you specify.
+
+This allows for localization of strings used during installation.
+
+=item B<Example>
+
+Let's say your template string looks like this:
+
+ The ##animal## jumped over the ##plant##.
+
+Let's say that string is called 'animal_jump_plant'. So you call the function
+like this:
+
+ install_string('animal_jump_plant', { animal => 'fox', plant => 'tree' });
+
+That will output this:
+
+ The fox jumped over the tree.
+
+=item B<Params>
+
+=over
+
+=item C<$string_id> - The name of the string from F<strings.txt.pl>.
+
+=item C<$vars> - A hashref containing the replacement values for variables
+inside of the string.
+
+=back
+
+=item B<Returns>: The appropriate string, with variables replaced.
+
+=back
+
=item C<vers_cmp>
=over