Bugzilla.pm 32 KB
Newer Older
1 2 3
# 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/.
4
#
5 6
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
7 8 9

package Bugzilla;

10
use 5.10.1;
11
use strict;
12
use warnings;
13

14 15
# We want any compile errors to get to the browser, if possible.
BEGIN {
16 17 18 19 20
  # This makes sure we're in a CGI.
  if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
    require CGI::Carp;
    CGI::Carp->import('fatalsToBrowser');
  }
21 22
}

23
use Bugzilla::Auth;
24
use Bugzilla::Auth::Persist::Cookie;
25
use Bugzilla::CGI;
26 27
use Bugzilla::Config;
use Bugzilla::Constants;
28
use Bugzilla::DB;
29 30 31 32
use Bugzilla::Error;
use Bugzilla::Extension;
use Bugzilla::Field;
use Bugzilla::Flag;
33
use Bugzilla::Install::Localconfig qw(read_localconfig);
34
use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES have_vers);
35
use Bugzilla::Install::Util qw(init_console include_languages);
36
use Bugzilla::Memcached;
37
use Bugzilla::Template;
38
use Bugzilla::Token;
39
use Bugzilla::User;
40 41 42
use Bugzilla::Util;

use File::Basename;
43
use File::Spec::Functions;
44
use DateTime::TimeZone;
45
use Date::Parse;
46
use Safe;
47 48 49 50 51 52

#####################################################################
# Constants
#####################################################################

# Scripts that are not stopped by shutdownhtml being in effect.
53
use constant SHUTDOWNHTML_EXEMPT => qw(
54 55 56 57
  editparams.cgi
  checksetup.pl
  migrate.pl
  recode.pl
58
);
59

60
# Non-cgi scripts that should silently exit.
61
use constant SHUTDOWNHTML_EXIT_SILENTLY => qw(
62
  whine.pl
63
);
64

65 66 67 68
# 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;

69 70 71 72
#####################################################################
# Global Code
#####################################################################

73
#$::SIG{__DIE__} = i_am_cgi() ? \&CGI::Carp::confess : \&Carp::confess;
74

75
# Note that this is a raw subroutine, not a method, so $class isn't available.
76
sub init_page {
77 78 79 80 81 82
  if (Bugzilla->usage_mode == USAGE_MODE_CMDLINE) {
    init_console();
  }
  elsif (Bugzilla->params->{'utf8'}) {
    binmode STDOUT, ':utf8';
  }
83

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  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;
  }
112

113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
  # 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();
      }
    };
  }
132

133
  my $script = basename($0);
134

135 136 137 138
  # Because of attachment_base, attachment.cgi handles this itself.
  if ($script ne 'attachment.cgi') {
    do_ssl_redirect_if_required();
  }
139

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
  # 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'))
179
    {
180
      $extension = 'html';
181
    }
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    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;
  }
198 199 200 201 202
}

#####################################################################
# Subroutines and Methods
#####################################################################
203

204
sub template {
205
  return $_[0]->request_cache->{template} ||= Bugzilla::Template->create();
206
}
207

208
sub template_inner {
209 210 211 212 213 214
  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);
215 216
}

217
our $extension_packages;
218

219
sub extensions {
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
  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};
238 239
}

240
sub feature {
241 242 243 244 245 246 247 248 249 250 251 252 253 254
  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;
  }
255

256 257 258
  if (!$feature_map->{$feature}) {
    ThrowCodeError('invalid_feature', {feature => $feature});
  }
259

260 261 262 263 264 265
  my $success = 1;
  foreach my $package (@{$feature_map->{$feature}}) {
    have_vers($package) or $success = 0;
  }
  $cache->{feature}->{$feature} = $success;
  return $success;
266 267
}

268
sub cgi {
269
  return $_[0]->request_cache->{cgi} ||= new Bugzilla::CGI();
270 271
}

272
sub input_params {
273 274 275 276 277 278 279 280
  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};
281

282 283 284 285 286
  # 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};
287 288
}

