1086 lines
34 KiB
Perl
1086 lines
34 KiB
Perl
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
#
|
|
# This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
# defined by the Mozilla Public License, v. 2.0.
|
|
|
|
package Bugzilla;
|
|
|
|
use 5.10.1;
|
|
use strict;
|
|
use warnings;
|
|
|
|
# We want any compile errors to get to the browser, if possible.
|
|
BEGIN {
|
|
# This makes sure we're in a CGI.
|
|
if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
|
|
require CGI::Carp;
|
|
CGI::Carp->import('fatalsToBrowser');
|
|
}
|
|
}
|
|
|
|
use Bugzilla::Auth;
|
|
use Bugzilla::Auth::Persist::Cookie;
|
|
use Bugzilla::CGI;
|
|
use Bugzilla::Config;
|
|
use Bugzilla::Constants;
|
|
use Bugzilla::DB;
|
|
use Bugzilla::Error;
|
|
use Bugzilla::Extension;
|
|
use Bugzilla::Field;
|
|
use Bugzilla::Flag;
|
|
use Bugzilla::Install::Localconfig qw(read_localconfig);
|
|
use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES have_vers);
|
|
use Bugzilla::Install::Util qw(init_console include_languages);
|
|
use Bugzilla::Memcached;
|
|
use Bugzilla::Template;
|
|
use Bugzilla::Token;
|
|
use Bugzilla::User;
|
|
use Bugzilla::Util;
|
|
|
|
use File::Basename;
|
|
use File::Spec::Functions;
|
|
use DateTime::TimeZone;
|
|
use Date::Parse;
|
|
use Safe;
|
|
|
|
#####################################################################
|
|
# Constants
|
|
#####################################################################
|
|
|
|
# Scripts that are not stopped by shutdownhtml being in effect.
|
|
use constant SHUTDOWNHTML_EXEMPT => qw(
|
|
editparams.cgi
|
|
checksetup.pl
|
|
migrate.pl
|
|
recode.pl
|
|
);
|
|
|
|
# Non-cgi scripts that should silently exit.
|
|
use constant SHUTDOWNHTML_EXIT_SILENTLY => qw(
|
|
whine.pl
|
|
);
|
|
|
|
# shutdownhtml pages are sent as an HTTP 503. After how many seconds
|
|
# should search engines attempt to index the page again?
|
|
use constant SHUTDOWNHTML_RETRY_AFTER => 3600;
|
|
|
|
#####################################################################
|
|
# Global Code
|
|
#####################################################################
|
|
|
|
#$::SIG{__DIE__} = i_am_cgi() ? \&CGI::Carp::confess : \&Carp::confess;
|
|
|
|
# Note that this is a raw subroutine, not a method, so $class isn't available.
|
|
sub init_page {
|
|
if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
|
|
init_console();
|
|
}
|
|
elsif (Bugzilla->params->{'utf8'}) {
|
|
binmode STDOUT, ':utf8';
|
|
}
|
|
|
|
if (${^TAINT}) {
|
|
my $path = '';
|
|
if (ON_WINDOWS) {
|
|
# On Windows, these paths are tainted, preventing
|
|
# File::Spec::Win32->tmpdir from using them. But we need
|
|
# a place to temporary store attachments which are uploaded.
|
|
foreach my $temp (qw(TMPDIR TMP TEMP WINDIR)) {
|
|
trick_taint($ENV{$temp}) if $ENV{$temp};
|
|
}
|
|
# Some DLLs used by Strawberry Perl are also in c\bin,
|
|
# see https://rt.cpan.org/Public/Bug/Display.html?id=99104
|
|
if (!ON_ACTIVESTATE) {
|
|
my $c_path = $path = dirname($^X);
|
|
$c_path =~ s/\bperl\b(?=\\bin)/c/;
|
|
$path .= ";$c_path";
|
|
trick_taint($path);
|
|
}
|
|
}
|
|
# Some environment variables are not taint safe
|
|
delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
|
|
# Some modules throw undefined errors (notably File::Spec::Win32) if
|
|
# PATH is undefined.
|
|
$ENV{'PATH'} = $path;
|
|
}
|
|
|
|
# Because this function is run live from perl "use" commands of
|
|
# other scripts, we're skipping the rest of this function if we get here
|
|
# during a perl syntax check (perl -c, like we do during the
|
|
# 001compile.t test).
|
|
return if $^C;
|
|
|
|
# IIS prints out warnings to the webpage, so ignore them, or log them
|
|
# to a file if the file exists.
|
|
if ($ENV{SERVER_SOFTWARE} && $ENV{SERVER_SOFTWARE} =~ /microsoft-iis/i) {
|
|
$SIG{__WARN__} = sub {
|
|
my ($msg) = @_;
|
|
my $datadir = bz_locations()->{'datadir'};
|
|
if (-w "$datadir/errorlog") {
|
|
my $warning_log = new IO::File(">>$datadir/errorlog");
|
|
print $warning_log $msg;
|
|
$warning_log->close();
|
|
}
|
|
};
|
|
}
|
|
|
|
my $script = basename($0);
|
|
|
|
# Because of attachment_base, attachment.cgi handles this itself.
|
|
if ($script ne 'attachment.cgi') {
|
|
do_ssl_redirect_if_required();
|
|
}
|
|
|
|
# If Bugzilla is shut down, do not allow anything to run, just display a
|
|
# message to the user about the downtime and log out. Scripts listed in
|
|
# SHUTDOWNHTML_EXEMPT are exempt from this message.
|
|
#
|
|
# This code must go here. It cannot go anywhere in Bugzilla::CGI, because
|
|
# it uses Template, and that causes various dependency loops.
|
|
if (!grep { $_ eq $script } SHUTDOWNHTML_EXEMPT
|
|
and Bugzilla->params->{'shutdownhtml'})
|
|
{
|
|
# Allow non-cgi scripts to exit silently (without displaying any
|
|
# message), if desired. At this point, no DBI call has been made
|
|
# yet, and no error will be returned if the DB is inaccessible.
|
|
if (!i_am_cgi()
|
|
&& grep { $_ eq $script } SHUTDOWNHTML_EXIT_SILENTLY)
|
|
{
|
|
exit;
|
|
}
|
|
|
|
# For security reasons, log out users when Bugzilla is down.
|
|
# Bugzilla->login() is required to catch the logincookie, if any.
|
|
my $user;
|
|
eval { $user = Bugzilla->login(LOGIN_OPTIONAL); };
|
|
if ($@) {
|
|
# The DB is not accessible. Use the default user object.
|
|
$user = Bugzilla->user;
|
|
$user->{settings} = {};
|
|
}
|
|
my $userid = $user->id;
|
|
Bugzilla->logout();
|
|
|
|
my $template = Bugzilla->template;
|
|
my $vars = {};
|
|
$vars->{'message'} = 'shutdown';
|
|
$vars->{'userid'} = $userid;
|
|
# Generate and return a message about the downtime, appropriately
|
|
# for if we're a command-line script or a CGI script.
|
|
my $extension;
|
|
if (i_am_cgi() && (!Bugzilla->cgi->param('ctype')
|
|
|| Bugzilla->cgi->param('ctype') eq 'html')) {
|
|
$extension = 'html';
|
|
}
|
|
else {
|
|
$extension = 'txt';
|
|
}
|
|
if (i_am_cgi()) {
|
|
# Set the HTTP status to 503 when Bugzilla is down to avoid pages
|
|
# being indexed by search engines.
|
|
print Bugzilla->cgi->header(-status => 503,
|
|
-retry_after => SHUTDOWNHTML_RETRY_AFTER);
|
|
}
|
|
$template->process("global/message.$extension.tmpl", $vars)
|
|
|| ThrowTemplateError($template->error);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
#####################################################################
|
|
# Subroutines and Methods
|
|
#####################################################################
|
|
|
|
sub template {
|
|
return $_[0]->request_cache->{template} ||= Bugzilla::Template->create();
|
|
}
|
|
|
|
sub template_inner {
|
|
my ($class, $lang) = @_;
|
|
my $cache = $class->request_cache;
|
|
my $current_lang = $cache->{template_current_lang}->[0];
|
|
$lang ||= $current_lang || '';
|
|
return $cache->{"template_inner_$lang"} ||= Bugzilla::Template->create(language => $lang);
|
|
}
|
|
|
|
our $extension_packages;
|
|
sub extensions {
|
|
my ($class) = @_;
|
|
my $cache = $class->request_cache;
|
|
if (!$cache->{extensions}) {
|
|
# Under mod_perl, mod_perl.pl populates $extension_packages for us.
|
|
if (!$extension_packages) {
|
|
$extension_packages = Bugzilla::Extension->load_all();
|
|
}
|
|
my @extensions;
|
|
foreach my $package (@$extension_packages) {
|
|
my $extension = $package->new();
|
|
if ($extension->enabled) {
|
|
push(@extensions, $extension);
|
|
}
|
|
}
|
|
$cache->{extensions} = \@extensions;
|
|
}
|
|
return $cache->{extensions};
|
|
}
|
|
|
|
sub feature {
|
|
my ($class, $feature) = @_;
|
|
my $cache = $class->request_cache;
|
|
return $cache->{feature}->{$feature}
|
|
if exists $cache->{feature}->{$feature};
|
|
|
|
my $feature_map = $cache->{feature_map};
|
|
if (!$feature_map) {
|
|
foreach my $package (@{ OPTIONAL_MODULES() }) {
|
|
foreach my $f (@{ $package->{feature} }) {
|
|
$feature_map->{$f} ||= [];
|
|
push(@{ $feature_map->{$f} }, $package);
|
|
}
|
|
}
|
|
$cache->{feature_map} = $feature_map;
|
|
}
|
|
|
|
if (!$feature_map->{$feature}) {
|
|
ThrowCodeError('invalid_feature', { feature => $feature });
|
|
}
|
|
|
|
my $success = 1;
|
|
foreach my $package (@{ $feature_map->{$feature} }) {
|
|
have_vers($package) or $success = 0;
|
|
}
|
|
$cache->{feature}->{$feature} = $success;
|
|
return $success;
|
|
}
|
|
|
|
sub cgi {
|
|
return $_[0]->request_cache->{cgi} ||= new Bugzilla::CGI();
|
|
}
|
|
|
|
sub input_params {
|
|
my ($class, $params) = @_;
|
|
my $cache = $class->request_cache;
|
|
# This is how the WebService and other places set input_params.
|
|
if (defined $params) {
|
|
$cache->{input_params} = $params;
|
|
}
|
|
return $cache->{input_params} if defined $cache->{input_params};
|
|
|
|
# Making this scalar makes it a tied hash to the internals of $cgi,
|
|
# so if a variable is changed, then it actually changes the $cgi object
|
|
# as well.
|
|
$cache->{input_params} = $class->cgi->Vars;
|
|
return $cache->{input_params};
|
|
}
|
|
|
|
sub localconfig {
|
|
return $_[0]->process_cache->{localconfig} ||= read_localconfig();
|
|
}
|
|
|
|
sub params {
|
|
return $_[0]->request_cache->{params} ||= Bugzilla::Config::read_param_file();
|
|
}
|
|
|
|
sub user {
|
|
return $_[0]->request_cache->{user} ||= new Bugzilla::User;
|
|
}
|
|
|
|
sub set_user {
|
|
my ($class, $user) = @_;
|
|
$class->request_cache->{user} = $user;
|
|
}
|
|
|
|
sub sudoer {
|
|
return $_[0]->request_cache->{sudoer};
|
|
}
|
|
|
|
sub sudo_request {
|
|
my ($class, $new_user, $new_sudoer) = @_;
|
|
$class->request_cache->{user} = $new_user;
|
|
$class->request_cache->{sudoer} = $new_sudoer;
|
|
# NOTE: If you want to log the start of an sudo session, do it here.
|
|
}
|
|
|
|
sub page_requires_login {
|
|
return $_[0]->request_cache->{page_requires_login};
|
|
}
|
|
|
|
sub login {
|
|
my ($class, $type) = @_;
|
|
|
|
return $class->user if $class->user->id;
|
|
|
|
my $authorizer = new Bugzilla::Auth();
|
|
$type = LOGIN_REQUIRED if $class->cgi->param('GoAheadAndLogIn');
|
|
|
|
if (!defined $type || $type == LOGIN_NORMAL) {
|
|
$type = $class->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
|
|
}
|
|
|
|
# Allow templates to know that we're in a page that always requires
|
|
# login.
|
|
if ($type == LOGIN_REQUIRED) {
|
|
$class->request_cache->{page_requires_login} = 1;
|
|
}
|
|
|
|
my $authenticated_user = $authorizer->login($type);
|
|
|
|
# At this point, we now know if a real person is logged in.
|
|
# We must now check to see if an sudo session is in progress.
|
|
# For a session to be in progress, the following must be true:
|
|
# 1: There must be a logged in user
|
|
# 2: That user must be in the 'bz_sudoer' group
|
|
# 3: There must be a valid value in the 'sudo' cookie
|
|
# 4: A Bugzilla::User object must exist for the given cookie value
|
|
# 5: That user must NOT be in the 'bz_sudo_protect' group
|
|
my $token = $class->cgi->cookie('sudo');
|
|
if (defined $authenticated_user && $token) {
|
|
my ($user_id, $date, $sudo_target_id) = Bugzilla::Token::GetTokenData($token);
|
|
if (!$user_id
|
|
|| $user_id != $authenticated_user->id
|
|
|| !detaint_natural($sudo_target_id)
|
|
|| (time() - str2time($date) > MAX_SUDO_TOKEN_AGE))
|
|
{
|
|
$class->cgi->remove_cookie('sudo');
|
|
ThrowUserError('sudo_invalid_cookie');
|
|
}
|
|
|
|
my $sudo_target = new Bugzilla::User($sudo_target_id);
|
|
if ($authenticated_user->in_group('bz_sudoers')
|
|
&& defined $sudo_target
|
|
&& !$sudo_target->in_group('bz_sudo_protect'))
|
|
{
|
|
$class->set_user($sudo_target);
|
|
$class->request_cache->{sudoer} = $authenticated_user;
|
|
# And make sure that both users have the same Auth object,
|
|
# since we never call Auth::login for the sudo target.
|
|
$sudo_target->set_authorizer($authenticated_user->authorizer);
|
|
|
|
# NOTE: If you want to do any special logging, do it here.
|
|
}
|
|
else {
|
|
delete_token($token);
|
|
$class->cgi->remove_cookie('sudo');
|
|
ThrowUserError('sudo_illegal_action', { sudoer => $authenticated_user,
|
|
target_user => $sudo_target });
|
|
}
|
|
}
|
|
else {
|
|
$class->set_user($authenticated_user);
|
|
}
|
|
|
|
if ($class->sudoer) {
|
|
$class->sudoer->update_last_seen_date();
|
|
} else {
|
|
$class->user->update_last_seen_date();
|
|
}
|
|
|
|
return $class->user;
|
|
}
|
|
|
|
sub logout {
|
|
my ($class, $option) = @_;
|
|
|
|
# If we're not logged in, go away
|
|
return unless $class->user->id;
|
|
|
|
$option = LOGOUT_CURRENT unless defined $option;
|
|
Bugzilla::Auth::Persist::Cookie->logout({type => $option});
|
|
$class->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
|
|
}
|
|
|
|
sub logout_user {
|
|
my ($class, $user) = @_;
|
|
# When we're logging out another user we leave cookies alone, and
|
|
# therefore avoid calling Bugzilla->logout() directly.
|
|
Bugzilla::Auth::Persist::Cookie->logout({user => $user});
|
|
}
|
|
|
|
# just a compatibility front-end to logout_user that gets a user by id
|
|
sub logout_user_by_id {
|
|
my ($class, $id) = @_;
|
|
my $user = new Bugzilla::User($id);
|
|
$class->logout_user($user);
|
|
}
|
|
|
|
# hack that invalidates credentials for a single request
|
|
sub logout_request {
|
|
my $class = shift;
|
|
delete $class->request_cache->{user};
|
|
delete $class->request_cache->{sudoer};
|
|
# We can't delete from $cgi->cookie, so logincookie data will remain
|
|
# there. Don't rely on it: use Bugzilla->user->login instead!
|
|
}
|
|
|
|
sub job_queue {
|
|
require Bugzilla::JobQueue;
|
|
return $_[0]->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
|
|
}
|
|
|
|
sub dbh {
|
|
# If we're not connected, then we must want the main db
|
|
return $_[0]->request_cache->{dbh} ||= $_[0]->dbh_main;
|
|
}
|
|
|
|
sub dbh_main {
|
|
return $_[0]->request_cache->{dbh_main} ||= Bugzilla::DB::connect_main();
|
|
}
|
|
|
|
sub languages {
|
|
return Bugzilla::Install::Util::supported_languages();
|
|
}
|
|
|
|
sub current_language {
|
|
return $_[0]->request_cache->{current_language} ||= (include_languages())[0];
|
|
}
|
|
|
|
sub error_mode {
|
|
my ($class, $newval) = @_;
|
|
if (defined $newval) {
|
|
$class->request_cache->{error_mode} = $newval;
|
|
}
|
|
|
|
# XXX - Once we require Perl 5.10.1, this test can be replaced by //.
|
|
if (exists $class->request_cache->{error_mode}) {
|
|
return $class->request_cache->{error_mode};
|
|
}
|
|
else {
|
|
return (i_am_cgi() ? ERROR_MODE_WEBPAGE : ERROR_MODE_DIE);
|
|
}
|
|
}
|
|
|
|
# This is used only by Bugzilla::Error to throw errors.
|
|
sub _json_server {
|
|
my ($class, $newval) = @_;
|
|
if (defined $newval) {
|
|
$class->request_cache->{_json_server} = $newval;
|
|
}
|
|
return $class->request_cache->{_json_server};
|
|
}
|
|
|
|
sub usage_mode {
|
|
my ($class, $newval) = @_;
|
|
if (defined $newval) {
|
|
if ($newval == USAGE_MODE_BROWSER) {
|
|
$class->error_mode(ERROR_MODE_WEBPAGE);
|
|
}
|
|
elsif ($newval == USAGE_MODE_CMDLINE) {
|
|
$class->error_mode(ERROR_MODE_DIE);
|
|
}
|
|
elsif ($newval == USAGE_MODE_XMLRPC) {
|
|
$class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
|
|
}
|
|
elsif ($newval == USAGE_MODE_JSON) {
|
|
$class->error_mode(ERROR_MODE_JSON_RPC);
|
|
}
|
|
elsif ($newval == USAGE_MODE_EMAIL) {
|
|
$class->error_mode(ERROR_MODE_DIE);
|
|
}
|
|
elsif ($newval == USAGE_MODE_TEST) {
|
|
$class->error_mode(ERROR_MODE_TEST);
|
|
}
|
|
elsif ($newval == USAGE_MODE_REST) {
|
|
$class->error_mode(ERROR_MODE_REST);
|
|
}
|
|
else {
|
|
ThrowCodeError('usage_mode_invalid',
|
|
{'invalid_usage_mode', $newval});
|
|
}
|
|
$class->request_cache->{usage_mode} = $newval;
|
|
}
|
|
|
|
# XXX - Once we require Perl 5.10.1, this test can be replaced by //.
|
|
if (exists $class->request_cache->{usage_mode}) {
|
|
return $class->request_cache->{usage_mode};
|
|
}
|
|
else {
|
|
return (i_am_cgi()? USAGE_MODE_BROWSER : USAGE_MODE_CMDLINE);
|
|
}
|
|
}
|
|
|
|
sub installation_mode {
|
|
my ($class, $newval) = @_;
|
|
($class->request_cache->{installation_mode} = $newval) if defined $newval;
|
|
return $class->request_cache->{installation_mode}
|
|
|| INSTALLATION_MODE_INTERACTIVE;
|
|
}
|
|
|
|
sub installation_answers {
|
|
my ($class, $filename) = @_;
|
|
if ($filename) {
|
|
my $s = new Safe;
|
|
$s->rdo($filename);
|
|
|
|
die "Error reading $filename: $!" if $!;
|
|
die "Error evaluating $filename: $@" if $@;
|
|
|
|
# Now read the param back out from the sandbox
|
|
$class->request_cache->{installation_answers} = $s->varglob('answer');
|
|
}
|
|
return $class->request_cache->{installation_answers} || {};
|
|
}
|
|
|
|
sub switch_to_shadow_db {
|
|
my $class = shift;
|
|
|
|
if (!$class->request_cache->{dbh_shadow}) {
|
|
if ($class->params->{'shadowdb'}) {
|
|
$class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
|
|
} else {
|
|
$class->request_cache->{dbh_shadow} = $class->dbh_main;
|
|
}
|
|
}
|
|
|
|
$class->request_cache->{dbh} = $class->request_cache->{dbh_shadow};
|
|
# we have to return $class->dbh instead of {dbh} as
|
|
# {dbh_shadow} may be undefined if no shadow DB is used
|
|
# and no connection to the main DB has been established yet.
|
|
return $class->dbh;
|
|
}
|
|
|
|
sub switch_to_main_db {
|
|
my $class = shift;
|
|
|
|
$class->request_cache->{dbh} = $class->dbh_main;
|
|
return $class->dbh_main;
|
|
}
|
|
|
|
sub is_shadow_db {
|
|
my $class = shift;
|
|
return $class->request_cache->{dbh} != $class->dbh_main;
|
|
}
|
|
|
|
sub fields {
|
|
my ($class, $criteria) = @_;
|
|
$criteria ||= {};
|
|
my $cache = $class->request_cache;
|
|
|
|
# We create an advanced cache for fields by type, so that we
|
|
# can avoid going back to the database for every fields() call.
|
|
# (And most of our fields() calls are for getting fields by type.)
|
|
#
|
|
# We also cache fields by name, because calling $field->name a few
|
|
# million times can be slow in calling code, but if we just do it
|
|
# once here, that makes things a lot faster for callers.
|
|
if (!defined $cache->{fields}) {
|
|
my @all_fields = Bugzilla::Field->get_all;
|
|
my (%by_name, %by_type);
|
|
foreach my $field (@all_fields) {
|
|
my $name = $field->name;
|
|
$by_type{$field->type}->{$name} = $field;
|
|
$by_name{$name} = $field;
|
|
}
|
|
$cache->{fields} = { by_type => \%by_type, by_name => \%by_name };
|
|
}
|
|
|
|
my $fields = $cache->{fields};
|
|
my %requested;
|
|
if (my $types = delete $criteria->{type}) {
|
|
$types = ref($types) ? $types : [$types];
|
|
%requested = map { %{ $fields->{by_type}->{$_} || {} } } @$types;
|
|
}
|
|
else {
|
|
%requested = %{ $fields->{by_name} };
|
|
}
|
|
|
|
my $do_by_name = delete $criteria->{by_name};
|
|
|
|
# Filtering before returning the fields based on
|
|
# the criterias.
|
|
foreach my $filter (keys %$criteria) {
|
|
foreach my $field (keys %requested) {
|
|
if ($requested{$field}->$filter != $criteria->{$filter}) {
|
|
delete $requested{$field};
|
|
}
|
|
}
|
|
}
|
|
|
|
return $do_by_name ? \%requested
|
|
: [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name } values %requested];
|
|
}
|
|
|
|
sub active_custom_fields {
|
|
my $class = shift;
|
|
if (!exists $class->request_cache->{active_custom_fields}) {
|
|
$class->request_cache->{active_custom_fields} =
|
|
Bugzilla::Field->match({ custom => 1, obsolete => 0 });
|
|
}
|
|
return @{$class->request_cache->{active_custom_fields}};
|
|
}
|
|
|
|
sub has_flags {
|
|
my $class = shift;
|
|
|
|
if (!defined $class->request_cache->{has_flags}) {
|
|
$class->request_cache->{has_flags} = Bugzilla::Flag->any_exist;
|
|
}
|
|
return $class->request_cache->{has_flags};
|
|
}
|
|
|
|
sub local_timezone {
|
|
return $_[0]->process_cache->{local_timezone}
|
|
||= DateTime::TimeZone->new(name => 'local');
|
|
}
|
|
|
|
# This creates the request cache for non-mod_perl installations.
|
|
# This is identical to Install::Util::_cache so that things loaded
|
|
# into Install::Util::_cache during installation can be read out
|
|
# of request_cache later in installation.
|
|
our $_request_cache = $Bugzilla::Install::Util::_cache;
|
|
|
|
sub request_cache {
|
|
if ($ENV{MOD_PERL}) {
|
|
require Apache2::RequestUtil;
|
|
# Sometimes (for example, during mod_perl.pl), the request
|
|
# object isn't available, and we should use $_request_cache instead.
|
|
my $request = eval { Apache2::RequestUtil->request };
|
|
return $_request_cache if !$request;
|
|
return $request->pnotes();
|
|
}
|
|
return $_request_cache;
|
|
}
|
|
|
|
sub clear_request_cache {
|
|
$_request_cache = {};
|
|
if ($ENV{MOD_PERL}) {
|
|
require Apache2::RequestUtil;
|
|
my $request = eval { Apache2::RequestUtil->request };
|
|
if ($request) {
|
|
my $pnotes = $request->pnotes;
|
|
delete @$pnotes{(keys %$pnotes)};
|
|
}
|
|
}
|
|
}
|
|
|
|
# This is a per-process cache. Under mod_cgi it's identical to the
|
|
# request_cache. When using mod_perl, items in this cache live until the
|
|
# worker process is terminated.
|
|
our $_process_cache = {};
|
|
|
|
sub process_cache {
|
|
return $_process_cache;
|
|
}
|
|
|
|
# This is a memcached wrapper, which provides cross-process and cross-system
|
|
# caching.
|
|
sub memcached {
|
|
return $_[0]->process_cache->{memcached} ||= Bugzilla::Memcached->_new();
|
|
}
|
|
|
|
# Private methods
|
|
|
|
# Per-process cleanup. Note that this is a plain subroutine, not a method,
|
|
# so we don't have $class available.
|
|
sub _cleanup {
|
|
my $cache = Bugzilla->request_cache;
|
|
my $main = $cache->{dbh_main};
|
|
my $shadow = $cache->{dbh_shadow};
|
|
foreach my $dbh ($main, $shadow) {
|
|
next if !$dbh;
|
|
$dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
|
|
$dbh->disconnect;
|
|
}
|
|
my $smtp = $cache->{smtp};
|
|
$smtp->disconnect if $smtp;
|
|
clear_request_cache();
|
|
|
|
# These are both set by CGI.pm but need to be undone so that
|
|
# Apache can actually shut down its children if it needs to.
|
|
foreach my $signal (qw(TERM PIPE)) {
|
|
$SIG{$signal} = 'DEFAULT' if $SIG{$signal} && $SIG{$signal} eq 'IGNORE';
|
|
}
|
|
}
|
|
|
|
sub END {
|
|
# Bugzilla.pm cannot compile in mod_perl.pl if this runs.
|
|
_cleanup() unless $ENV{MOD_PERL};
|
|
}
|
|
|
|
init_page() if !$ENV{MOD_PERL};
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
Bugzilla - Semi-persistent collection of various objects used by scripts
|
|
and modules
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use Bugzilla;
|
|
|
|
sub someModulesSub {
|
|
Bugzilla->dbh->prepare(...);
|
|
Bugzilla->template->process(...);
|
|
}
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Several Bugzilla 'things' are used by a variety of modules and scripts. This
|
|
includes database handles, template objects, and so on.
|
|
|
|
This module is a singleton intended as a central place to store these objects.
|
|
This approach has several advantages:
|
|
|
|
=over 4
|
|
|
|
=item *
|
|
|
|
They're not global variables, so we don't have issues with them staying around
|
|
with mod_perl
|
|
|
|
=item *
|
|
|
|
Everything is in one central place, so it's easy to access, modify, and maintain
|
|
|
|
=item *
|
|
|
|
Code in modules can get access to these objects without having to have them
|
|
all passed from the caller, and the caller's caller, and....
|
|
|
|
=item *
|
|
|
|
We can reuse objects across requests using mod_perl where appropriate (eg
|
|
templates), whilst destroying those which are only valid for a single request
|
|
(such as the current user)
|
|
|
|
=back
|
|
|
|
Note that items accessible via this object are demand-loaded when requested.
|
|
|
|
For something to be added to this object, it should either be able to benefit
|
|
from persistence when run under mod_perl (such as the a C<template> object),
|
|
or should be something which is globally required by a large ammount of code
|
|
(such as the current C<user> object).
|
|
|
|
=head1 METHODS
|
|
|
|
Note that all C<Bugzilla> functionality is method based; use C<Bugzilla-E<gt>dbh>
|
|
rather than C<Bugzilla::dbh>. Nothing cares about this now, but don't rely on
|
|
that.
|
|
|
|
=over 4
|
|
|
|
=item C<template>
|
|
|
|
The current C<Template> object, to be used for output
|
|
|
|
=item C<template_inner>
|
|
|
|
If you ever need a L<Bugzilla::Template> object while you're already
|
|
processing a template, use this. Also use it if you want to specify
|
|
the language to use. If no argument is passed, it uses the last
|
|
language set. If the argument is "" (empty string), the language is
|
|
reset to the current one (the one used by C<Bugzilla-E<gt>template>).
|
|
|
|
=item C<cgi>
|
|
|
|
The current C<cgi> object. Note that modules should B<not> be using this in
|
|
general. Not all Bugzilla actions are cgi requests. Its useful as a convenience
|
|
method for those scripts/templates which are only use via CGI, though.
|
|
|
|
=item C<input_params>
|
|
|
|
When running under the WebService, this is a hashref containing the arguments
|
|
passed to the WebService method that was called. When running in a normal
|
|
script, this is a hashref containing the contents of the CGI parameters.
|
|
|
|
Modifying this hashref will modify the CGI parameters or the WebService
|
|
arguments (depending on what C<input_params> currently represents).
|
|
|
|
This should be used instead of L</cgi> in situations where your code
|
|
could be being called by either a normal CGI script or a WebService method,
|
|
such as during a code hook.
|
|
|
|
B<Note:> When C<input_params> represents the CGI parameters, any
|
|
parameter specified more than once (like C<foo=bar&foo=baz>) will appear
|
|
as an arrayref in the hash, but any value specified only once will appear
|
|
as a scalar. This means that even if a value I<can> appear multiple times,
|
|
if it only I<does> appear once, then it will be a scalar in C<input_params>,
|
|
not an arrayref.
|
|
|
|
=item C<user>
|
|
|
|
Default C<Bugzilla::User> object if there is no currently logged in user or
|
|
if the login code has not yet been run. If an sudo session is in progress,
|
|
the C<Bugzilla::User> corresponding to the person who is being impersonated.
|
|
If no session is in progress, the current C<Bugzilla::User>.
|
|
|
|
=item C<set_user>
|
|
|
|
Allows you to directly set what L</user> will return. You can use this
|
|
if you want to bypass L</login> for some reason and directly "log in"
|
|
a specific L<Bugzilla::User>. Be careful with it, though!
|
|
|
|
=item C<sudoer>
|
|
|
|
C<undef> if there is no currently logged in user, the currently logged in user
|
|
is not in the I<sudoer> group, or there is no session in progress. If an sudo
|
|
session is in progress, returns the C<Bugzilla::User> object corresponding to
|
|
the person who logged in and initiated the session. If no session is in
|
|
progress, returns the C<Bugzilla::User> object corresponding to the currently
|
|
logged in user.
|
|
|
|
=item C<sudo_request>
|
|
This begins an sudo session for the current request. It is meant to be
|
|
used when a session has just started. For normal use, sudo access should
|
|
normally be set at login time.
|
|
|
|
=item C<login>
|
|
|
|
Logs in a user, returning a C<Bugzilla::User> object, or C<undef> if there is
|
|
no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth>, and
|
|
L<Bugzilla::User|Bugzilla::User>.
|
|
|
|
=item C<page_requires_login>
|
|
|
|
If the current page always requires the user to log in (for example,
|
|
C<enter_bug.cgi> or any page called with C<?GoAheadAndLogIn=1>) then
|
|
this will return something true. Otherwise it will return false. (This is
|
|
set when you call L</login>.)
|
|
|
|
=item C<logout($option)>
|
|
|
|
Logs out the current user, which involves invalidating user sessions and
|
|
cookies. Three options are available from
|
|
L<Bugzilla::Constants|Bugzilla::Constants>: LOGOUT_CURRENT (the
|
|
default), LOGOUT_ALL or LOGOUT_KEEP_CURRENT.
|
|
|
|
=item C<logout_user($user)>
|
|
|
|
Logs out the specified user (invalidating all their sessions), taking a
|
|
Bugzilla::User instance.
|
|
|
|
=item C<logout_by_id($id)>
|
|
|
|
Logs out the user with the id specified. This is a compatibility
|
|
function to be used in callsites where there is only a userid and no
|
|
Bugzilla::User instance.
|
|
|
|
=item C<logout_request>
|
|
|
|
Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has the
|
|
effect of logging out a user for the current request only; cookies and
|
|
database sessions are left intact.
|
|
|
|
=item C<fields>
|
|
|
|
This is the standard way to get arrays or hashes of L<Bugzilla::Field>
|
|
objects when you need them. It takes the following named arguments
|
|
in a hashref:
|
|
|
|
=over
|
|
|
|
=item C<by_name>
|
|
|
|
If false (or not specified), this method will return an arrayref of
|
|
the requested fields.
|
|
|
|
If true, this method will return a hashref of fields, where the keys
|
|
are field names and the valules are L<Bugzilla::Field> objects.
|
|
|
|
=item C<type>
|
|
|
|
Either a single C<FIELD_TYPE_*> constant or an arrayref of them. If specified,
|
|
the returned fields will be limited to the types in the list. If you don't
|
|
specify this argument, all fields will be returned.
|
|
|
|
=back
|
|
|
|
=item C<error_mode>
|
|
|
|
Call either C<Bugzilla-E<gt>error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
|
|
or C<Bugzilla-E<gt>error_mode(Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT)> to
|
|
change this flag's default of C<Bugzilla::Constants::ERROR_MODE_WEBPAGE> and to
|
|
indicate that errors should be passed to error mode specific error handlers
|
|
rather than being sent to a browser and finished with an exit().
|
|
|
|
This is useful, for example, to keep C<eval> blocks from producing wild HTML
|
|
on errors, making it easier for you to catch them.
|
|
(Remember to reset the error mode to its previous value afterwards, though.)
|
|
|
|
C<Bugzilla-E<gt>error_mode> will return the current state of this flag.
|
|
|
|
Note that C<Bugzilla-E<gt>error_mode> is being called by C<Bugzilla-E<gt>usage_mode> on
|
|
usage mode changes.
|
|
|
|
=item C<usage_mode>
|
|
|
|
Call either C<Bugzilla-E<gt>usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
|
|
or C<Bugzilla-E<gt>usage_mode(Bugzilla::Constants::USAGE_MODE_XMLRPC)> near the
|
|
beginning of your script to change this flag's default of
|
|
C<Bugzilla::Constants::USAGE_MODE_BROWSER> and to indicate that Bugzilla is
|
|
being called in a non-interactive manner.
|
|
|
|
This influences error handling because on usage mode changes, C<usage_mode>
|
|
calls C<Bugzilla-E<gt>error_mode> to set an error mode which makes sense for the
|
|
usage mode.
|
|
|
|
C<Bugzilla-E<gt>usage_mode> will return the current state of this flag.
|
|
|
|
=item C<installation_mode>
|
|
|
|
Determines whether or not installation should be silent. See
|
|
L<Bugzilla::Constants> for the C<INSTALLATION_MODE> constants.
|
|
|
|
=item C<installation_answers>
|
|
|
|
Returns a hashref representing any "answers" file passed to F<checksetup.pl>,
|
|
used to automatically answer or skip prompts.
|
|
|
|
=item C<dbh>
|
|
|
|
The current database handle. See L<DBI>.
|
|
|
|
=item C<dbh_main>
|
|
|
|
The main database handle. See L<DBI>.
|
|
|
|
=item C<languages>
|
|
|
|
Currently installed languages.
|
|
Returns a reference to a list of RFC 1766 language tags of installed languages.
|
|
|
|
=item C<current_language>
|
|
|
|
The currently active language.
|
|
|
|
=item C<switch_to_shadow_db>
|
|
|
|
Switch from using the main database to using the shadow database.
|
|
|
|
=item C<switch_to_main_db>
|
|
|
|
Change the database object to refer to the main database.
|
|
|
|
=item C<is_shadow_db>
|
|
|
|
Returns true if the currently active database is the shadow database.
|
|
Returns false if a the currently active database is the man database, or if a
|
|
shadow database is not configured or enabled.
|
|
|
|
=item C<params>
|
|
|
|
The current Parameters of Bugzilla, as a hashref. If C<data/params.json>
|
|
does not exist, then we return an empty hashref. If C<data/params.json>
|
|
is unreadable or is not valid, we C<die>.
|
|
|
|
=item C<local_timezone>
|
|
|
|
Returns the local timezone of the Bugzilla installation,
|
|
as a DateTime::TimeZone object. This detection is very time
|
|
consuming, so we cache this information for future references.
|
|
|
|
=item C<job_queue>
|
|
|
|
Returns a L<Bugzilla::JobQueue> that you can use for queueing jobs.
|
|
Will throw an error if job queueing is not correctly configured on
|
|
this Bugzilla installation.
|
|
|
|
=item C<feature>
|
|
|
|
Tells you whether or not a specific feature is enabled. For names
|
|
of features, see C<OPTIONAL_MODULES> in C<Bugzilla::Install::Requirements>.
|
|
|
|
=back
|
|
|
|
=head1 B<CACHING>
|
|
|
|
Bugzilla has several different caches available which provide different
|
|
capabilities and lifetimes.
|
|
|
|
The keys of all caches are unregulated; use of prefixes is suggested to avoid
|
|
collisions.
|
|
|
|
=over
|
|
|
|
=item B<Request Cache>
|
|
|
|
The request cache is a hashref which supports caching any perl variable for the
|
|
duration of the current request. At the end of the current request the contents
|
|
of this cache are cleared.
|
|
|
|
Examples of its use include caching objects to avoid re-fetching the same data
|
|
from the database, and passing data between otherwise unconnected parts of
|
|
Bugzilla.
|
|
|
|
=over
|
|
|
|
=item C<request_cache>
|
|
|
|
Returns a hashref which can be checked and modified to store any perl variable
|
|
for the duration of the current request.
|
|
|
|
=item C<clear_request_cache>
|
|
|
|
Removes all entries from the C<request_cache>.
|
|
|
|
=back
|
|
|
|
=item B<Process Cache>
|
|
|
|
The process cache is a hashref which support caching of any perl variable. If
|
|
Bugzilla is configured to run using Apache mod_perl, the contents of this cache
|
|
are persisted across requests for the lifetime of the Apache worker process
|
|
(which varies depending on the SizeLimit configuration in mod_perl.pl).
|
|
|
|
If Bugzilla isn't running under mod_perl, the process cache's contents are
|
|
cleared at the end of the request.
|
|
|
|
The process cache is only suitable for items which never change while Bugzilla
|
|
is running (for example the path where Bugzilla is installed).
|
|
|
|
=over
|
|
|
|
=item C<process_cache>
|
|
|
|
Returns a hashref which can be checked and modified to store any perl variable
|
|
for the duration of the current process (mod_perl) or request (mod_cgi).
|
|
|
|
=back
|
|
|
|
=item B<Memcached>
|
|
|
|
If Memcached is installed and configured, Bugzilla can use it to cache data
|
|
across requests and between webheads. Unlike the request and process caches,
|
|
only scalars, hashrefs, and arrayrefs can be stored in Memcached.
|
|
|
|
Memcached integration is only required for large installations of Bugzilla -- if
|
|
you have multiple webheads then configuring Memcached is recommended.
|
|
|
|
=over
|
|
|
|
=item C<memcached>
|
|
|
|
Returns a C<Bugzilla::Memcached> object. An object is always returned even if
|
|
Memcached is not available.
|
|
|
|
See the documentation for the C<Bugzilla::Memcached> module for more
|
|
information.
|
|
|
|
=back
|
|
|
|
=back
|
|
|
|
=head1 B<Methods in need of POD>
|
|
|
|
=over
|
|
|
|
=item init_page
|
|
|
|
=item extensions
|
|
|
|
=item logout_user_by_id
|
|
|
|
=item localconfig
|
|
|
|
=item active_custom_fields
|
|
|
|
=item has_flags
|
|
|
|
=back
|