552 lines
16 KiB
Perl
552 lines
16 KiB
Perl
# -*- 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 Everything Solved.
|
|
# Portions created by Everything Solved are Copyright (C) 2006
|
|
# Everything Solved. All Rights Reserved.
|
|
#
|
|
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
|
|
|
package Bugzilla::Install::Util;
|
|
|
|
# The difference between this module and Bugzilla::Util is that this
|
|
# module may require *only* Bugzilla::Constants and built-in
|
|
# perl modules.
|
|
|
|
use strict;
|
|
|
|
use Bugzilla::Constants;
|
|
|
|
use File::Basename;
|
|
use POSIX qw(setlocale LC_CTYPE);
|
|
use Safe;
|
|
use Scalar::Util qw(tainted);
|
|
|
|
use base qw(Exporter);
|
|
our @EXPORT_OK = qw(
|
|
bin_loc
|
|
get_version_and_os
|
|
indicate_progress
|
|
install_string
|
|
include_languages
|
|
template_include_path
|
|
vers_cmp
|
|
get_console_locale
|
|
);
|
|
|
|
sub bin_loc {
|
|
my ($bin) = @_;
|
|
return '' if ON_WINDOWS;
|
|
# Don't print any errors from "which"
|
|
open(my $saveerr, ">&STDERR");
|
|
open(STDERR, '>/dev/null');
|
|
my $loc = `which $bin`;
|
|
close(STDERR);
|
|
open(STDERR, ">&", $saveerr);
|
|
my $exit_code = $? >> 8; # See the perlvar manpage.
|
|
return '' if $exit_code > 0;
|
|
chomp($loc);
|
|
return $loc;
|
|
}
|
|
|
|
sub get_version_and_os {
|
|
# Display version information
|
|
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];
|
|
if (ON_WINDOWS) {
|
|
require Win32;
|
|
$os_name = Win32::GetOSName();
|
|
}
|
|
# $os_details[3] is the minor version.
|
|
return { bz_ver => BUGZILLA_VERSION,
|
|
perl_ver => sprintf('%vd', $^V),
|
|
os_name => $os_name,
|
|
os_ver => $os_details[3] };
|
|
}
|
|
|
|
sub indicate_progress {
|
|
my ($params) = @_;
|
|
my $current = $params->{current};
|
|
my $total = $params->{total};
|
|
my $every = $params->{every} || 1;
|
|
|
|
print "." if !($current % $every);
|
|
if ($current == $total || $current % ($every * 60) == 0) {
|
|
print "$current/$total (" . int($current * 100 / $total) . "%)\n";
|
|
}
|
|
}
|
|
|
|
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 template that defines this string.
|
|
foreach my $dir (@$path) {
|
|
my $base = "$dir/setup/strings";
|
|
$string_template = _get_string_from_file($string_id, "$base.txt.pl")
|
|
if !defined $string_template;
|
|
last if defined $string_template;
|
|
}
|
|
|
|
die "No language defines the string '$string_id'"
|
|
if !defined $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 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 include_languages {
|
|
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 ($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);
|
|
}
|
|
}
|
|
|
|
# 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');
|
|
}
|
|
|
|
return @usedlanguages;
|
|
}
|
|
|
|
sub template_include_path {
|
|
my @usedlanguages = include_languages(@_);
|
|
# 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) {
|
|
next if -e "$extension/disabled";
|
|
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 {
|
|
my ($a, $b) = @_;
|
|
|
|
# Remove leading zeroes - Bug 344661
|
|
$a =~ s/^0*(\d.+)/$1/;
|
|
$b =~ s/^0*(\d.+)/$1/;
|
|
|
|
my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g);
|
|
my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g);
|
|
|
|
my ($A, $B);
|
|
while (@A and @B) {
|
|
$A = shift @A;
|
|
$B = shift @B;
|
|
if ($A eq '-' and $B eq '-') {
|
|
next;
|
|
} elsif ( $A eq '-' ) {
|
|
return -1;
|
|
} elsif ( $B eq '-') {
|
|
return 1;
|
|
} elsif ($A eq '.' and $B eq '.') {
|
|
next;
|
|
} elsif ( $A eq '.' ) {
|
|
return -1;
|
|
} elsif ( $B eq '.' ) {
|
|
return 1;
|
|
} elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
|
|
if ($A =~ /^0/ || $B =~ /^0/) {
|
|
return $A cmp $B if $A cmp $B;
|
|
} else {
|
|
return $A <=> $B if $A <=> $B;
|
|
}
|
|
} else {
|
|
$A = uc $A;
|
|
$B = uc $B;
|
|
return $A cmp $B if $A cmp $B;
|
|
}
|
|
}
|
|
@A <=> @B;
|
|
}
|
|
|
|
######################
|
|
# Helper Subroutines #
|
|
######################
|
|
|
|
# Used by install_string
|
|
sub _get_string_from_file {
|
|
my ($string_id, $file) = @_;
|
|
|
|
return undef if !-e $file;
|
|
my $safe = new Safe;
|
|
$safe->rdo($file);
|
|
my %strings = %{$safe->varglob('strings')};
|
|
return $strings{$string_id};
|
|
}
|
|
|
|
# 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'};
|
|
unshift(@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));
|
|
}
|
|
|
|
sub get_console_locale {
|
|
require Locale::Language;
|
|
my $locale = setlocale(LC_CTYPE);
|
|
my $language;
|
|
# Some distros set e.g. LC_CTYPE = fr_CH.UTF-8. We clean it up.
|
|
if ($locale =~ /^([^\.]+)/) {
|
|
$locale = $1;
|
|
}
|
|
$locale =~ s/_/-/;
|
|
# It's pretty sure that there is no language pack of the form fr-CH
|
|
# installed, so we also include fr as a wanted language.
|
|
if ($locale =~ /^(\S+)\-/) {
|
|
$language = $1;
|
|
$locale .= ",$language";
|
|
}
|
|
else {
|
|
$language = $locale;
|
|
}
|
|
|
|
# Some OSs or distributions may have setlocale return a string of the form
|
|
# German_Germany.1252 (this example taken from a Windows XP system), which
|
|
# is unsuitable for our needs because Bugzilla works on language codes.
|
|
# We try and convert them here.
|
|
if ($language = Locale::Language::language2code($language)) {
|
|
$locale .= ",$language";
|
|
}
|
|
|
|
return $locale;
|
|
}
|
|
|
|
|
|
# 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]));
|
|
}
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla::Install::Util - Utility functions that are useful both during
|
|
installation and afterwards.
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module contains various subroutines that are used primarily
|
|
during installation. However, these subroutines can also be useful to
|
|
non-installation code, so they have been split out into this module.
|
|
|
|
The difference between this module and L<Bugzilla::Util> is that this
|
|
module is safe to C<use> anywhere in Bugzilla, even during installation,
|
|
because it depends only on L<Bugzilla::Constants> and built-in perl modules.
|
|
|
|
None of the subroutines are exported by default--you must explicitly
|
|
export them.
|
|
|
|
=head1 SUBROUTINES
|
|
|
|
=over
|
|
|
|
=item C<bin_loc>
|
|
|
|
On *nix systems, given the name of a binary, returns the path to that
|
|
binary, if the binary is in the C<PATH>.
|
|
|
|
=item C<get_version_and_os>
|
|
|
|
Returns a hash containing information about what version of Bugzilla we're
|
|
running, what perl version we're using, and what OS we're running on.
|
|
|
|
=item C<get_console_locale>
|
|
|
|
Returns the language to use based on the LC_CTYPE value returned by the OS.
|
|
If LC_CTYPE is of the form fr-CH, then fr is appended to the list.
|
|
|
|
=item C<indicate_progress>
|
|
|
|
=over
|
|
|
|
=item B<Description>
|
|
|
|
This prints out lines of dots as a long update is going on, to let the user
|
|
know where we are and that we're not frozen. A new line of dots will start
|
|
every 60 dots.
|
|
|
|
Sample usage: C<indicate_progress({ total =E<gt> $total, current =E<gt>
|
|
$count, every =E<gt> 1 })>
|
|
|
|
=item B<Sample Output>
|
|
|
|
Here's some sample output with C<total = 1000> and C<every = 10>:
|
|
|
|
............................................................600/1000 (60%)
|
|
........................................
|
|
|
|
=item B<Params>
|
|
|
|
=over
|
|
|
|
=item C<total> - The total number of items we're processing.
|
|
|
|
=item C<current> - The number of the current item we're processing.
|
|
|
|
=item C<every> - How often the function should print out a dot.
|
|
For example, if this is 10, the function will print out a dot every
|
|
ten items. Defaults to 1 if not specified.
|
|
|
|
=back
|
|
|
|
=item B<Returns>: nothing
|
|
|
|
=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<template_include_path>
|
|
|
|
Used by L<Bugzilla::Template> and L</install_string> to determine the
|
|
directories where templates are installed. Templates can be installed
|
|
in many places. They're listed here in the basic order that they're
|
|
searched:
|
|
|
|
=over
|
|
|
|
=item extensions/C<$extension>/template/C<$language>/C<$project>
|
|
|
|
=item extensions/C<$extension>/template/C<$language>/custom
|
|
|
|
=item extensions/C<$extension>/template/C<$language>/default
|
|
|
|
=item template/C<$language>/C<$project>
|
|
|
|
=item template/C<$language>/custom
|
|
|
|
=item template/C<$language>/default
|
|
|
|
=back
|
|
|
|
C<$project> has to do with installations that are using the C<$ENV{PROJECT}>
|
|
variable to have different "views" on a single Bugzilla.
|
|
|
|
The F<default> directory includes templates shipped with Bugzilla.
|
|
|
|
The F<custom> directory is a directory for local installations to override
|
|
the F<default> templates. Any individual template in F<custom> will
|
|
override a template of the same name and path in F<default>.
|
|
|
|
C<$language> is a language code, C<en> being the default language shipped
|
|
with Bugzilla. Localizers ship other languages.
|
|
|
|
C<$extension> is the name of any directory in the F<extensions/> directory.
|
|
Each extension has its own directory.
|
|
|
|
Note that languages are sorted by the user's preference (as specified
|
|
in their browser, usually), and extensions are sorted alphabetically.
|
|
|
|
=item C<include_languages>
|
|
|
|
Used by L<Bugzilla::Template> to determine the languages' list which
|
|
are compiled with the browser's I<Accept-Language> and the languages
|
|
of installed templates.
|
|
|
|
=item C<vers_cmp>
|
|
|
|
=over
|
|
|
|
=item B<Description>
|
|
|
|
This is a comparison function, like you would use in C<sort>, except that
|
|
it compares two version numbers. So, for example, 2.10 would be greater
|
|
than 2.2.
|
|
|
|
It's based on versioncmp from L<Sort::Versions>, with some Bugzilla-specific
|
|
fixes.
|
|
|
|
=item B<Params>: C<$a> and C<$b> - The versions you want to compare.
|
|
|
|
=item B<Returns>
|
|
|
|
C<-1> if C<$a> is less than C<$b>, C<0> if they are equal, or C<1> if C<$a>
|
|
is greater than C<$b>.
|
|
|
|
=back
|
|
|
|
=back
|