289
sub localconfig {
290
  return $_[0]->process_cache->{localconfig} ||= read_localconfig();
291 292
}

293
sub params {
294
  return $_[0]->request_cache->{params} ||= Bugzilla::Config::read_param_file();
295 296
}

297
sub user {
298
  return $_[0]->request_cache->{user} ||= new Bugzilla::User;
299 300
}

301
sub set_user {
302 303
  my ($class, $user) = @_;
  $class->request_cache->{user} = $user;
304 305
}

306
sub sudoer {
307
  return $_[0]->request_cache->{sudoer};
308 309 310
}

sub sudo_request {
311 312 313 314 315
  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.
316 317
}

318
sub page_requires_login {
319
  return $_[0]->request_cache->{page_requires_login};
320 321
}

322
sub login {
323
  my ($class, $type) = @_;
324

325
  return $class->user if $class->user->id;
326

327 328
  my $authorizer = new Bugzilla::Auth();
  $type = LOGIN_REQUIRED if $class->cgi->param('GoAheadAndLogIn');
329

330 331 332
  if (!defined $type || $type == LOGIN_NORMAL) {
    $type = $class->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
  }
333

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359
  # 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');
360 361
    }

362 363 364 365 366 367 368 369 370 371 372 373 374
    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.
375 376
    }
    else {
377 378 379 380
      delete_token($token);
      $class->cgi->remove_cookie('sudo');
      ThrowUserError('sudo_illegal_action',
        {sudoer => $authenticated_user, target_user => $sudo_target});
381
    }
382 383 384 385
  }
  else {
    $class->set_user($authenticated_user);
  }
386

387 388 389 390 391 392
  if ($class->sudoer) {
    $class->sudoer->update_last_seen_date();
  }
  else {
    $class->user->update_last_seen_date();
  }
393

394
  return $class->user;
395 396 397
}

sub logout {
398
  my ($class, $option) = @_;
399

400 401
  # If we're not logged in, go away
  return unless $class->user->id;
402

403 404 405
  $option = LOGOUT_CURRENT unless defined $option;
  Bugzilla::Auth::Persist::Cookie->logout({type => $option});
  $class->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
406 407 408
}

sub logout_user {
409 410 411 412 413
  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});
414 415
}

416 417
# just a compatibility front-end to logout_user that gets a user by id
sub logout_user_by_id {
418 419 420
  my ($class, $id) = @_;
  my $user = new Bugzilla::User($id);
  $class->logout_user($user);
421 422 423
}

# hack that invalidates credentials for a single request
424
sub logout_request {
425 426 427 428 429 430
  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!
431 432
}

433
sub job_queue {
434 435
  require Bugzilla::JobQueue;
  return $_[0]->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
436 437
}

438
sub dbh {
439 440 441

  # If we're not connected, then we must want the main db
  return $_[0]->request_cache->{dbh} ||= $_[0]->dbh_main;
442 443
}

444
sub dbh_main {
445
  return $_[0]->request_cache->{dbh_main} ||= Bugzilla::DB::connect_main();
446 447
}

448
sub languages {
449
  return Bugzilla::Install::Util::supported_languages();
450 451
}

452
sub current_language {
453
  return $_[0]->request_cache->{current_language} ||= (include_languages())[0];
454 455
}

456
sub error_mode {
457 458 459 460
  my ($class, $newval) = @_;
  if (defined $newval) {
    $class->request_cache->{error_mode} = $newval;
  }
461

462 463 464 465 466 467 468
  # 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);
  }
469 470
}

471 472
# This is used only by Bugzilla::Error to throw errors.
sub _json_server {
473 474 475 476 477
  my ($class, $newval) = @_;
  if (defined $newval) {
    $class->request_cache->{_json_server} = $newval;
  }
  return $class->request_cache->{_json_server};
478 479
}

480
sub usage_mode {
481 482 483 484
  my ($class, $newval) = @_;
  if (defined $newval) {
    if ($newval == USAGE_MODE_BROWSER) {
      $class->error_mode(ERROR_MODE_WEBPAGE);
485
    }
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
    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);
503 504
    }
    else {
505
      ThrowCodeError('usage_mode_invalid', {'invalid_usage_mode', $newval});
506
    }
507 508 509 510 511 512 513 514 515 516
    $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);
  }
517 518
}

519
sub installation_mode {
520 521 522 523
  my ($class, $newval) = @_;
  ($class->request_cache->{installation_mode} = $newval) if defined $newval;
  return $class->request_cache->{installation_mode}
    || INSTALLATION_MODE_INTERACTIVE;
524 525 526
}

sub installation_answers {
527 528 529 530
  my ($class, $filename) = @_;
  if ($filename) {
    my $s = new Safe;
    $s->rdo($filename);
531

532 533
    die "Error reading $filename: $!"    if $!;
    die "Error evaluating $filename: $@" if $@;
534

535 536 537 538
    # Now read the param back out from the sandbox
    $class->request_cache->{installation_answers} = $s->varglob('answer');
  }
  return $class->request_cache->{installation_answers} || {};
539 540
}

541
sub switch_to_shadow_db {
542 543 544 545 546 547 548 549
  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;
550
    }
551 552 553
  }

  $class->request_cache->{dbh} = $class->request_cache->{dbh_shadow};
554

555 556 557 558
  # 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;
559 560 561
}

sub switch_to_main_db {
562
  my $class = shift;
563

564 565
  $class->request_cache->{dbh} = $class->dbh_main;
  return $class->dbh_main;
566 567
}

568
sub is_shadow_db {
569 570
  my $class = shift;
  return $class->request_cache->{dbh} != $class->dbh_main;
571 572
}

573
sub fields {
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
  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};
  }
595

596 597 598
  my $fields = $cache->{fields};
  my %requested;
  if (my $types = delete $criteria->{type}) {
599
    $types     = ref($types) ? $types : [$types];
600 601 602 603 604
    %requested = map { %{$fields->{by_type}->{$_} || {}} } @$types;
  }
  else {
    %requested = %{$fields->{by_name}};
  }
605

606
  my $do_by_name = delete $criteria->{by_name};
607

608 609 610 611 612 613 614
  # 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};
      }
615
    }
616
  }
617

618 619 620 621
  return $do_by_name
    ? \%requested
    : [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name }
      values %requested];
622 623
}

624
sub active_custom_fields {
625 626 627 628 629 630
  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}};
631 632
}

633
sub has_flags {
634
  my $class = shift;
635

636 637 638 639
  if (!defined $class->request_cache->{has_flags}) {
    $class->request_cache->{has_flags} = Bugzilla::Flag->any_exist;
  }
  return $class->request_cache->{has_flags};
640 641
}

642
sub local_timezone {
643 644
  return $_[0]->process_cache->{local_timezone}
    ||= DateTime::TimeZone->new(name => 'local');
645 646
}

647 648 649 650 651 652
# 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;

653
sub request_cache {
654 655 656 657 658 659 660 661 662 663
  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;
664 665
}

666
sub clear_request_cache {
667 668 669 670 671 672 673
  $_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)};
674
    }
675
  }
676 677
}

678 679 680 681 682 683
# 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 {
684
  return $_process_cache;
685 686
}

687 688 689
# This is a memcached wrapper, which provides cross-process and cross-system
# caching.
sub memcached {
690
  return $_[0]->process_cache->{memcached} ||= Bugzilla::Memcached->_new();
691 692
}

693
# Private methods
694

695 696
# Per-process cleanup. Note that this is a plain subroutine, not a method,
# so we don't have $class available.
697
sub _cleanup {
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713
  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;
  }
  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';
  }
714 715
}

716
sub END {
717 718 719

  # Bugzilla.pm cannot compile in mod_perl.pl if this runs.
  _cleanup() unless $ENV{MOD_PERL};
720 721
}

722 723
init_page() if !$ENV{MOD_PERL};

724 725 726 727 728 729 730 731 732 733 734 735 736 737
1;

__END__

=head1 NAME

Bugzilla - Semi-persistent collection of various objects used by scripts
and modules

=head1 SYNOPSIS

  use Bugzilla;

  sub someModulesSub {
738 739
    Bugzilla->dbh->prepare(...);
    Bugzilla->template->process(...);
740 741 742 743 744 745 746 747 748 749 750 751 752 753
  }

=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 *

754
They're not global variables, so we don't have issues with them staying around
755 756 757 758
with mod_perl

=item *

759
Everything is in one central place, so it's easy to access, modify, and maintain
760 761 762 763 764 765 766 767 768 769 770 771 772 773

=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

774
Note that items accessible via this object are demand-loaded when requested.
775 776 777 778 779 780

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).

781
=head1 METHODS
782

783
Note that all C<Bugzilla> functionality is method based; use C<Bugzilla-E<gt>dbh>
784 785
rather than C<Bugzilla::dbh>. Nothing cares about this now, but don't rely on
that.
786 787 788 789 790 791 792

=over 4

=item C<template>

The current C<Template> object, to be used for output

793 794 795
=item C<template_inner>

If you ever need a L<Bugzilla::Template> object while you're already
796 797 798
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
799
reset to the current one (the one used by C<Bugzilla-E<gt>template>).
800

801 802 803 804 805 806
=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.

807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826
=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.

827 828
=item C<user>

829 830 831 832
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>.
833

834 835 836 837 838 839
=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!

840 841 842 843 844 845 846 847 848 849 850 851 852
=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.
853 854 855

=item C<login>

856
Logs in a user, returning a C<Bugzilla::User> object, or C<undef> if there is
857
no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth>, and
858 859
L<Bugzilla::User|Bugzilla::User>.

860 861 862 863 864 865 866
=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>.)

867 868 869 870 871 872 873 874 875
=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)>

876
Logs out the specified user (invalidating all their sessions), taking a
877 878 879
Bugzilla::User instance.

=item C<logout_by_id($id)>
880

881 882 883
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.
884 885 886

=item C<logout_request>

887
Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has the
888
effect of logging out a user for the current request only; cookies and
889
database sessions are left intact.
890

891 892 893 894 895 896 897 898 899 900 901
=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
902
the requested fields.
903 904 905 906 907 908 909 910 911 912 913 914

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

915 916
=item C<error_mode>

917 918
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
919 920 921 922 923 924 925 926
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.)

927
C<Bugzilla-E<gt>error_mode> will return the current state of this flag.
928

929
Note that C<Bugzilla-E<gt>error_mode> is being called by C<Bugzilla-E<gt>usage_mode> on
930 931 932 933
usage mode changes.

=item C<usage_mode>

934 935
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
936 937 938
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.
939

940
This influences error handling because on usage mode changes, C<usage_mode>
941
calls C<Bugzilla-E<gt>error_mode> to set an error mode which makes sense for the
942
usage mode.
943

944
C<Bugzilla-E<gt>usage_mode> will return the current state of this flag.
945

946 947 948 949 950 951 952 953 954 955
=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.

956 957 958 959
=item C<dbh>

The current database handle. See L<DBI>.

960 961 962 963
=item C<dbh_main>

The main database handle. See L<DBI>.

964 965 966 967 968
=item C<languages>

Currently installed languages.
Returns a reference to a list of RFC 1766 language tags of installed languages.

969 970 971 972
=item C<current_language>

The currently active language.

973 974 975 976 977 978 979 980
=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.

981 982 983 984 985 986
=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.

987 988
=item C<params>

989 990
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>
991
is unreadable or is not valid, we C<die>.
992

993 994 995 996 997 998
=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.

999 1000 1001 1002 1003 1004
=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.

1005 1006 1007 1008 1009
=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>.

1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
=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.

1039 1040 1041 1042
=item C<clear_request_cache>

Removes all entries from the C<request_cache>.

1043
=back
1044

1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056
=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).
1057 1058 1059

=over

1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
=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
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106

=item init_page

=item extensions

=item logout_user_by_id

=item localconfig

=item active_custom_fields

=item has_flags

=back