Compare commits
1 Commits
tags/BUGZI
...
src
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
258dc9fead |
@@ -1,697 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# A. Karl Kornel <karl@kornel.name>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
# 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::Config;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Auth;
|
||||
use Bugzilla::Auth::Persist::Cookie;
|
||||
use Bugzilla::CGI;
|
||||
use Bugzilla::DB;
|
||||
use Bugzilla::Install::Localconfig qw(read_localconfig);
|
||||
use Bugzilla::Template;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Flag;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions;
|
||||
use Safe;
|
||||
|
||||
# This creates the request cache for non-mod_perl installations.
|
||||
our $_request_cache = {};
|
||||
|
||||
#####################################################################
|
||||
# Constants
|
||||
#####################################################################
|
||||
|
||||
# Scripts that are not stopped by shutdownhtml being in effect.
|
||||
use constant SHUTDOWNHTML_EXEMPT => [
|
||||
'editparams.cgi',
|
||||
'checksetup.pl',
|
||||
'recode.pl',
|
||||
];
|
||||
|
||||
# Non-cgi scripts that should silently exit.
|
||||
use constant SHUTDOWNHTML_EXIT_SILENTLY => [
|
||||
'whine.pl'
|
||||
];
|
||||
|
||||
#####################################################################
|
||||
# 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 {
|
||||
(binmode STDOUT, ':utf8') if Bugzilla->params->{'utf8'};
|
||||
|
||||
# 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'} = '';
|
||||
|
||||
# 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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Because this is code which is run live from perl "use" commands of other
|
||||
# scripts, we're skipping this part if we get here during a perl syntax
|
||||
# check -- runtests.pl compiles scripts without running them, so we
|
||||
# need to make sure that this check doesn't apply to 'perl -c' calls.
|
||||
#
|
||||
# This code must go here. It cannot go anywhere in Bugzilla::CGI, because
|
||||
# it uses Template, and that causes various dependency loops.
|
||||
if (!$^C && Bugzilla->params->{"shutdownhtml"}
|
||||
&& lsearch(SHUTDOWNHTML_EXEMPT, basename($0)) == -1)
|
||||
{
|
||||
# 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 (lsearch(SHUTDOWNHTML_EXIT_SILENTLY, basename($0)) > -1
|
||||
&& !i_am_cgi())
|
||||
{
|
||||
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';
|
||||
}
|
||||
print Bugzilla->cgi->header() if i_am_cgi();
|
||||
my $t_output;
|
||||
$template->process("global/message.$extension.tmpl", $vars, \$t_output)
|
||||
|| ThrowTemplateError($template->error);
|
||||
print $t_output . "\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
init_page() if !$ENV{MOD_PERL};
|
||||
|
||||
#####################################################################
|
||||
# Subroutines and Methods
|
||||
#####################################################################
|
||||
|
||||
sub template {
|
||||
my $class = shift;
|
||||
$class->request_cache->{language} = "";
|
||||
$class->request_cache->{template} ||= Bugzilla::Template->create();
|
||||
return $class->request_cache->{template};
|
||||
}
|
||||
|
||||
sub template_inner {
|
||||
my ($class, $lang) = @_;
|
||||
$lang = defined($lang) ? $lang : ($class->request_cache->{language} || "");
|
||||
$class->request_cache->{language} = $lang;
|
||||
$class->request_cache->{"template_inner_$lang"}
|
||||
||= Bugzilla::Template->create();
|
||||
return $class->request_cache->{"template_inner_$lang"};
|
||||
}
|
||||
|
||||
sub cgi {
|
||||
my $class = shift;
|
||||
$class->request_cache->{cgi} ||= new Bugzilla::CGI();
|
||||
return $class->request_cache->{cgi};
|
||||
}
|
||||
|
||||
sub localconfig {
|
||||
my $class = shift;
|
||||
$class->request_cache->{localconfig} ||= read_localconfig();
|
||||
return $class->request_cache->{localconfig};
|
||||
}
|
||||
|
||||
sub params {
|
||||
my $class = shift;
|
||||
$class->request_cache->{params} ||= Bugzilla::Config::read_param_file();
|
||||
return $class->request_cache->{params};
|
||||
}
|
||||
|
||||
sub user {
|
||||
my $class = shift;
|
||||
$class->request_cache->{user} ||= new Bugzilla::User;
|
||||
return $class->request_cache->{user};
|
||||
}
|
||||
|
||||
sub set_user {
|
||||
my ($class, $user) = @_;
|
||||
$class->request_cache->{user} = $user;
|
||||
}
|
||||
|
||||
sub sudoer {
|
||||
my $class = shift;
|
||||
return $class->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 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;
|
||||
}
|
||||
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 $sudo_cookie = $class->cgi->cookie('sudo');
|
||||
detaint_natural($sudo_cookie) if defined($sudo_cookie);
|
||||
my $sudo_target;
|
||||
$sudo_target = new Bugzilla::User($sudo_cookie) if defined($sudo_cookie);
|
||||
if (defined($authenticated_user) &&
|
||||
$authenticated_user->in_group('bz_sudoers') &&
|
||||
defined($sudo_cookie) &&
|
||||
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 {
|
||||
$class->set_user($authenticated_user);
|
||||
}
|
||||
|
||||
# We run after the login has completed since
|
||||
# some of the checks in ssl_require_redirect
|
||||
# look for Bugzilla->user->id to determine
|
||||
# if redirection is required.
|
||||
if (i_am_cgi() && ssl_require_redirect()) {
|
||||
$class->cgi->require_https($class->params->{'sslbase'});
|
||||
}
|
||||
|
||||
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 dbh {
|
||||
my $class = shift;
|
||||
# If we're not connected, then we must want the main db
|
||||
$class->request_cache->{dbh} ||= $class->request_cache->{dbh_main}
|
||||
= Bugzilla::DB::connect_main();
|
||||
|
||||
return $class->request_cache->{dbh};
|
||||
}
|
||||
|
||||
sub languages {
|
||||
my $class = shift;
|
||||
return $class->request_cache->{languages}
|
||||
if $class->request_cache->{languages};
|
||||
|
||||
my @files = glob(catdir(bz_locations->{'templatedir'}, '*'));
|
||||
my @languages;
|
||||
foreach my $dir_entry (@files) {
|
||||
# It's a language directory only if it contains "default" or
|
||||
# "custom". This auto-excludes CVS directories as well.
|
||||
next unless (-d catdir($dir_entry, 'default')
|
||||
|| -d catdir($dir_entry, 'custom'));
|
||||
$dir_entry = basename($dir_entry);
|
||||
# Check for language tag format conforming to RFC 1766.
|
||||
next unless $dir_entry =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
|
||||
push(@languages, $dir_entry);
|
||||
}
|
||||
return $class->request_cache->{languages} = \@languages;
|
||||
}
|
||||
|
||||
sub error_mode {
|
||||
my ($class, $newval) = @_;
|
||||
if (defined $newval) {
|
||||
$class->request_cache->{error_mode} = $newval;
|
||||
}
|
||||
return $class->request_cache->{error_mode}
|
||||
|| Bugzilla::Constants::ERROR_MODE_WEBPAGE;
|
||||
}
|
||||
|
||||
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_WEBSERVICE) {
|
||||
$class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
|
||||
}
|
||||
elsif ($newval == USAGE_MODE_EMAIL) {
|
||||
$class->error_mode(ERROR_MODE_DIE);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('usage_mode_invalid',
|
||||
{'invalid_usage_mode', $newval});
|
||||
}
|
||||
$class->request_cache->{usage_mode} = $newval;
|
||||
}
|
||||
return $class->request_cache->{usage_mode}
|
||||
|| Bugzilla::Constants::USAGE_MODE_BROWSER;
|
||||
}
|
||||
|
||||
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} = request_cache()->{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->request_cache->{dbh_main};
|
||||
# We have to return $class->dbh instead of {dbh} as
|
||||
# {dbh_main} may be undefined if no connection to the main DB
|
||||
# has been established yet.
|
||||
return $class->dbh;
|
||||
}
|
||||
|
||||
sub get_fields {
|
||||
my $class = shift;
|
||||
my $criteria = shift;
|
||||
# This function may be called during installation, and Field::match
|
||||
# may fail at that time. so we want to return an empty list in that
|
||||
# case.
|
||||
my $fields = eval { Bugzilla::Field->match($criteria) } || [];
|
||||
return @$fields;
|
||||
}
|
||||
|
||||
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::has_flags();
|
||||
}
|
||||
return $class->request_cache->{has_flags};
|
||||
}
|
||||
|
||||
sub hook_args {
|
||||
my ($class, $args) = @_;
|
||||
$class->request_cache->{hook_args} = $args if $args;
|
||||
return $class->request_cache->{hook_args};
|
||||
}
|
||||
|
||||
sub request_cache {
|
||||
if ($ENV{MOD_PERL}) {
|
||||
require Apache2::RequestUtil;
|
||||
return Apache2::RequestUtil->request->pnotes();
|
||||
}
|
||||
return $_request_cache;
|
||||
}
|
||||
|
||||
# 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 $main = Bugzilla->request_cache->{dbh_main};
|
||||
my $shadow = Bugzilla->request_cache->{dbh_shadow};
|
||||
foreach my $dbh ($main, $shadow) {
|
||||
next if !$dbh;
|
||||
$dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
|
||||
$dbh->disconnect;
|
||||
}
|
||||
undef $_request_cache;
|
||||
}
|
||||
|
||||
sub END {
|
||||
# Bugzilla.pm cannot compile in mod_perl.pl if this runs.
|
||||
_cleanup() unless $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 Bugzilla->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<user>
|
||||
|
||||
C<undef> 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<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 his 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<error_mode>
|
||||
|
||||
Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
|
||||
or C<Bugzilla->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->error_mode> will return the current state of this flag.
|
||||
|
||||
Note that C<Bugzilla->error_mode> is being called by C<Bugzilla->usage_mode> on
|
||||
usage mode changes.
|
||||
|
||||
=item C<usage_mode>
|
||||
|
||||
Call either C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
|
||||
or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE)> 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->error_mode> to set an error mode which makes sense for the
|
||||
usage mode.
|
||||
|
||||
C<Bugzilla->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<languages>
|
||||
|
||||
Currently installed languages.
|
||||
Returns a reference to a list of RFC 1766 language tags of installed languages.
|
||||
|
||||
=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<params>
|
||||
|
||||
The current Parameters of Bugzilla, as a hashref. If C<data/params>
|
||||
does not exist, then we return an empty hashref. If C<data/params>
|
||||
is unreadable or is not valid perl, we C<die>.
|
||||
|
||||
=item C<hook_args>
|
||||
|
||||
If you are running inside a code hook (see L<Bugzilla::Hook>) this
|
||||
is how you get the arguments passed to the hook.
|
||||
|
||||
=back
|
||||
@@ -1,959 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Attachment;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Attachment - a file related to a bug that a user has uploaded
|
||||
to the Bugzilla server
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Attachment;
|
||||
|
||||
# Get the attachment with the given ID.
|
||||
my $attachment = Bugzilla::Attachment->get($attach_id);
|
||||
|
||||
# Get the attachments with the given IDs.
|
||||
my $attachments = Bugzilla::Attachment->get_list($attach_ids);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module defines attachment objects, which represent files related to bugs
|
||||
that users upload to the Bugzilla server.
|
||||
|
||||
=cut
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Flag;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Field;
|
||||
|
||||
sub get {
|
||||
my $invocant = shift;
|
||||
my $id = shift;
|
||||
|
||||
my $attachments = _retrieve([$id]);
|
||||
my $self = $attachments->[0];
|
||||
bless($self, ref($invocant) || $invocant) if $self;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_list {
|
||||
my $invocant = shift;
|
||||
my $ids = shift;
|
||||
|
||||
my $attachments = _retrieve($ids);
|
||||
foreach my $attachment (@$attachments) {
|
||||
bless($attachment, ref($invocant) || $invocant);
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
sub _retrieve {
|
||||
my ($ids) = @_;
|
||||
|
||||
return [] if scalar(@$ids) == 0;
|
||||
|
||||
my @columns = (
|
||||
'attachments.attach_id AS id',
|
||||
'attachments.bug_id AS bug_id',
|
||||
'attachments.description AS description',
|
||||
'attachments.mimetype AS contenttype',
|
||||
'attachments.submitter_id AS attacher_id',
|
||||
Bugzilla->dbh->sql_date_format('attachments.creation_ts',
|
||||
'%Y.%m.%d %H:%i') . " AS attached",
|
||||
'attachments.modification_time',
|
||||
'attachments.filename AS filename',
|
||||
'attachments.ispatch AS ispatch',
|
||||
'attachments.isurl AS isurl',
|
||||
'attachments.isobsolete AS isobsolete',
|
||||
'attachments.isprivate AS isprivate'
|
||||
);
|
||||
my $columns = join(", ", @columns);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $records = $dbh->selectall_arrayref(
|
||||
"SELECT $columns
|
||||
FROM attachments
|
||||
WHERE "
|
||||
. Bugzilla->dbh->sql_in('attach_id', $ids)
|
||||
. " ORDER BY attach_id",
|
||||
{ Slice => {} });
|
||||
return $records;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Properties
|
||||
|
||||
=over
|
||||
|
||||
=item C<id>
|
||||
|
||||
the unique identifier for the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub id {
|
||||
my $self = shift;
|
||||
return $self->{id};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug_id>
|
||||
|
||||
the ID of the bug to which the attachment is attached
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
# XXX Once Bug.pm slims down sufficiently this should become a reference
|
||||
# to a bug object.
|
||||
sub bug_id {
|
||||
my $self = shift;
|
||||
return $self->{bug_id};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<description>
|
||||
|
||||
user-provided text describing the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub description {
|
||||
my $self = shift;
|
||||
return $self->{description};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<contenttype>
|
||||
|
||||
the attachment's MIME media type
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub contenttype {
|
||||
my $self = shift;
|
||||
return $self->{contenttype};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<attacher>
|
||||
|
||||
the user who attached the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub attacher {
|
||||
my $self = shift;
|
||||
return $self->{attacher} if exists $self->{attacher};
|
||||
$self->{attacher} = new Bugzilla::User($self->{attacher_id});
|
||||
return $self->{attacher};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<attached>
|
||||
|
||||
the date and time on which the attacher attached the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub attached {
|
||||
my $self = shift;
|
||||
return $self->{attached};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<modification_time>
|
||||
|
||||
the date and time on which the attachment was last modified.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub modification_time {
|
||||
my $self = shift;
|
||||
return $self->{modification_time};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<filename>
|
||||
|
||||
the name of the file the attacher attached
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub filename {
|
||||
my $self = shift;
|
||||
return $self->{filename};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<ispatch>
|
||||
|
||||
whether or not the attachment is a patch
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub ispatch {
|
||||
my $self = shift;
|
||||
return $self->{ispatch};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isurl>
|
||||
|
||||
whether or not the attachment is a URL
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isurl {
|
||||
my $self = shift;
|
||||
return $self->{isurl};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isobsolete>
|
||||
|
||||
whether or not the attachment is obsolete
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isobsolete {
|
||||
my $self = shift;
|
||||
return $self->{isobsolete};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isprivate>
|
||||
|
||||
whether or not the attachment is private
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isprivate {
|
||||
my $self = shift;
|
||||
return $self->{isprivate};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<is_viewable>
|
||||
|
||||
Returns 1 if the attachment has a content-type viewable in this browser.
|
||||
Note that we don't use $cgi->Accept()'s ability to check if a content-type
|
||||
matches, because this will return a value even if it's matched by the generic
|
||||
*/* which most browsers add to the end of their Accept: headers.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub is_viewable {
|
||||
my $self = shift;
|
||||
my $contenttype = $self->contenttype;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# We assume we can view all text and image types.
|
||||
return 1 if ($contenttype =~ /^(text|image)\//);
|
||||
|
||||
# Mozilla can view XUL. Note the trailing slash on the Gecko detection to
|
||||
# avoid sending XUL to Safari.
|
||||
return 1 if (($contenttype =~ /^application\/vnd\.mozilla\./)
|
||||
&& ($cgi->user_agent() =~ /Gecko\//));
|
||||
|
||||
# If it's not one of the above types, we check the Accept: header for any
|
||||
# types mentioned explicitly.
|
||||
my $accept = join(",", $cgi->Accept());
|
||||
return 1 if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<data>
|
||||
|
||||
the content of the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
return $self->{data} if exists $self->{data};
|
||||
|
||||
# First try to get the attachment data from the database.
|
||||
($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata
|
||||
FROM attach_data
|
||||
WHERE id = ?",
|
||||
undef,
|
||||
$self->{id});
|
||||
|
||||
# If there's no attachment data in the database, the attachment is stored
|
||||
# in a local file, so retrieve it from there.
|
||||
if (length($self->{data}) == 0) {
|
||||
if (open(AH, $self->_get_local_filename())) {
|
||||
local $/;
|
||||
binmode AH;
|
||||
$self->{data} = <AH>;
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{data};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<datasize>
|
||||
|
||||
the length (in characters) of the attachment content
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
# datasize is a property of the data itself, and it's unclear whether we should
|
||||
# expose it at all, since you can easily derive it from the data itself: in TT,
|
||||
# attachment.data.size; in Perl, length($attachment->{data}). But perhaps
|
||||
# it makes sense for performance reasons, since accessing the data forces it
|
||||
# to get retrieved from the database/filesystem and loaded into memory,
|
||||
# while datasize avoids loading the attachment into memory, calling SQL's
|
||||
# LENGTH() function or stat()ing the file instead. I've left it in for now.
|
||||
|
||||
sub datasize {
|
||||
my $self = shift;
|
||||
return $self->{datasize} if exists $self->{datasize};
|
||||
|
||||
# If we have already retrieved the data, return its size.
|
||||
return length($self->{data}) if exists $self->{data};
|
||||
|
||||
$self->{datasize} =
|
||||
Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata)
|
||||
FROM attach_data
|
||||
WHERE id = ?",
|
||||
undef, $self->{id}) || 0;
|
||||
|
||||
# If there's no attachment data in the database, either the attachment
|
||||
# is stored in a local file, and so retrieve its size from the file,
|
||||
# or the attachment has been deleted.
|
||||
unless ($self->{datasize}) {
|
||||
if (open(AH, $self->_get_local_filename())) {
|
||||
binmode AH;
|
||||
$self->{datasize} = (stat(AH))[7];
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{datasize};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<flags>
|
||||
|
||||
flags that have been set on the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub flags {
|
||||
my $self = shift;
|
||||
return $self->{flags} if exists $self->{flags};
|
||||
|
||||
$self->{flags} = Bugzilla::Flag->match({ 'attach_id' => $self->id });
|
||||
return $self->{flags};
|
||||
}
|
||||
|
||||
# Instance methods; no POD documentation here yet because the only ones so far
|
||||
# are private.
|
||||
|
||||
sub _get_local_filename {
|
||||
my $self = shift;
|
||||
my $hash = ($self->id % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
|
||||
}
|
||||
|
||||
sub _validate_filename {
|
||||
my ($throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
defined $cgi->upload('data')
|
||||
|| ($throw_error ? ThrowUserError("file_not_specified") : return 0);
|
||||
|
||||
my $filename = $cgi->upload('data');
|
||||
|
||||
# Remove path info (if any) from the file name. The browser should do this
|
||||
# for us, but some are buggy. This may not work on Mac file names and could
|
||||
# mess up file names with slashes in them, but them's the breaks. We only
|
||||
# use this as a hint to users downloading attachments anyway, so it's not
|
||||
# a big deal if it munges incorrectly occasionally.
|
||||
$filename =~ s/^.*[\/\\]//;
|
||||
|
||||
# Truncate the filename to 100 characters, counting from the end of the
|
||||
# string to make sure we keep the filename extension.
|
||||
$filename = substr($filename, -100, 100);
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
sub _validate_data {
|
||||
my ($throw_error, $hr_vars) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $maxsize = $cgi->param('ispatch') ? Bugzilla->params->{'maxpatchsize'}
|
||||
: Bugzilla->params->{'maxattachmentsize'};
|
||||
$maxsize *= 1024; # Convert from K
|
||||
my $fh;
|
||||
# Skip uploading into a local variable if the user wants to upload huge
|
||||
# attachments into local files.
|
||||
if (!$cgi->param('bigfile')) {
|
||||
$fh = $cgi->upload('data');
|
||||
}
|
||||
my $data;
|
||||
|
||||
# We could get away with reading only as much as required, except that then
|
||||
# we wouldn't have a size to print to the error handler below.
|
||||
if (!$cgi->param('bigfile')) {
|
||||
# enable 'slurp' mode
|
||||
local $/;
|
||||
$data = <$fh>;
|
||||
}
|
||||
|
||||
$data
|
||||
|| ($cgi->param('bigfile'))
|
||||
|| ($throw_error ? ThrowUserError("zero_length_file") : return 0);
|
||||
|
||||
# Windows screenshots are usually uncompressed BMP files which
|
||||
# makes for a quick way to eat up disk space. Let's compress them.
|
||||
# We do this before we check the size since the uncompressed version
|
||||
# could easily be greater than maxattachmentsize.
|
||||
if (Bugzilla->params->{'convert_uncompressed_images'}
|
||||
&& $cgi->param('contenttype') eq 'image/bmp') {
|
||||
require Image::Magick;
|
||||
my $img = Image::Magick->new(magick=>'bmp');
|
||||
$img->BlobToImage($data);
|
||||
$img->set(magick=>'png');
|
||||
my $imgdata = $img->ImageToBlob();
|
||||
$data = $imgdata;
|
||||
$cgi->param('contenttype', 'image/png');
|
||||
$hr_vars->{'convertedbmp'} = 1;
|
||||
}
|
||||
|
||||
# Make sure the attachment does not exceed the maximum permitted size
|
||||
my $len = $data ? length($data) : 0;
|
||||
if ($maxsize && $len > $maxsize) {
|
||||
my $vars = { filesize => sprintf("%.0f", $len/1024) };
|
||||
if ($cgi->param('ispatch')) {
|
||||
$throw_error ? ThrowUserError("patch_too_large", $vars) : return 0;
|
||||
}
|
||||
else {
|
||||
$throw_error ? ThrowUserError("file_too_large", $vars) : return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return $data || '';
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Class Methods
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_attachments_by_bug($bug_id)>
|
||||
|
||||
Description: retrieves and returns the attachments the currently logged in
|
||||
user can view for the given bug.
|
||||
|
||||
Params: C<$bug_id> - integer - the ID of the bug for which
|
||||
to retrieve and return attachments.
|
||||
|
||||
Returns: a reference to an array of attachment objects.
|
||||
|
||||
=cut
|
||||
|
||||
sub get_attachments_by_bug {
|
||||
my ($class, $bug_id) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# By default, private attachments are not accessible, unless the user
|
||||
# is in the insider group or submitted the attachment.
|
||||
my $and_restriction = '';
|
||||
my @values = ($bug_id);
|
||||
|
||||
unless ($user->is_insider) {
|
||||
$and_restriction = 'AND (isprivate = 0 OR submitter_id = ?)';
|
||||
push(@values, $user->id);
|
||||
}
|
||||
|
||||
my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
|
||||
WHERE bug_id = ? $and_restriction",
|
||||
undef, @values);
|
||||
my $attachments = Bugzilla::Attachment->get_list($attach_ids);
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_is_patch()>
|
||||
|
||||
Description: validates the "patch" flag passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_is_patch {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# Set the ispatch flag to zero if it is undefined, since the UI uses
|
||||
# an HTML checkbox to represent this flag, and unchecked HTML checkboxes
|
||||
# do not get sent in HTML requests.
|
||||
$cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
|
||||
|
||||
# Set the content type to text/plain if the attachment is a patch.
|
||||
$cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_description()>
|
||||
|
||||
Description: validates the description passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_description {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
$cgi->param('description')
|
||||
|| ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_content_type()>
|
||||
|
||||
Description: validates the content type passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_content_type {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
if (!defined $cgi->param('contenttypemethod')) {
|
||||
$throw_error ? ThrowUserError("missing_content_type_method") : return 0;
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
|
||||
my $contenttype =
|
||||
$cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
|
||||
# The user asked us to auto-detect the content type, so use the type
|
||||
# specified in the HTTP request headers.
|
||||
if ( !$contenttype ) {
|
||||
$throw_error ? ThrowUserError("missing_content_type") : return 0;
|
||||
}
|
||||
$cgi->param('contenttype', $contenttype);
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'list') {
|
||||
# The user selected a content type from the list, so use their
|
||||
# selection.
|
||||
$cgi->param('contenttype', $cgi->param('contenttypeselection'));
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'manual') {
|
||||
# The user entered a content type manually, so use their entry.
|
||||
$cgi->param('contenttype', $cgi->param('contenttypeentry'));
|
||||
}
|
||||
else {
|
||||
$throw_error ?
|
||||
ThrowCodeError("illegal_content_type_method",
|
||||
{ contenttypemethod => $cgi->param('contenttypemethod') }) :
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( $cgi->param('contenttype') !~
|
||||
/^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
|
||||
$throw_error ?
|
||||
ThrowUserError("invalid_content_type",
|
||||
{ contenttype => $cgi->param('contenttype') }) :
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_can_edit($attachment, $product_id)>
|
||||
|
||||
Description: validates if the user is allowed to view and edit the attachment.
|
||||
Only the submitter or someone with editbugs privs can edit it.
|
||||
Only the submitter and users in the insider group can view
|
||||
private attachments.
|
||||
|
||||
Params: $attachment - the attachment object being edited.
|
||||
$product_id - the product ID the attachment belongs to.
|
||||
|
||||
Returns: 1 on success. Else an error is thrown.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_can_edit {
|
||||
my ($attachment, $product_id) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
# The submitter can edit their attachments.
|
||||
return 1 if ($attachment->attacher->id == $user->id
|
||||
|| ((!$attachment->isprivate || $user->is_insider)
|
||||
&& $user->in_group('editbugs', $product_id)));
|
||||
|
||||
# If we come here, then this attachment cannot be seen by the user.
|
||||
ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
|
||||
}
|
||||
|
||||
=item C<validate_obsolete($bug)>
|
||||
|
||||
Description: validates if attachments the user wants to mark as obsolete
|
||||
really belong to the given bug and are not already obsolete.
|
||||
Moreover, a user cannot mark an attachment as obsolete if
|
||||
he cannot view it (due to restrictions on it).
|
||||
|
||||
Params: $bug - The bug object obsolete attachments should belong to.
|
||||
|
||||
Returns: 1 on success. Else an error is thrown.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_obsolete {
|
||||
my ($class, $bug) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# Make sure the attachment id is valid and the user has permissions to view
|
||||
# the bug to which it is attached. Make sure also that the user can view
|
||||
# the attachment itself.
|
||||
my @obsolete_attachments;
|
||||
foreach my $attachid ($cgi->param('obsolete')) {
|
||||
my $vars = {};
|
||||
$vars->{'attach_id'} = $attachid;
|
||||
|
||||
detaint_natural($attachid)
|
||||
|| ThrowCodeError('invalid_attach_id_to_obsolete', $vars);
|
||||
|
||||
my $attachment = Bugzilla::Attachment->get($attachid);
|
||||
|
||||
# Make sure the attachment exists in the database.
|
||||
ThrowUserError('invalid_attach_id', $vars) unless $attachment;
|
||||
|
||||
# Check that the user can view and edit this attachment.
|
||||
$attachment->validate_can_edit($bug->product_id);
|
||||
|
||||
$vars->{'description'} = $attachment->description;
|
||||
|
||||
if ($attachment->bug_id != $bug->bug_id) {
|
||||
$vars->{'my_bug_id'} = $bug->bug_id;
|
||||
$vars->{'attach_bug_id'} = $attachment->bug_id;
|
||||
ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
|
||||
}
|
||||
|
||||
if ($attachment->isobsolete) {
|
||||
ThrowCodeError('attachment_already_obsolete', $vars);
|
||||
}
|
||||
|
||||
push(@obsolete_attachments, $attachment);
|
||||
}
|
||||
return @obsolete_attachments;
|
||||
}
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=item C<insert_attachment_for_bug($throw_error, $bug, $user, $timestamp, $hr_vars)>
|
||||
|
||||
Description: inserts an attachment from CGI input for the given bug.
|
||||
|
||||
Params: C<$bug> - Bugzilla::Bug object - the bug for which to insert
|
||||
the attachment.
|
||||
C<$user> - Bugzilla::User object - the user we're inserting an
|
||||
attachment for.
|
||||
C<$timestamp> - scalar - timestamp of the insert as returned
|
||||
by SELECT NOW().
|
||||
C<$hr_vars> - hash reference - reference to a hash of template
|
||||
variables.
|
||||
|
||||
Returns: the ID of the new attachment.
|
||||
|
||||
=cut
|
||||
|
||||
sub insert_attachment_for_bug {
|
||||
my ($class, $throw_error, $bug, $user, $timestamp, $hr_vars) = @_;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $attachurl = $cgi->param('attachurl') || '';
|
||||
my $data;
|
||||
my $filename;
|
||||
my $contenttype;
|
||||
my $isurl;
|
||||
$class->validate_is_patch($throw_error) || return;
|
||||
$class->validate_description($throw_error) || return;
|
||||
|
||||
if (Bugzilla->params->{'allow_attach_url'}
|
||||
&& ($attachurl =~ /^(http|https|ftp):\/\/\S+/)
|
||||
&& !defined $cgi->upload('data'))
|
||||
{
|
||||
$filename = '';
|
||||
$data = $attachurl;
|
||||
$isurl = 1;
|
||||
$contenttype = 'text/plain';
|
||||
$cgi->param('ispatch', 0);
|
||||
$cgi->delete('bigfile');
|
||||
}
|
||||
else {
|
||||
$filename = _validate_filename($throw_error) || return;
|
||||
# need to validate content type before data as
|
||||
# we now check the content type for image/bmp in _validate_data()
|
||||
unless ($cgi->param('ispatch')) {
|
||||
$class->validate_content_type($throw_error) || return;
|
||||
|
||||
# Set the ispatch flag to 1 if we're set to autodetect
|
||||
# and the content type is text/x-diff or text/x-patch
|
||||
if ($cgi->param('contenttypemethod') eq 'autodetect'
|
||||
&& $cgi->param('contenttype') =~ m{text/x-(?:diff|patch)})
|
||||
{
|
||||
$cgi->param('ispatch', 1);
|
||||
$cgi->param('contenttype', 'text/plain');
|
||||
}
|
||||
}
|
||||
$data = _validate_data($throw_error, $hr_vars);
|
||||
# If the attachment is stored locally, $data eq ''.
|
||||
# If an error is thrown, $data eq '0'.
|
||||
($data ne '0') || return;
|
||||
$contenttype = $cgi->param('contenttype');
|
||||
|
||||
# These are inserted using placeholders so no need to panic
|
||||
trick_taint($filename);
|
||||
trick_taint($contenttype);
|
||||
$isurl = 0;
|
||||
}
|
||||
|
||||
# Check attachments the user tries to mark as obsolete.
|
||||
my @obsolete_attachments;
|
||||
if ($cgi->param('obsolete')) {
|
||||
@obsolete_attachments = $class->validate_obsolete($bug);
|
||||
}
|
||||
|
||||
# The order of these function calls is important, as Flag::validate
|
||||
# assumes User::match_field has ensured that the
|
||||
# values in the requestee fields are legitimate user email addresses.
|
||||
my $match_status = Bugzilla::User::match_field($cgi, {
|
||||
'^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
|
||||
}, MATCH_SKIP_CONFIRM);
|
||||
|
||||
$hr_vars->{'match_field'} = 'requestee';
|
||||
if ($match_status == USER_MATCH_FAILED) {
|
||||
$hr_vars->{'message'} = 'user_match_failed';
|
||||
}
|
||||
elsif ($match_status == USER_MATCH_MULTIPLE) {
|
||||
$hr_vars->{'message'} = 'user_match_multiple';
|
||||
}
|
||||
|
||||
# Escape characters in strings that will be used in SQL statements.
|
||||
my $description = $cgi->param('description');
|
||||
trick_taint($description);
|
||||
my $isprivate = $cgi->param('isprivate') ? 1 : 0;
|
||||
|
||||
# Insert the attachment into the database.
|
||||
my $sth = $dbh->do(
|
||||
"INSERT INTO attachments
|
||||
(bug_id, creation_ts, modification_time, filename, description,
|
||||
mimetype, ispatch, isurl, isprivate, submitter_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $timestamp,
|
||||
$filename, $description, $contenttype, $cgi->param('ispatch'),
|
||||
$isurl, $isprivate, $user->id));
|
||||
# Retrieve the ID of the newly created attachment record.
|
||||
my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
|
||||
|
||||
# We only use $data here in this INSERT with a placeholder,
|
||||
# so it's safe.
|
||||
$sth = $dbh->prepare("INSERT INTO attach_data
|
||||
(id, thedata) VALUES ($attachid, ?)");
|
||||
trick_taint($data);
|
||||
$sth->bind_param(1, $data, $dbh->BLOB_TYPE);
|
||||
$sth->execute();
|
||||
|
||||
# If the file is to be stored locally, stream the file from the web server
|
||||
# to the local file without reading it into a local variable.
|
||||
if ($cgi->param('bigfile')) {
|
||||
my $attachdir = bz_locations()->{'attachdir'};
|
||||
my $fh = $cgi->upload('data');
|
||||
my $hash = ($attachid % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
mkdir "$attachdir/$hash", 0770;
|
||||
chmod 0770, "$attachdir/$hash";
|
||||
open(AH, ">$attachdir/$hash/attachment.$attachid");
|
||||
binmode AH;
|
||||
my $sizecount = 0;
|
||||
my $limit = (Bugzilla->params->{"maxlocalattachment"} * 1048576);
|
||||
while (<$fh>) {
|
||||
print AH $_;
|
||||
$sizecount += length($_);
|
||||
if ($sizecount > $limit) {
|
||||
close AH;
|
||||
close $fh;
|
||||
unlink "$attachdir/$hash/attachment.$attachid";
|
||||
$throw_error ? ThrowUserError("local_file_too_large") : return;
|
||||
}
|
||||
}
|
||||
close AH;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
# Make existing attachments obsolete.
|
||||
my $fieldid = get_field_id('attachments.isobsolete');
|
||||
|
||||
foreach my $obsolete_attachment (@obsolete_attachments) {
|
||||
# If the obsolete attachment has request flags, cancel them.
|
||||
# This call must be done before updating the 'attachments' table.
|
||||
Bugzilla::Flag->CancelRequests($bug, $obsolete_attachment, $timestamp);
|
||||
|
||||
$dbh->do('UPDATE attachments SET isobsolete = 1, modification_time = ?
|
||||
WHERE attach_id = ?',
|
||||
undef, ($timestamp, $obsolete_attachment->id));
|
||||
|
||||
$dbh->do('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
|
||||
fieldid, removed, added)
|
||||
VALUES (?,?,?,?,?,?,?)',
|
||||
undef, ($bug->bug_id, $obsolete_attachment->id, $user->id,
|
||||
$timestamp, $fieldid, 0, 1));
|
||||
}
|
||||
|
||||
my $attachment = Bugzilla::Attachment->get($attachid);
|
||||
|
||||
# 1. Add flags, if any. To avoid dying if something goes wrong
|
||||
# while processing flags, we will eval() flag validation.
|
||||
# This requires errors to die().
|
||||
# XXX: this can go away as soon as flag validation is able to
|
||||
# fail without dying.
|
||||
#
|
||||
# 2. Flag::validate() should not detect any reference to existing flags
|
||||
# when creating a new attachment. Setting the third param to -1 will
|
||||
# force this function to check this point.
|
||||
my $error_mode_cache = Bugzilla->error_mode;
|
||||
Bugzilla->error_mode(ERROR_MODE_DIE);
|
||||
eval {
|
||||
Bugzilla::Flag::validate($bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
|
||||
Bugzilla::Flag->process($bug, $attachment, $timestamp, $hr_vars);
|
||||
};
|
||||
Bugzilla->error_mode($error_mode_cache);
|
||||
if ($@) {
|
||||
$hr_vars->{'message'} = 'flag_creation_failed';
|
||||
$hr_vars->{'flag_creation_error'} = $@;
|
||||
}
|
||||
|
||||
# Return the new attachment object.
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: removes an attachment from the DB.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
$dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $self->id);
|
||||
$dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
|
||||
$dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?, isobsolete = ?
|
||||
WHERE attach_id = ?', undef, ('text/plain', 0, 0, 1, $self->id));
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,295 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): John Keiser <john@johnkeiser.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Attachment::PatchReader;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Attachment;
|
||||
use Bugzilla::Util;
|
||||
|
||||
sub process_diff {
|
||||
my ($attachment, $format, $context) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $lc = Bugzilla->localconfig;
|
||||
my $vars = {};
|
||||
|
||||
my ($reader, $last_reader) = setup_patch_readers(undef, $context);
|
||||
|
||||
if ($format eq 'raw') {
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
|
||||
# Actually print out the patch.
|
||||
print $cgi->header(-type => 'text/plain',
|
||||
-expires => '+3M');
|
||||
disable_utf8();
|
||||
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
|
||||
}
|
||||
else {
|
||||
my @other_patches = ();
|
||||
if ($lc->{interdiffbin} && $lc->{diffpath}) {
|
||||
# Get the list of attachments that the user can view in this bug.
|
||||
my @attachments =
|
||||
@{Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id)};
|
||||
# Extract patches only.
|
||||
@attachments = grep {$_->ispatch == 1} @attachments;
|
||||
# We want them sorted from newer to older.
|
||||
@attachments = sort { $b->id <=> $a->id } @attachments;
|
||||
|
||||
# Ignore the current patch, but select the one right before it
|
||||
# chronologically.
|
||||
my $select_next_patch = 0;
|
||||
foreach my $attach (@attachments) {
|
||||
if ($attach->id == $attachment->id) {
|
||||
$select_next_patch = 1;
|
||||
}
|
||||
else {
|
||||
push(@other_patches, { 'id' => $attach->id,
|
||||
'desc' => $attach->description,
|
||||
'selected' => $select_next_patch });
|
||||
$select_next_patch = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$vars->{'bugid'} = $attachment->bug_id;
|
||||
$vars->{'attachid'} = $attachment->id;
|
||||
$vars->{'description'} = $attachment->description;
|
||||
$vars->{'other_patches'} = \@other_patches;
|
||||
|
||||
setup_template_patch_reader($last_reader, $format, $context, $vars);
|
||||
# The patch is going to be displayed in a HTML page and if the utf8
|
||||
# param is enabled, we have to encode attachment data as utf8.
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
$attachment->data; # Populate ->{data}
|
||||
utf8::decode($attachment->{data});
|
||||
}
|
||||
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
|
||||
}
|
||||
}
|
||||
|
||||
sub process_interdiff {
|
||||
my ($old_attachment, $new_attachment, $format, $context) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $lc = Bugzilla->localconfig;
|
||||
my $vars = {};
|
||||
|
||||
# Encode attachment data as utf8 if it's going to be displayed in a HTML
|
||||
# page using the UTF-8 encoding.
|
||||
if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
|
||||
$old_attachment->data; # Populate ->{data}
|
||||
utf8::decode($old_attachment->{data});
|
||||
$new_attachment->data; # Populate ->{data}
|
||||
utf8::decode($new_attachment->{data});
|
||||
}
|
||||
|
||||
# Get old patch data.
|
||||
my ($old_filename, $old_file_list) = get_unified_diff($old_attachment, $format);
|
||||
# Get new patch data.
|
||||
my ($new_filename, $new_file_list) = get_unified_diff($new_attachment, $format);
|
||||
|
||||
my $warning = warn_if_interdiff_might_fail($old_file_list, $new_file_list);
|
||||
|
||||
# Send through interdiff, send output directly to template.
|
||||
# Must hack path so that interdiff will work.
|
||||
$ENV{'PATH'} = $lc->{diffpath};
|
||||
open my $interdiff_fh, "$lc->{interdiffbin} $old_filename $new_filename|";
|
||||
binmode $interdiff_fh;
|
||||
my ($reader, $last_reader) = setup_patch_readers("", $context);
|
||||
|
||||
if ($format eq 'raw') {
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
|
||||
# Actually print out the patch.
|
||||
print $cgi->header(-type => 'text/plain',
|
||||
-expires => '+3M');
|
||||
disable_utf8();
|
||||
}
|
||||
else {
|
||||
# In case the HTML page is displayed with the UTF-8 encoding.
|
||||
binmode $interdiff_fh, ':utf8' if Bugzilla->params->{'utf8'};
|
||||
|
||||
$vars->{'warning'} = $warning if $warning;
|
||||
$vars->{'bugid'} = $new_attachment->bug_id;
|
||||
$vars->{'oldid'} = $old_attachment->id;
|
||||
$vars->{'old_desc'} = $old_attachment->description;
|
||||
$vars->{'newid'} = $new_attachment->id;
|
||||
$vars->{'new_desc'} = $new_attachment->description;
|
||||
|
||||
setup_template_patch_reader($last_reader, $format, $context, $vars);
|
||||
}
|
||||
$reader->iterate_fh($interdiff_fh, 'interdiff #' . $old_attachment->id .
|
||||
' #' . $new_attachment->id);
|
||||
close $interdiff_fh;
|
||||
$ENV{'PATH'} = '';
|
||||
|
||||
# Delete temporary files.
|
||||
unlink($old_filename) or warn "Could not unlink $old_filename: $!";
|
||||
unlink($new_filename) or warn "Could not unlink $new_filename: $!";
|
||||
}
|
||||
|
||||
######################
|
||||
# Internal routines
|
||||
######################
|
||||
|
||||
sub get_unified_diff {
|
||||
my ($attachment, $format) = @_;
|
||||
|
||||
# Bring in the modules we need.
|
||||
require PatchReader::Raw;
|
||||
require PatchReader::FixPatchRoot;
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
require PatchReader::PatchInfoGrabber;
|
||||
require File::Temp;
|
||||
|
||||
$attachment->ispatch
|
||||
|| ThrowCodeError('must_be_patch', { 'attach_id' => $attachment->id });
|
||||
|
||||
# Reads in the patch, converting to unified diff in a temp file.
|
||||
my $reader = new PatchReader::Raw;
|
||||
my $last_reader = $reader;
|
||||
|
||||
# Fixes patch root (makes canonical if possible).
|
||||
if (Bugzilla->params->{'cvsroot'}) {
|
||||
my $fix_patch_root =
|
||||
new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'});
|
||||
$last_reader->sends_data_to($fix_patch_root);
|
||||
$last_reader = $fix_patch_root;
|
||||
}
|
||||
|
||||
# Grabs the patch file info.
|
||||
my $patch_info_grabber = new PatchReader::PatchInfoGrabber();
|
||||
$last_reader->sends_data_to($patch_info_grabber);
|
||||
$last_reader = $patch_info_grabber;
|
||||
|
||||
# Prints out to temporary file.
|
||||
my ($fh, $filename) = File::Temp::tempfile();
|
||||
if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
|
||||
# The HTML page will be displayed with the UTF-8 encoding.
|
||||
binmode $fh, ':utf8';
|
||||
}
|
||||
my $raw_printer = new PatchReader::DiffPrinter::raw($fh);
|
||||
$last_reader->sends_data_to($raw_printer);
|
||||
$last_reader = $raw_printer;
|
||||
|
||||
# Iterate!
|
||||
$reader->iterate_string($attachment->id, $attachment->data);
|
||||
|
||||
return ($filename, $patch_info_grabber->patch_info()->{files});
|
||||
}
|
||||
|
||||
sub warn_if_interdiff_might_fail {
|
||||
my ($old_file_list, $new_file_list) = @_;
|
||||
|
||||
# Verify that the list of files diffed is the same.
|
||||
my @old_files = sort keys %{$old_file_list};
|
||||
my @new_files = sort keys %{$new_file_list};
|
||||
if (@old_files != @new_files
|
||||
|| join(' ', @old_files) ne join(' ', @new_files))
|
||||
{
|
||||
return 'interdiff1';
|
||||
}
|
||||
|
||||
# Verify that the revisions in the files are the same.
|
||||
foreach my $file (keys %{$old_file_list}) {
|
||||
if ($old_file_list->{$file}{old_revision} ne
|
||||
$new_file_list->{$file}{old_revision})
|
||||
{
|
||||
return 'interdiff2';
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub setup_patch_readers {
|
||||
my ($diff_root, $context) = @_;
|
||||
|
||||
# Parameters:
|
||||
# format=raw|html
|
||||
# context=patch|file|0-n
|
||||
# collapsed=0|1
|
||||
# headers=0|1
|
||||
|
||||
# Define the patch readers.
|
||||
# The reader that reads the patch in (whatever its format).
|
||||
require PatchReader::Raw;
|
||||
my $reader = new PatchReader::Raw;
|
||||
my $last_reader = $reader;
|
||||
# Fix the patch root if we have a cvs root.
|
||||
if (Bugzilla->params->{'cvsroot'}) {
|
||||
require PatchReader::FixPatchRoot;
|
||||
$last_reader->sends_data_to(new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'}));
|
||||
$last_reader->sends_data_to->diff_root($diff_root) if defined($diff_root);
|
||||
$last_reader = $last_reader->sends_data_to;
|
||||
}
|
||||
|
||||
# Add in cvs context if we have the necessary info to do it
|
||||
if ($context ne 'patch' && Bugzilla->localconfig->{cvsbin}
|
||||
&& Bugzilla->params->{'cvsroot_get'})
|
||||
{
|
||||
require PatchReader::AddCVSContext;
|
||||
# We need to set $cvsbin as global, because PatchReader::CVSClient
|
||||
# needs it in order to find 'cvs'.
|
||||
$main::cvsbin = Bugzilla->localconfig->{cvsbin};
|
||||
$last_reader->sends_data_to(
|
||||
new PatchReader::AddCVSContext($context, Bugzilla->params->{'cvsroot_get'}));
|
||||
$last_reader = $last_reader->sends_data_to;
|
||||
}
|
||||
|
||||
return ($reader, $last_reader);
|
||||
}
|
||||
|
||||
sub setup_template_patch_reader {
|
||||
my ($last_reader, $format, $context, $vars) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
require PatchReader::DiffPrinter::template;
|
||||
|
||||
# Define the vars for templates.
|
||||
if (defined $cgi->param('headers')) {
|
||||
$vars->{'headers'} = $cgi->param('headers');
|
||||
}
|
||||
else {
|
||||
$vars->{'headers'} = 1;
|
||||
}
|
||||
|
||||
$vars->{'collapsed'} = $cgi->param('collapsed');
|
||||
$vars->{'context'} = $context;
|
||||
$vars->{'do_context'} = Bugzilla->localconfig->{cvsbin}
|
||||
&& Bugzilla->params->{'cvsroot_get'} && !$vars->{'newid'};
|
||||
|
||||
# Print everything out.
|
||||
print $cgi->header(-type => 'text/html',
|
||||
-expires => '+3M');
|
||||
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
|
||||
"attachment/diff-header.$format.tmpl",
|
||||
"attachment/diff-file.$format.tmpl",
|
||||
"attachment/diff-footer.$format.tmpl",
|
||||
{ %{$vars},
|
||||
bonsai_url => Bugzilla->params->{'bonsai_url'},
|
||||
lxr_url => Bugzilla->params->{'lxr_url'},
|
||||
lxr_root => Bugzilla->params->{'lxr_root'},
|
||||
}));
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
@@ -1,466 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth;
|
||||
|
||||
use strict;
|
||||
use fields qw(
|
||||
_info_getter
|
||||
_verifier
|
||||
_persister
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Auth::Login::Stack;
|
||||
use Bugzilla::Auth::Verify::Stack;
|
||||
use Bugzilla::Auth::Persist::Cookie;
|
||||
|
||||
sub new {
|
||||
my ($class, $params) = @_;
|
||||
my $self = fields::new($class);
|
||||
|
||||
$params ||= {};
|
||||
$params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie';
|
||||
$params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
|
||||
|
||||
$self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
|
||||
$self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
|
||||
# If we ever have any other login persistence methods besides cookies,
|
||||
# this could become more configurable.
|
||||
$self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub login {
|
||||
my ($self, $type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Get login info from the cookie, form, environment variables, etc.
|
||||
my $login_info = $self->{_info_getter}->get_login_info();
|
||||
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
# Now verify his username and password against the DB, LDAP, etc.
|
||||
if ($self->{_info_getter}->{successful}->requires_verification) {
|
||||
$login_info = $self->{_verifier}->check_credentials($login_info);
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
$login_info =
|
||||
$self->{_verifier}->{successful}->create_or_update_user($login_info);
|
||||
}
|
||||
else {
|
||||
$login_info = $self->{_verifier}->create_or_update_user($login_info);
|
||||
}
|
||||
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
# Make sure the user isn't disabled.
|
||||
my $user = $login_info->{user};
|
||||
if ($user->disabledtext) {
|
||||
return $self->_handle_login_result({ failure => AUTH_DISABLED,
|
||||
user => $user }, $type);
|
||||
}
|
||||
$user->set_authorizer($self);
|
||||
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
my ($self) = @_;
|
||||
my $verifier = $self->{_verifier}->{successful};
|
||||
$verifier ||= $self->{_verifier};
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $verifier->can_change_password &&
|
||||
$getter->user_can_create_account;
|
||||
}
|
||||
|
||||
sub can_login {
|
||||
my ($self) = @_;
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $getter->can_login;
|
||||
}
|
||||
|
||||
sub can_logout {
|
||||
my ($self) = @_;
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
# If there's no successful getter, we're not logged in, so of
|
||||
# course we can't log out!
|
||||
return 0 unless $getter;
|
||||
return $getter->can_logout;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
my $verifier = $self->{_verifier}->{successful};
|
||||
$verifier ||= $self->{_verifier};
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $verifier->user_can_create_account
|
||||
&& $getter->user_can_create_account;
|
||||
}
|
||||
|
||||
sub can_change_email {
|
||||
return $_[0]->user_can_create_account;
|
||||
}
|
||||
|
||||
sub _handle_login_result {
|
||||
my ($self, $result, $login_type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $user = $result->{user};
|
||||
my $fail_code = $result->{failure};
|
||||
|
||||
if (!$fail_code) {
|
||||
if ($self->{_info_getter}->{successful}->requires_persistence) {
|
||||
$self->{_persister}->persist_login($user);
|
||||
}
|
||||
}
|
||||
elsif ($fail_code == AUTH_ERROR) {
|
||||
ThrowCodeError($result->{error}, $result->{details});
|
||||
}
|
||||
elsif ($fail_code == AUTH_NODATA) {
|
||||
if ($login_type == LOGIN_REQUIRED) {
|
||||
# This seems like as good as time as any to get rid of
|
||||
# old crufty junk in the logincookies table. Get rid
|
||||
# of any entry that hasn't been used in a month.
|
||||
$dbh->do("DELETE FROM logincookies WHERE " .
|
||||
$dbh->sql_to_days('NOW()') . " - " .
|
||||
$dbh->sql_to_days('lastused') . " > 30");
|
||||
$self->{_info_getter}->fail_nodata($self);
|
||||
}
|
||||
# Otherwise, we just return the "default" user.
|
||||
$user = Bugzilla->user;
|
||||
}
|
||||
# The username/password may be wrong
|
||||
# Don't let the user know whether the username exists or whether
|
||||
# the password was just wrong. (This makes it harder for a cracker
|
||||
# to find account names by brute force)
|
||||
elsif (($fail_code == AUTH_LOGINFAILED) || ($fail_code == AUTH_NO_SUCH_USER)) {
|
||||
ThrowUserError("invalid_username_or_password");
|
||||
}
|
||||
# The account may be disabled
|
||||
elsif ($fail_code == AUTH_DISABLED) {
|
||||
$self->{_persister}->logout();
|
||||
# XXX This is NOT a good way to do this, architecturally.
|
||||
$self->{_persister}->clear_browser_cookies();
|
||||
# and throw a user error
|
||||
ThrowUserError("account_disabled",
|
||||
{'disabled_reason' => $result->{user}->disabledtext});
|
||||
}
|
||||
# If we get here, then we've run out of options, which shouldn't happen.
|
||||
else {
|
||||
ThrowCodeError("authres_unhandled", { value => $fail_code });
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth - An object that authenticates the login credentials for
|
||||
a user.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Handles authentication for Bugzilla users.
|
||||
|
||||
Authentication from Bugzilla involves two sets of modules. One set is
|
||||
used to obtain the username/password (from CGI, email, etc), and the
|
||||
other set uses this data to authenticate against the datasource
|
||||
(the Bugzilla DB, LDAP, PAM, etc.).
|
||||
|
||||
Modules for obtaining the username/password are subclasses of
|
||||
L<Bugzilla::Auth::Login>, and modules for authenticating are subclasses
|
||||
of L<Bugzilla::Auth::Verify>.
|
||||
|
||||
=head1 AUTHENTICATION ERROR CODES
|
||||
|
||||
Whenever a method in the C<Bugzilla::Auth> family fails in some way,
|
||||
it will return a hashref containing at least a single key called C<failure>.
|
||||
C<failure> will point to an integer error code, and depending on the error
|
||||
code the hashref may contain more data.
|
||||
|
||||
The error codes are explained here below.
|
||||
|
||||
=head2 C<AUTH_NODATA>
|
||||
|
||||
Insufficient login data was provided by the user. This may happen in several
|
||||
cases, such as cookie authentication when the cookie is not present.
|
||||
|
||||
=head2 C<AUTH_ERROR>
|
||||
|
||||
An error occurred when trying to use the login mechanism.
|
||||
|
||||
The hashref will also contain an C<error> element, which is the name
|
||||
of an error from C<template/en/default/global/code-error.html> --
|
||||
the same type of error that would be thrown by
|
||||
L<Bugzilla::Error::ThrowCodeError>.
|
||||
|
||||
The hashref *may* contain an element called C<details>, which is a hashref
|
||||
that should be passed to L<Bugzilla::Error::ThrowCodeError> as the
|
||||
various fields to be used in the error message.
|
||||
|
||||
=head2 C<AUTH_LOGINFAILED>
|
||||
|
||||
An incorrect username or password was given.
|
||||
|
||||
=head2 C<AUTH_NO_SUCH_USER>
|
||||
|
||||
This is an optional more-specific version of C<AUTH_LOGINFAILED>.
|
||||
Modules should throw this error when they discover that the
|
||||
requested user account actually does not exist, according to them.
|
||||
|
||||
That is, for example, L<Bugzilla::Auth::Verify::LDAP> would throw
|
||||
this if the user didn't exist in LDAP.
|
||||
|
||||
The difference between C<AUTH_NO_SUCH_USER> and C<AUTH_LOGINFAILED>
|
||||
should never be communicated to the user, for security reasons.
|
||||
|
||||
=head2 C<AUTH_DISABLED>
|
||||
|
||||
The user successfully logged in, but their account has been disabled.
|
||||
Usually this is throw only by C<Bugzilla::Auth::login>.
|
||||
|
||||
=head1 LOGIN TYPES
|
||||
|
||||
The C<login> function (below) can do different types of login, depending
|
||||
on what constant you pass into it:
|
||||
|
||||
=head2 C<LOGIN_OPTIONAL>
|
||||
|
||||
A login is never required to access this data. Attempting to login is
|
||||
still useful, because this allows the page to be personalised. Note that
|
||||
an incorrect login will still trigger an error, even though the lack of
|
||||
a login will be OK.
|
||||
|
||||
=head2 C<LOGIN_NORMAL>
|
||||
|
||||
A login may or may not be required, depending on the setting of the
|
||||
I<requirelogin> parameter. This is the default if you don't specify a
|
||||
type.
|
||||
|
||||
=head2 C<LOGIN_REQUIRED>
|
||||
|
||||
A login is always required to access this data.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
These are methods that can be called on a C<Bugzilla::Auth> object
|
||||
itself.
|
||||
|
||||
=head2 Login
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<login($type)>
|
||||
|
||||
Description: Logs a user in. For more details on how this works
|
||||
internally, see the section entitled "STRUCTURE."
|
||||
Params: $type - One of the Login Types from above.
|
||||
Returns: An authenticated C<Bugzilla::User>. Or, if the type was
|
||||
not C<LOGIN_REQUIRED>, then we return an
|
||||
empty C<Bugzilla::User> if no login data was passed in.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Info Methods
|
||||
|
||||
These are methods that give information about the Bugzilla::Auth object.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<can_change_password>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
changing passwords.
|
||||
Params: None
|
||||
Returns: C<true> if users and administrators should be allowed to
|
||||
change passwords, C<false> otherwise.
|
||||
|
||||
=item C<can_login>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
users to log in through the web interface.
|
||||
Params: None
|
||||
Returns: C<true> if users can log in through the web interface,
|
||||
C<false> otherwise.
|
||||
|
||||
=item C<can_logout>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
users to log themselves out.
|
||||
Params: None
|
||||
Returns: C<true> if users can log themselves out, C<false> otherwise.
|
||||
If a user isn't logged in, we always return C<false>.
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Description: Tells you whether or not users are allowed to manually create
|
||||
their own accounts, based on the current login system in use.
|
||||
Note that this doesn't check the C<createemailregexp>
|
||||
parameter--you have to do that by yourself in your code.
|
||||
Params: None
|
||||
Returns: C<true> if users are allowed to create new Bugzilla accounts,
|
||||
C<false> otherwise.
|
||||
|
||||
=item C<can_change_email>
|
||||
|
||||
Description: Whether or not the current login system allows users to
|
||||
change their own email address.
|
||||
Params: None
|
||||
Returns: C<true> if users can change their own email address,
|
||||
C<false> otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 STRUCTURE
|
||||
|
||||
This section is mostly interesting to developers who want to implement
|
||||
a new authentication type. It describes the general structure of the
|
||||
Bugzilla::Auth family, and how the C<login> function works.
|
||||
|
||||
A C<Bugzilla::Auth> object is essentially a collection of a few other
|
||||
objects: the "Info Getter," the "Verifier," and the "Persistence
|
||||
Mechanism."
|
||||
|
||||
They are used inside the C<login> function in the following order:
|
||||
|
||||
=head2 The Info Getter
|
||||
|
||||
This is a C<Bugzilla::Auth::Login> object. Basically, it gets the
|
||||
username and password from the user, somehow. Or, it just gets enough
|
||||
information to uniquely identify a user, and passes that on down the line.
|
||||
(For example, a C<user_id> is enough to uniquely identify a user,
|
||||
even without a username and password.)
|
||||
|
||||
Some Info Getters don't require any verification. For example, if we got
|
||||
the C<user_id> from a Cookie, we don't need to check the username and
|
||||
password.
|
||||
|
||||
If an Info Getter returns only a C<user_id> and no username/password,
|
||||
then it MUST NOT require verification. If an Info Getter requires
|
||||
verfication, then it MUST return at least a C<username>.
|
||||
|
||||
=head2 The Verifier
|
||||
|
||||
This verifies that the username and password are valid.
|
||||
|
||||
It's possible that some methods of verification don't require a password.
|
||||
|
||||
=head2 The Persistence Mechanism
|
||||
|
||||
This makes it so that the user doesn't have to log in on every page.
|
||||
Normally this object just sends a cookie to the user's web browser,
|
||||
as that's the most common method of "login persistence."
|
||||
|
||||
=head2 Other Things We Do
|
||||
|
||||
After we verify the username and password, sometimes we automatically
|
||||
create an account in the Bugzilla database, for certain authentication
|
||||
types. We use the "Account Source" to get data about the user, and
|
||||
create them in the database. (Or, if their data has changed since the
|
||||
last time they logged in, their data gets updated.)
|
||||
|
||||
=head2 The C<$login_data> Hash
|
||||
|
||||
All of the C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify>
|
||||
methods take an argument called C<$login_data>. This is basically
|
||||
a hash that becomes more and more populated as we go through the
|
||||
C<login> function.
|
||||
|
||||
All C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods
|
||||
also *return* the C<$login_data> structure, when they succeed. They
|
||||
may have added new data to it.
|
||||
|
||||
For all C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods,
|
||||
the rule is "you must return the same hashref you were passed in." You can
|
||||
modify the hashref all you want, but you can't create a new one. The only
|
||||
time you can return a new one is if you're returning some error code
|
||||
instead of the C<$login_data> structure.
|
||||
|
||||
Each C<Bugzilla::Auth::Login> or C<Bugzilla::Auth::Verify> method
|
||||
explains in its documentation which C<$login_data> elements are
|
||||
required by it, and which are set by it.
|
||||
|
||||
Here are all of the elements that *may* be in C<$login_data>:
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<user_id>
|
||||
|
||||
A Bugzilla C<user_id> that uniquely identifies a user.
|
||||
|
||||
=item C<username>
|
||||
|
||||
The username that was provided by the user.
|
||||
|
||||
=item C<bz_username>
|
||||
|
||||
The username of this user inside of Bugzilla. Sometimes this differs from
|
||||
C<username>.
|
||||
|
||||
=item C<password>
|
||||
|
||||
The password provided by the user.
|
||||
|
||||
=item C<realname>
|
||||
|
||||
The real name of the user.
|
||||
|
||||
=item C<extern_id>
|
||||
|
||||
Some string that uniquely identifies the user in an external account
|
||||
source. If this C<extern_id> already exists in the database with
|
||||
a different username, the username will be *changed* to be the
|
||||
username specified in this C<$login_data>.
|
||||
|
||||
That is, let's my extern_id is C<mkanat>. I already have an account
|
||||
in Bugzilla with the username of C<mkanat@foo.com>. But this time,
|
||||
when I log in, I have an extern_id of C<mkanat> and a C<username>
|
||||
of C<mkanat@bar.org>. So now, Bugzilla will automatically change my
|
||||
username to C<mkanat@bar.org> instead of C<mkanat@foo.com>.
|
||||
|
||||
=item C<user>
|
||||
|
||||
A L<Bugzilla::User> object representing the authenticated user.
|
||||
Note that C<Bugzilla::Auth::login> may modify this object at various points.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login;
|
||||
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
# Determines whether or not a user can logout. It's really a subroutine,
|
||||
# but we implement it here as a constant. Override it in subclasses if
|
||||
# that particular type of login method cannot log out.
|
||||
use constant can_logout => 1;
|
||||
use constant can_login => 1;
|
||||
use constant requires_persistence => 1;
|
||||
use constant requires_verification => 1;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub new {
|
||||
my ($class) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Login - Gets username/password data from the user.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Auth::Login is used to get information that uniquely identifies
|
||||
a user and allows us to authorize their Bugzilla access.
|
||||
|
||||
It is mostly an abstract class, requiring subclasses to implement
|
||||
most methods.
|
||||
|
||||
Note that callers outside of the C<Bugzilla::Auth> package should never
|
||||
create this object directly. Just create a C<Bugzilla::Auth> object
|
||||
and call C<login> on it.
|
||||
|
||||
=head1 LOGIN METHODS
|
||||
|
||||
These are methods that have to do with getting the actual login data
|
||||
from the user or handling a login somehow.
|
||||
|
||||
These methods are abstract -- they MUST be implemented by a subclass.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<get_login_info()>
|
||||
|
||||
Description: Gets a username/password from the user, or some other
|
||||
information that uniquely identifies them.
|
||||
Params: None
|
||||
Returns: A C<$login_data> hashref. (See L<Bugzilla::Auth> for details.)
|
||||
The hashref MUST contain: C<user_id> *or* C<username>
|
||||
If this is a login method that requires verification,
|
||||
the hashref MUST contain C<password>.
|
||||
The hashref MAY contain C<realname> and C<extern_id>.
|
||||
|
||||
=item C<fail_nodata()>
|
||||
|
||||
Description: This function is called when Bugzilla doesn't get
|
||||
a username/password and the login type is C<LOGIN_REQUIRED>
|
||||
(See L<Bugzilla::Auth> for a description of C<LOGIN_REQUIRED>).
|
||||
That is, this handles C<AUTH_NODATA> in that situation.
|
||||
|
||||
This function MUST stop CGI execution when it is complete.
|
||||
That is, it must call C<exit> or C<ThrowUserError> or some
|
||||
such thing.
|
||||
Params: None
|
||||
Returns: Never Returns.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INFO METHODS
|
||||
|
||||
These are methods that describe the capabilities of this
|
||||
C<Bugzilla::Auth::Login> object. These are all no-parameter
|
||||
methods that return either C<true> or C<false>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<can_logout>
|
||||
|
||||
Whether or not users can log out if they logged in using this
|
||||
object. Defaults to C<true>.
|
||||
|
||||
=item C<can_login>
|
||||
|
||||
Whether or not users can log in through the web interface using
|
||||
this object. Defaults to C<true>.
|
||||
|
||||
=item C<requires_persistence>
|
||||
|
||||
Whether or not we should send the user a cookie if they logged in with
|
||||
this method. Defaults to C<true>.
|
||||
|
||||
=item C<requires_verification>
|
||||
|
||||
Whether or not we should check the username/password that we
|
||||
got from this login method. Defaults to C<true>.
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Whether or not users can create accounts, if this login method is
|
||||
currently being used by the system. Defaults to C<false>.
|
||||
|
||||
=back
|
||||
@@ -1,86 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::CGI;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
use constant user_can_create_account => 1;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my $username = trim($cgi->param("Bugzilla_login"));
|
||||
my $password = $cgi->param("Bugzilla_password");
|
||||
|
||||
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
|
||||
|
||||
if (!defined $username || !defined $password) {
|
||||
return { failure => AUTH_NODATA };
|
||||
}
|
||||
|
||||
return { username => $username, password => $password };
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
if (Bugzilla->error_mode == Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT) {
|
||||
die SOAP::Fault
|
||||
->faultcode(ERROR_AUTH_NODATA)
|
||||
->faultstring('Login Required');
|
||||
}
|
||||
|
||||
# If system is not configured to never require SSL connections
|
||||
# we want to always redirect to SSL since passing usernames and
|
||||
# passwords over an unprotected connection is a bad idea. If we
|
||||
# get here then a login form will be provided to the user so we
|
||||
# want this to be protected if possible.
|
||||
if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
|
||||
&& Bugzilla->params->{'ssl'} ne 'never')
|
||||
{
|
||||
$cgi->require_https(Bugzilla->params->{'sslbase'});
|
||||
}
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("account/auth/login.html.tmpl",
|
||||
{ 'target' => $cgi->url(-relative=>1) })
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,96 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Cookie;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
use constant requires_persistence => 0;
|
||||
use constant requires_verification => 0;
|
||||
use constant can_login => 0;
|
||||
|
||||
# Note that Cookie never consults the Verifier, it always assumes
|
||||
# it has a valid DB account or it fails.
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ip_addr = $cgi->remote_addr();
|
||||
my $net_addr = get_netaddr($ip_addr);
|
||||
my $login_cookie = $cgi->cookie("Bugzilla_logincookie");
|
||||
my $user_id = $cgi->cookie("Bugzilla_login");
|
||||
|
||||
# If cookies cannot be found, this could mean that they haven't
|
||||
# been made available yet. In this case, look at Bugzilla_cookie_list.
|
||||
unless ($login_cookie) {
|
||||
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
$login_cookie = $cookie->value if $cookie;
|
||||
}
|
||||
unless ($user_id) {
|
||||
my $cookie = first {$_->name eq 'Bugzilla_login'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
$user_id = $cookie->value if $cookie;
|
||||
}
|
||||
|
||||
if ($login_cookie && $user_id) {
|
||||
# Anything goes for these params - they're just strings which
|
||||
# we're going to verify against the db
|
||||
trick_taint($ip_addr);
|
||||
trick_taint($login_cookie);
|
||||
detaint_natural($user_id);
|
||||
|
||||
my $query = "SELECT userid
|
||||
FROM logincookies
|
||||
WHERE logincookies.cookie = ?
|
||||
AND logincookies.userid = ?
|
||||
AND (logincookies.ipaddr = ?";
|
||||
|
||||
# If we have a network block that's allowed to use this cookie,
|
||||
# as opposed to just a single IP.
|
||||
my @params = ($login_cookie, $user_id, $ip_addr);
|
||||
if (defined $net_addr) {
|
||||
trick_taint($net_addr);
|
||||
$query .= " OR logincookies.ipaddr = ?";
|
||||
push(@params, $net_addr);
|
||||
}
|
||||
$query .= ")";
|
||||
|
||||
# If the cookie is valid, return a valid username.
|
||||
if ($dbh->selectrow_array($query, undef, @params)) {
|
||||
# If we logged in successfully, then update the lastused
|
||||
# time on the login cookie
|
||||
$dbh->do("UPDATE logincookies SET lastused = NOW()
|
||||
WHERE cookie = ?", undef, $login_cookie);
|
||||
return { user_id => $user_id };
|
||||
}
|
||||
}
|
||||
|
||||
# Either the he cookie is invalid, or we got no cookie. We don't want
|
||||
# to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
|
||||
# actually throw an error when it gets a bad cookie. It should just
|
||||
# look like there was no cookie to begin with.
|
||||
return { failure => AUTH_NODATA };
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Env;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use constant can_logout => 0;
|
||||
use constant can_login => 0;
|
||||
use constant requires_verification => 0;
|
||||
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
|
||||
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
|
||||
my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
|
||||
|
||||
return { failure => AUTH_NODATA } if !$env_email;
|
||||
|
||||
return { username => $env_email, extern_id => $env_id,
|
||||
realname => $env_realname };
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
ThrowCodeError('env_no_email');
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Stack;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
use fields qw(
|
||||
_stack
|
||||
successful
|
||||
);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
my $list = shift;
|
||||
$self->{_stack} = [];
|
||||
foreach my $login_method (split(',', $list)) {
|
||||
require "Bugzilla/Auth/Login/${login_method}.pm";
|
||||
push(@{$self->{_stack}},
|
||||
"Bugzilla::Auth::Login::$login_method"->new(@_));
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_login_info {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->get_login_info(@_);
|
||||
$self->{successful} = $object;
|
||||
last if !$result->{failure};
|
||||
# So that if none of them succeed, it's undef.
|
||||
$self->{successful} = undef;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
my $self = shift;
|
||||
# We fail from the bottom of the stack.
|
||||
my @reverse_stack = reverse @{$self->{_stack}};
|
||||
foreach my $object (@reverse_stack) {
|
||||
# We pick the first object that actually has the method
|
||||
# implemented.
|
||||
if ($object->can('fail_nodata')) {
|
||||
$object->fail_nodata(@_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub can_login {
|
||||
my ($self) = @_;
|
||||
# We return true if any method can log in.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->can_login;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
# We return true if any method allows users to create accounts.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->user_can_create_account;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,157 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Persist::Cookie;
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Token;
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
sub new {
|
||||
my ($class) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub persist_login {
|
||||
my ($self, $user) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my $ip_addr = $cgi->remote_addr;
|
||||
unless ($cgi->param('Bugzilla_restrictlogin') ||
|
||||
Bugzilla->params->{'loginnetmask'} == 32)
|
||||
{
|
||||
$ip_addr = get_netaddr($ip_addr);
|
||||
}
|
||||
|
||||
# The IP address is valid, at least for comparing with itself in a
|
||||
# subsequent login
|
||||
trick_taint($ip_addr);
|
||||
|
||||
my $login_cookie =
|
||||
Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
|
||||
|
||||
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
|
||||
VALUES (?, ?, ?, NOW())",
|
||||
undef, $login_cookie, $user->id, $ip_addr);
|
||||
|
||||
# Prevent JavaScript from accessing login cookies.
|
||||
my %cookieargs = ('-httponly' => 1);
|
||||
|
||||
# Remember cookie only if admin has told so
|
||||
# or admin didn't forbid it and user told to remember.
|
||||
if ( Bugzilla->params->{'rememberlogin'} eq 'on' ||
|
||||
(Bugzilla->params->{'rememberlogin'} ne 'off' &&
|
||||
$cgi->param('Bugzilla_remember') &&
|
||||
$cgi->param('Bugzilla_remember') eq 'on') )
|
||||
{
|
||||
# Not a session cookie, so set an infinite expiry
|
||||
$cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT';
|
||||
}
|
||||
if (Bugzilla->params->{'ssl'} ne 'never'
|
||||
&& Bugzilla->params->{'sslbase'} ne '')
|
||||
{
|
||||
# Bugzilla->login will automatically redirect to https://,
|
||||
# so it's safe to turn on the 'secure' bit.
|
||||
$cookieargs{'-secure'} = 1;
|
||||
}
|
||||
|
||||
$cgi->send_cookie(-name => 'Bugzilla_login',
|
||||
-value => $user->id,
|
||||
%cookieargs);
|
||||
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
|
||||
-value => $login_cookie,
|
||||
%cookieargs);
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my ($self, $param) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$param = {} unless $param;
|
||||
my $user = $param->{user} || Bugzilla->user;
|
||||
my $type = $param->{type} || LOGOUT_ALL;
|
||||
|
||||
if ($type == LOGOUT_ALL) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE userid = ?",
|
||||
undef, $user->id);
|
||||
return;
|
||||
}
|
||||
|
||||
# The LOGOUT_*_CURRENT options require the current login cookie.
|
||||
# If a new cookie has been issued during this run, that's the current one.
|
||||
# If not, it's the one we've received.
|
||||
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
my $login_cookie;
|
||||
if ($cookie) {
|
||||
$login_cookie = $cookie->value;
|
||||
}
|
||||
else {
|
||||
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
|
||||
}
|
||||
trick_taint($login_cookie);
|
||||
|
||||
# These queries use both the cookie ID and the user ID as keys. Even
|
||||
# though we know the userid must match, we still check it in the SQL
|
||||
# as a sanity check, since there is no locking here, and if the user
|
||||
# logged out from two machines simultaneously, while someone else
|
||||
# logged in and got the same cookie, we could be logging the other
|
||||
# user out here. Yes, this is very very very unlikely, but why take
|
||||
# chances? - bbaetz
|
||||
if ($type == LOGOUT_KEEP_CURRENT) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
|
||||
undef, $login_cookie, $user->id);
|
||||
} elsif ($type == LOGOUT_CURRENT) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
|
||||
undef, $login_cookie, $user->id);
|
||||
} else {
|
||||
die("Invalid type $type supplied to logout()");
|
||||
}
|
||||
|
||||
if ($type != LOGOUT_KEEP_CURRENT) {
|
||||
clear_browser_cookies();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub clear_browser_cookies {
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->remove_cookie('Bugzilla_login');
|
||||
$cgi->remove_cookie('Bugzilla_logincookie');
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,235 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify;
|
||||
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use constant user_can_create_account => 1;
|
||||
|
||||
sub new {
|
||||
my ($class, $login_type) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
return $_[0]->can('change_password');
|
||||
}
|
||||
|
||||
sub create_or_update_user {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $extern_id = $params->{extern_id};
|
||||
my $username = $params->{bz_username} || $params->{username};
|
||||
my $password = $params->{password} || '*';
|
||||
my $real_name = $params->{realname} || '';
|
||||
my $user_id = $params->{user_id};
|
||||
|
||||
# A passed-in user_id always overrides anything else, for determining
|
||||
# what account we should return.
|
||||
if (!$user_id) {
|
||||
my $username_user_id = login_to_id($username || '');
|
||||
my $extern_user_id;
|
||||
if ($extern_id) {
|
||||
trick_taint($extern_id);
|
||||
$extern_user_id = $dbh->selectrow_array('SELECT userid
|
||||
FROM profiles WHERE extern_id = ?', undef, $extern_id);
|
||||
}
|
||||
|
||||
# If we have both a valid extern_id and a valid username, and they are
|
||||
# not the same id, then we have a conflict.
|
||||
if ($username_user_id && $extern_user_id
|
||||
&& $username_user_id ne $extern_user_id)
|
||||
{
|
||||
my $extern_name = Bugzilla::User->new($extern_user_id)->login;
|
||||
return { failure => AUTH_ERROR, error => "extern_id_conflict",
|
||||
details => {extern_id => $extern_id,
|
||||
extern_user => $extern_name,
|
||||
username => $username} };
|
||||
}
|
||||
|
||||
# If we have a valid username, but no valid id,
|
||||
# then we have to create the user. This happens when we're
|
||||
# passed only a username, and that username doesn't exist already.
|
||||
if ($username && !$username_user_id && !$extern_user_id) {
|
||||
validate_email_syntax($username)
|
||||
|| return { failure => AUTH_ERROR,
|
||||
error => 'auth_invalid_email',
|
||||
details => {addr => $username} };
|
||||
# Usually we'd call validate_password, but external authentication
|
||||
# systems might follow different standards than ours. So in this
|
||||
# place here, we call trick_taint without checks.
|
||||
trick_taint($password);
|
||||
|
||||
# XXX Theoretically this could fail with an error, but the fix for
|
||||
# that is too involved to be done right now.
|
||||
my $user = Bugzilla::User->create({
|
||||
login_name => $username,
|
||||
cryptpassword => $password,
|
||||
realname => $real_name});
|
||||
$username_user_id = $user->id;
|
||||
}
|
||||
|
||||
# If we have a valid username id and an extern_id, but no valid
|
||||
# extern_user_id, then we have to set the user's extern_id.
|
||||
if ($extern_id && $username_user_id && !$extern_user_id) {
|
||||
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
|
||||
undef, $extern_id, $username_user_id);
|
||||
}
|
||||
|
||||
# Finally, at this point, one of these will give us a valid user id.
|
||||
$user_id = $extern_user_id || $username_user_id;
|
||||
}
|
||||
|
||||
# If we still don't have a valid user_id, then we weren't passed
|
||||
# enough information in $params, and we should die right here.
|
||||
ThrowCodeError('bad_arg', {argument => 'params', function =>
|
||||
'Bugzilla::Auth::Verify::create_or_update_user'})
|
||||
unless $user_id;
|
||||
|
||||
my $user = new Bugzilla::User($user_id);
|
||||
|
||||
# Now that we have a valid User, we need to see if any data has to be
|
||||
# updated.
|
||||
if ($username && lc($user->login) ne lc($username)) {
|
||||
validate_email_syntax($username)
|
||||
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email',
|
||||
details => {addr => $username} };
|
||||
$user->set_login($username);
|
||||
}
|
||||
if ($real_name && $user->name ne $real_name) {
|
||||
# $real_name is more than likely tainted, but we only use it
|
||||
# in a placeholder and we never use it after this.
|
||||
trick_taint($real_name);
|
||||
$user->set_name($real_name);
|
||||
}
|
||||
$user->update();
|
||||
|
||||
return { user => $user };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Verify - An object that verifies usernames and passwords.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Auth::Verify provides the "Verifier" part of the Bugzilla
|
||||
login process. (For details, see the "STRUCTURE" section of
|
||||
L<Bugzilla::Auth>.)
|
||||
|
||||
It is mostly an abstract class, requiring subclasses to implement
|
||||
most methods.
|
||||
|
||||
Note that callers outside of the C<Bugzilla::Auth> package should never
|
||||
create this object directly. Just create a C<Bugzilla::Auth> object
|
||||
and call C<login> on it.
|
||||
|
||||
=head1 VERIFICATION METHODS
|
||||
|
||||
These are the methods that have to do with the actual verification.
|
||||
|
||||
Subclasses MUST implement these methods.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<check_credentials($login_data)>
|
||||
|
||||
Description: Checks whether or not a username is valid.
|
||||
Params: $login_data - A C<$login_data> hashref, as described in
|
||||
L<Bugzilla::Auth>.
|
||||
This C<$login_data> hashref MUST contain
|
||||
C<username>, and SHOULD also contain
|
||||
C<password>.
|
||||
Returns: A C<$login_data> hashref with C<bz_username> set. This
|
||||
method may also set C<realname>. It must avoid changing
|
||||
anything that is already set.
|
||||
|
||||
=back
|
||||
|
||||
=head1 MODIFICATION METHODS
|
||||
|
||||
These are methods that change data in the actual authentication backend.
|
||||
|
||||
These methods are optional, they do not have to be implemented by
|
||||
subclasses.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<create_or_update_user($login_data)>
|
||||
|
||||
Description: Automatically creates a user account in the database
|
||||
if it doesn't already exist, or updates the account
|
||||
data if C<$login_data> contains newer information.
|
||||
|
||||
Params: $login_data - A C<$login_data> hashref, as described in
|
||||
L<Bugzilla::Auth>.
|
||||
This C<$login_data> hashref MUST contain
|
||||
either C<user_id>, C<bz_username>, or
|
||||
C<username>. If both C<username> and C<bz_username>
|
||||
are specified, C<bz_username> is used as the
|
||||
login name of the user to create in the database.
|
||||
It MAY also contain C<extern_id>, in which
|
||||
case it still MUST contain C<bz_username> or
|
||||
C<username>.
|
||||
It MAY contain C<password> and C<realname>.
|
||||
|
||||
Returns: A hashref with one element, C<user>, which is a
|
||||
L<Bugzilla::User> object. May also return a login error
|
||||
as described in L<Bugzilla::Auth>.
|
||||
|
||||
Note: This method is not abstract, it is actually implemented
|
||||
and creates accounts in the Bugzilla database. Subclasses
|
||||
should probably all call the C<Bugzilla::Auth::Verify>
|
||||
version of this function at the end of their own
|
||||
C<create_or_update_user>.
|
||||
|
||||
=item C<change_password($user, $password)>
|
||||
|
||||
Description: Modifies the user's password in the authentication backend.
|
||||
Params: $user - A L<Bugzilla::User> object representing the user
|
||||
whose password we want to change.
|
||||
$password - The user's new password.
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INFO METHODS
|
||||
|
||||
These are methods that describe the capabilities of this object.
|
||||
These are all no-parameter methods that return either C<true> or
|
||||
C<false>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Whether or not users can manually create accounts in this type of
|
||||
account source. Defaults to C<true>.
|
||||
|
||||
=back
|
||||
@@ -1,83 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth::Verify::DB;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Token;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $login_data) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $username = $login_data->{username};
|
||||
my $user_id = login_to_id($username);
|
||||
|
||||
return { failure => AUTH_NO_SUCH_USER } unless $user_id;
|
||||
|
||||
$login_data->{bz_username} = $username;
|
||||
my $password = $login_data->{password};
|
||||
|
||||
trick_taint($username);
|
||||
my ($real_password_crypted) = $dbh->selectrow_array(
|
||||
"SELECT cryptpassword FROM profiles WHERE userid = ?",
|
||||
undef, $user_id);
|
||||
|
||||
# Wide characters cause crypt to die
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::encode($password) if utf8::is_utf8($password);
|
||||
}
|
||||
|
||||
# Using the internal crypted password as the salt,
|
||||
# crypt the password the user entered.
|
||||
my $entered_password_crypted = crypt($password, $real_password_crypted);
|
||||
|
||||
return { failure => AUTH_LOGINFAILED }
|
||||
if $entered_password_crypted ne $real_password_crypted;
|
||||
|
||||
# The user's credentials are okay, so delete any outstanding
|
||||
# password tokens they may have generated.
|
||||
Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in");
|
||||
|
||||
return $login_data;
|
||||
}
|
||||
|
||||
sub change_password {
|
||||
my ($self, $user, $password) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cryptpassword = bz_crypt($password);
|
||||
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
|
||||
undef, $cryptpassword, $user->id);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,176 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify::LDAP;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
use fields qw(
|
||||
ldap
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Net::LDAP;
|
||||
|
||||
use constant admin_can_create_account => 0;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# We need to bind anonymously to the LDAP server. This is
|
||||
# because we need to get the Distinguished Name of the user trying
|
||||
# to log in. Some servers (such as iPlanet) allow you to have unique
|
||||
# uids spread out over a subtree of an area (such as "People"), so
|
||||
# just appending the Base DN to the uid isn't sufficient to get the
|
||||
# user's DN. For servers which don't work this way, there will still
|
||||
# be no harm done.
|
||||
$self->_bind_ldap_anonymously();
|
||||
|
||||
# Now, we verify that the user exists, and get a LDAP Distinguished
|
||||
# Name for the user.
|
||||
my $username = $params->{username};
|
||||
my $dn_result = $self->ldap->search(_bz_search_params($username),
|
||||
attrs => ['dn']);
|
||||
return { failure => AUTH_ERROR, error => "ldap_search_error",
|
||||
details => {errstr => $dn_result->error, username => $username}
|
||||
} if $dn_result->code;
|
||||
|
||||
return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count;
|
||||
|
||||
my $dn = $dn_result->shift_entry->dn;
|
||||
|
||||
# Check the password.
|
||||
my $pw_result = $self->ldap->bind($dn, password => $params->{password});
|
||||
return { failure => AUTH_LOGINFAILED } if $pw_result->code;
|
||||
|
||||
# And now we fill in the user's details.
|
||||
my $detail_result = $self->ldap->search(_bz_search_params($username));
|
||||
return { failure => AUTH_ERROR, error => "ldap_search_error",
|
||||
details => {errstr => $detail_result->error, username => $username}
|
||||
} if $detail_result->code;
|
||||
|
||||
my $user_entry = $detail_result->shift_entry;
|
||||
|
||||
my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
|
||||
if ($mail_attr) {
|
||||
if (!$user_entry->exists($mail_attr)) {
|
||||
return { failure => AUTH_ERROR,
|
||||
error => "ldap_cannot_retreive_attr",
|
||||
details => {attr => $mail_attr} };
|
||||
}
|
||||
|
||||
my @emails = $user_entry->get_value($mail_attr);
|
||||
|
||||
# Default to the first email address returned.
|
||||
$params->{bz_username} = $emails[0];
|
||||
|
||||
if (@emails > 1) {
|
||||
# Cycle through the adresses and check if they're Bugzilla logins.
|
||||
# Use the first one that returns a valid id.
|
||||
foreach my $email (@emails) {
|
||||
if ( login_to_id($email) ) {
|
||||
$params->{bz_username} = $email;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$params->{bz_username} = $username;
|
||||
}
|
||||
|
||||
$params->{realname} ||= $user_entry->get_value("displayName");
|
||||
$params->{realname} ||= $user_entry->get_value("cn");
|
||||
|
||||
$params->{extern_id} = $username;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub _bz_search_params {
|
||||
my ($username) = @_;
|
||||
return (base => Bugzilla->params->{"LDAPBaseDN"},
|
||||
scope => "sub",
|
||||
filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
|
||||
. "=$username)"
|
||||
. Bugzilla->params->{"LDAPfilter"} . ')');
|
||||
}
|
||||
|
||||
sub _bind_ldap_anonymously {
|
||||
my ($self) = @_;
|
||||
my $bind_result;
|
||||
if (Bugzilla->params->{"LDAPbinddn"}) {
|
||||
my ($LDAPbinddn,$LDAPbindpass) =
|
||||
split(":",Bugzilla->params->{"LDAPbinddn"});
|
||||
$bind_result =
|
||||
$self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
|
||||
}
|
||||
else {
|
||||
$bind_result = $self->ldap->bind();
|
||||
}
|
||||
ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
|
||||
if $bind_result->code;
|
||||
}
|
||||
|
||||
# We can't just do this in new(), because we're not allowed to throw any
|
||||
# error from anywhere under Bugzilla::Auth::new -- otherwise we
|
||||
# could create a situation where the admin couldn't get to editparams
|
||||
# to fix his mistake. (Because Bugzilla->login always calls
|
||||
# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
|
||||
sub ldap {
|
||||
my ($self) = @_;
|
||||
return $self->{ldap} if $self->{ldap};
|
||||
|
||||
my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
|
||||
ThrowCodeError("ldap_server_not_defined") unless @servers;
|
||||
|
||||
foreach (@servers) {
|
||||
$self->{ldap} = new Net::LDAP(trim($_));
|
||||
last if $self->{ldap};
|
||||
}
|
||||
ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) })
|
||||
unless $self->{ldap};
|
||||
|
||||
# try to start TLS if needed
|
||||
if (Bugzilla->params->{"LDAPstarttls"}) {
|
||||
my $mesg = $self->{ldap}->start_tls();
|
||||
ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() })
|
||||
if $mesg->code();
|
||||
}
|
||||
|
||||
return $self->{ldap};
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Auth::Verify::RADIUS;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Authen::Radius;
|
||||
|
||||
use constant admin_can_create_account => 0;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
|
||||
my $username = $params->{username};
|
||||
|
||||
# If we're using RADIUS_email_suffix, we may need to cut it off from
|
||||
# the login name.
|
||||
if ($address_suffix) {
|
||||
$username =~ s/\Q$address_suffix\E$//i;
|
||||
}
|
||||
|
||||
# Create RADIUS object.
|
||||
my $radius =
|
||||
new Authen::Radius(Host => Bugzilla->params->{'RADIUS_server'},
|
||||
Secret => Bugzilla->params->{'RADIUS_secret'})
|
||||
|| return { failure => AUTH_ERROR, error => 'radius_preparation_error',
|
||||
details => {errstr => Authen::Radius::strerror() } };
|
||||
|
||||
# Check the password.
|
||||
$radius->check_pwd($username, $params->{password},
|
||||
Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
|
||||
|| return { failure => AUTH_LOGINFAILED };
|
||||
|
||||
# Build the user account's e-mail address.
|
||||
$params->{bz_username} = $username . $address_suffix;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify::Stack;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
use fields qw(
|
||||
_stack
|
||||
successful
|
||||
);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $list = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
$self->{_stack} = [];
|
||||
foreach my $verify_method (split(',', $list)) {
|
||||
require "Bugzilla/Auth/Verify/${verify_method}.pm";
|
||||
push(@{$self->{_stack}},
|
||||
"Bugzilla::Auth::Verify::$verify_method"->new(@_));
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
my ($self) = @_;
|
||||
# We return true if any method can change passwords.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->can_change_password;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub check_credentials {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->check_credentials(@_);
|
||||
$self->{successful} = $object;
|
||||
last if !$result->{failure};
|
||||
# So that if none of them succeed, it's undef.
|
||||
$self->{successful} = undef;
|
||||
}
|
||||
# Returns the result at the bottom of the stack if they all fail.
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub create_or_update_user {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->create_or_update_user(@_);
|
||||
last if !$result->{failure};
|
||||
}
|
||||
# Returns the result at the bottom of the stack if they all fail.
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
# We return true if any method allows the user to create an account.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->user_can_create_account;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,733 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>,
|
||||
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Alan Raetz <al_raetz@yahoo.com>
|
||||
# Jacob Steenhagen <jake@actex.net>
|
||||
# Matthew Tuck <matty@chariot.net.au>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::BugMail;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::Classification;
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::Component;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Mailer;
|
||||
|
||||
use Date::Parse;
|
||||
use Date::Format;
|
||||
|
||||
use constant FORMAT_TRIPLE => "%19s|%-28s|%-28s";
|
||||
use constant FORMAT_3_SIZE => [19,28,28];
|
||||
use constant FORMAT_DOUBLE => "%19s %-55s";
|
||||
use constant FORMAT_2_SIZE => [19,55];
|
||||
|
||||
use constant BIT_DIRECT => 1;
|
||||
use constant BIT_WATCHING => 2;
|
||||
|
||||
# We need these strings for the X-Bugzilla-Reasons header
|
||||
# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
|
||||
use constant REL_NAMES => {
|
||||
REL_ASSIGNEE , "AssignedTo",
|
||||
REL_REPORTER , "Reporter",
|
||||
REL_QA , "QAcontact",
|
||||
REL_CC , "CC",
|
||||
REL_VOTER , "Voter",
|
||||
REL_GLOBAL_WATCHER, "GlobalWatcher"
|
||||
};
|
||||
|
||||
# We use this instead of format because format doesn't deal well with
|
||||
# multi-byte languages.
|
||||
sub multiline_sprintf {
|
||||
my ($format, $args, $sizes) = @_;
|
||||
my @parts;
|
||||
my @my_sizes = @$sizes; # Copy this so we don't modify the input array.
|
||||
foreach my $string (@$args) {
|
||||
my $size = shift @my_sizes;
|
||||
my @pieces = split("\n", wrap_hard($string, $size));
|
||||
push(@parts, \@pieces);
|
||||
}
|
||||
|
||||
my $formatted;
|
||||
while (1) {
|
||||
# Get the first item of each part.
|
||||
my @line = map { shift @$_ } @parts;
|
||||
# If they're all undef, we're done.
|
||||
last if !grep { defined $_ } @line;
|
||||
# Make any single undef item into ''
|
||||
@line = map { defined $_ ? $_ : '' } @line;
|
||||
# And append a formatted line
|
||||
$formatted .= sprintf($format, @line);
|
||||
# Remove trailing spaces, or they become lots of =20's in
|
||||
# quoted-printable emails.
|
||||
$formatted =~ s/\s+$//;
|
||||
$formatted .= "\n";
|
||||
}
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
sub three_columns {
|
||||
return multiline_sprintf(FORMAT_TRIPLE, \@_, FORMAT_3_SIZE);
|
||||
}
|
||||
|
||||
# This is a bit of a hack, basically keeping the old system()
|
||||
# cmd line interface. Should clean this up at some point.
|
||||
#
|
||||
# args: bug_id, and an optional hash ref which may have keys for:
|
||||
# changer, owner, qa, reporter, cc
|
||||
# Optional hash contains values of people which will be forced to those
|
||||
# roles when the email is sent.
|
||||
# All the names are email addresses, not userids
|
||||
# values are scalars, except for cc, which is a list
|
||||
# This hash usually comes from the "mailrecipients" var in a template call.
|
||||
sub Send {
|
||||
my ($id, $forced) = (@_);
|
||||
|
||||
my @headerlist;
|
||||
my %defmailhead;
|
||||
my %fielddescription;
|
||||
|
||||
my $msg = "";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# XXX - These variables below are useless. We could use field object
|
||||
# methods directly. But we first have to implement a cache in
|
||||
# Bugzilla->get_fields to avoid querying the DB all the time.
|
||||
foreach my $field (Bugzilla->get_fields({obsolete => 0})) {
|
||||
push(@headerlist, $field->name);
|
||||
$defmailhead{$field->name} = $field->in_new_bugmail;
|
||||
$fielddescription{$field->name} = $field->description;
|
||||
}
|
||||
|
||||
my %values = %{$dbh->selectrow_hashref(
|
||||
'SELECT ' . join(',', editable_bug_fields()) . ', reporter,
|
||||
lastdiffed AS start_time, LOCALTIMESTAMP(0) AS end_time
|
||||
FROM bugs WHERE bug_id = ?',
|
||||
undef, $id)};
|
||||
|
||||
my $product = new Bugzilla::Product($values{product_id});
|
||||
$values{product} = $product->name;
|
||||
if (Bugzilla->params->{'useclassification'}) {
|
||||
$values{classification} = Bugzilla::Classification->new($product->classification_id)->name;
|
||||
}
|
||||
my $component = new Bugzilla::Component($values{component_id});
|
||||
$values{component} = $component->name;
|
||||
|
||||
my ($start, $end) = ($values{start_time}, $values{end_time});
|
||||
|
||||
# User IDs of people in various roles. More than one person can 'have' a
|
||||
# role, if the person in that role has changed, or people are watching.
|
||||
my $reporter = $values{'reporter'};
|
||||
my @assignees = ($values{'assigned_to'});
|
||||
my @qa_contacts = ($values{'qa_contact'});
|
||||
|
||||
my $cc_users = $dbh->selectall_arrayref(
|
||||
"SELECT cc.who, profiles.login_name
|
||||
FROM cc
|
||||
INNER JOIN profiles
|
||||
ON cc.who = profiles.userid
|
||||
WHERE bug_id = ?",
|
||||
undef, $id);
|
||||
|
||||
my (@ccs, @cc_login_names);
|
||||
foreach my $cc_user (@$cc_users) {
|
||||
my ($user_id, $user_login) = @$cc_user;
|
||||
push (@ccs, $user_id);
|
||||
push (@cc_login_names, $user_login);
|
||||
}
|
||||
|
||||
# Include the people passed in as being in particular roles.
|
||||
# This can include people who used to hold those roles.
|
||||
# At this point, we don't care if there are duplicates in these arrays.
|
||||
my $changer = $forced->{'changer'};
|
||||
if ($forced->{'owner'}) {
|
||||
push (@assignees, login_to_id($forced->{'owner'}, THROW_ERROR));
|
||||
}
|
||||
|
||||
if ($forced->{'qacontact'}) {
|
||||
push (@qa_contacts, login_to_id($forced->{'qacontact'}, THROW_ERROR));
|
||||
}
|
||||
|
||||
if ($forced->{'cc'}) {
|
||||
foreach my $cc (@{$forced->{'cc'}}) {
|
||||
push(@ccs, login_to_id($cc, THROW_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
# Convert to names, for later display
|
||||
$values{'changer'} = $changer;
|
||||
# If no changer is specified, then it has no name.
|
||||
if ($changer) {
|
||||
$values{'changername'} = Bugzilla::User->new({name => $changer})->name;
|
||||
}
|
||||
$values{'assigned_to'} = user_id_to_login($values{'assigned_to'});
|
||||
$values{'reporter'} = user_id_to_login($values{'reporter'});
|
||||
if ($values{'qa_contact'}) {
|
||||
$values{'qa_contact'} = user_id_to_login($values{'qa_contact'});
|
||||
}
|
||||
$values{'cc'} = join(', ', @cc_login_names);
|
||||
$values{'estimated_time'} = format_time_decimal($values{'estimated_time'});
|
||||
|
||||
if ($values{'deadline'}) {
|
||||
$values{'deadline'} = time2str("%Y-%m-%d", str2time($values{'deadline'}));
|
||||
}
|
||||
|
||||
my $dependslist = $dbh->selectcol_arrayref(
|
||||
'SELECT dependson FROM dependencies
|
||||
WHERE blocked = ? ORDER BY dependson',
|
||||
undef, ($id));
|
||||
|
||||
$values{'dependson'} = join(",", @$dependslist);
|
||||
|
||||
my $blockedlist = $dbh->selectcol_arrayref(
|
||||
'SELECT blocked FROM dependencies
|
||||
WHERE dependson = ? ORDER BY blocked',
|
||||
undef, ($id));
|
||||
|
||||
$values{'blocked'} = join(",", @$blockedlist);
|
||||
|
||||
my @args = ($id);
|
||||
|
||||
# If lastdiffed is NULL, then we don't limit the search on time.
|
||||
my $when_restriction = '';
|
||||
if ($start) {
|
||||
$when_restriction = ' AND bug_when > ? AND bug_when <= ?';
|
||||
push @args, ($start, $end);
|
||||
}
|
||||
|
||||
my $diffs = $dbh->selectall_arrayref(
|
||||
"SELECT profiles.login_name, profiles.realname, fielddefs.description,
|
||||
bugs_activity.bug_when, bugs_activity.removed,
|
||||
bugs_activity.added, bugs_activity.attach_id, fielddefs.name
|
||||
FROM bugs_activity
|
||||
INNER JOIN fielddefs
|
||||
ON fielddefs.id = bugs_activity.fieldid
|
||||
INNER JOIN profiles
|
||||
ON profiles.userid = bugs_activity.who
|
||||
WHERE bugs_activity.bug_id = ?
|
||||
$when_restriction
|
||||
ORDER BY bugs_activity.bug_when", undef, @args);
|
||||
|
||||
my @new_depbugs;
|
||||
my $difftext = "";
|
||||
my $diffheader = "";
|
||||
my @diffparts;
|
||||
my $lastwho = "";
|
||||
my $fullwho;
|
||||
my @changedfields;
|
||||
foreach my $ref (@$diffs) {
|
||||
my ($who, $whoname, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref);
|
||||
my $diffpart = {};
|
||||
if ($who ne $lastwho) {
|
||||
$lastwho = $who;
|
||||
$fullwho = $whoname ? "$whoname <$who>" : $who;
|
||||
$diffheader = "\n$fullwho changed:\n\n";
|
||||
$diffheader .= three_columns("What ", "Removed", "Added");
|
||||
$diffheader .= ('-' x 76) . "\n";
|
||||
}
|
||||
$what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid;
|
||||
if( $fieldname eq 'estimated_time' ||
|
||||
$fieldname eq 'remaining_time' ) {
|
||||
$old = format_time_decimal($old);
|
||||
$new = format_time_decimal($new);
|
||||
}
|
||||
if ($fieldname eq 'dependson') {
|
||||
push(@new_depbugs, grep {$_ =~ /^\d+$/} split(/[\s,]+/, $new));
|
||||
}
|
||||
if ($attachid) {
|
||||
($diffpart->{'isprivate'}) = $dbh->selectrow_array(
|
||||
'SELECT isprivate FROM attachments WHERE attach_id = ?',
|
||||
undef, ($attachid));
|
||||
}
|
||||
$difftext = three_columns($what, $old, $new);
|
||||
$diffpart->{'header'} = $diffheader;
|
||||
$diffpart->{'fieldname'} = $fieldname;
|
||||
$diffpart->{'text'} = $difftext;
|
||||
push(@diffparts, $diffpart);
|
||||
push(@changedfields, $what);
|
||||
}
|
||||
$values{'changed_fields'} = join(' ', @changedfields);
|
||||
|
||||
my @depbugs;
|
||||
my $deptext = "";
|
||||
# Do not include data about dependent bugs when they have just been added.
|
||||
# Completely skip checking for dependent bugs on bug creation as all
|
||||
# dependencies bugs will just have been added.
|
||||
if ($start) {
|
||||
my $dep_restriction = "";
|
||||
if (scalar @new_depbugs) {
|
||||
$dep_restriction = "AND bugs_activity.bug_id NOT IN (" .
|
||||
join(", ", @new_depbugs) . ")";
|
||||
}
|
||||
|
||||
my $dependency_diffs = $dbh->selectall_arrayref(
|
||||
"SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name,
|
||||
bugs_activity.removed, bugs_activity.added
|
||||
FROM bugs_activity
|
||||
INNER JOIN bugs
|
||||
ON bugs.bug_id = bugs_activity.bug_id
|
||||
INNER JOIN dependencies
|
||||
ON bugs_activity.bug_id = dependencies.dependson
|
||||
INNER JOIN fielddefs
|
||||
ON fielddefs.id = bugs_activity.fieldid
|
||||
WHERE dependencies.blocked = ?
|
||||
AND (fielddefs.name = 'bug_status'
|
||||
OR fielddefs.name = 'resolution')
|
||||
$when_restriction
|
||||
$dep_restriction
|
||||
ORDER BY bugs_activity.bug_when, bugs.bug_id", undef, @args);
|
||||
|
||||
my $thisdiff = "";
|
||||
my $lastbug = "";
|
||||
my $interestingchange = 0;
|
||||
foreach my $dependency_diff (@$dependency_diffs) {
|
||||
my ($depbug, $summary, $what, $old, $new) = @$dependency_diff;
|
||||
|
||||
if ($depbug ne $lastbug) {
|
||||
if ($interestingchange) {
|
||||
$deptext .= $thisdiff;
|
||||
}
|
||||
$lastbug = $depbug;
|
||||
my $urlbase = Bugzilla->params->{"urlbase"};
|
||||
$thisdiff =
|
||||
"\nBug $id depends on bug $depbug, which changed state.\n\n" .
|
||||
"Bug $depbug Summary: $summary\n" .
|
||||
"${urlbase}show_bug.cgi?id=$depbug\n\n";
|
||||
$thisdiff .= three_columns("What ", "Old Value", "New Value");
|
||||
$thisdiff .= ('-' x 76) . "\n";
|
||||
$interestingchange = 0;
|
||||
}
|
||||
$thisdiff .= three_columns($fielddescription{$what}, $old, $new);
|
||||
if ($what eq 'bug_status'
|
||||
&& is_open_state($old) ne is_open_state($new))
|
||||
{
|
||||
$interestingchange = 1;
|
||||
}
|
||||
push(@depbugs, $depbug);
|
||||
}
|
||||
|
||||
if ($interestingchange) {
|
||||
$deptext .= $thisdiff;
|
||||
}
|
||||
$deptext = trim($deptext);
|
||||
|
||||
if ($deptext) {
|
||||
my $diffpart = {};
|
||||
$diffpart->{'text'} = "\n" . trim("\n\n" . $deptext);
|
||||
push(@diffparts, $diffpart);
|
||||
}
|
||||
}
|
||||
|
||||
my ($raw_comments, $anyprivate, $count) = get_comments_by_bug($id, $start, $end);
|
||||
|
||||
###########################################################################
|
||||
# Start of email filtering code
|
||||
###########################################################################
|
||||
|
||||
# A user_id => roles hash to keep track of people.
|
||||
my %recipients;
|
||||
my %watching;
|
||||
|
||||
# Now we work out all the people involved with this bug, and note all of
|
||||
# the relationships in a hash. The keys are userids, the values are an
|
||||
# array of role constants.
|
||||
|
||||
# Voters
|
||||
my $voters = $dbh->selectcol_arrayref(
|
||||
"SELECT who FROM votes WHERE bug_id = ?", undef, ($id));
|
||||
|
||||
$recipients{$_}->{+REL_VOTER} = BIT_DIRECT foreach (@$voters);
|
||||
|
||||
# CCs
|
||||
$recipients{$_}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
|
||||
|
||||
# Reporter (there's only ever one)
|
||||
$recipients{$reporter}->{+REL_REPORTER} = BIT_DIRECT;
|
||||
|
||||
# QA Contact
|
||||
if (Bugzilla->params->{'useqacontact'}) {
|
||||
foreach (@qa_contacts) {
|
||||
# QA Contact can be blank; ignore it if so.
|
||||
$recipients{$_}->{+REL_QA} = BIT_DIRECT if $_;
|
||||
}
|
||||
}
|
||||
|
||||
# Assignee
|
||||
$recipients{$_}->{+REL_ASSIGNEE} = BIT_DIRECT foreach (@assignees);
|
||||
|
||||
# The last relevant set of people are those who are being removed from
|
||||
# their roles in this change. We get their names out of the diffs.
|
||||
foreach my $ref (@$diffs) {
|
||||
my ($who, $whoname, $what, $when, $old, $new) = (@$ref);
|
||||
if ($old) {
|
||||
# You can't stop being the reporter, and mail isn't sent if you
|
||||
# remove your vote.
|
||||
# Ignore people whose user account has been deleted or renamed.
|
||||
if ($what eq "CC") {
|
||||
foreach my $cc_user (split(/[\s,]+/, $old)) {
|
||||
my $uid = login_to_id($cc_user);
|
||||
$recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
|
||||
}
|
||||
}
|
||||
elsif ($what eq "QAContact") {
|
||||
my $uid = login_to_id($old);
|
||||
$recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
|
||||
}
|
||||
elsif ($what eq "AssignedTo") {
|
||||
my $uid = login_to_id($old);
|
||||
$recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Bugzilla->params->{"supportwatchers"}) {
|
||||
# Find all those user-watching anyone on the current list, who is not
|
||||
# on it already themselves.
|
||||
my $involved = join(",", keys %recipients);
|
||||
|
||||
my $userwatchers =
|
||||
$dbh->selectall_arrayref("SELECT watcher, watched FROM watch
|
||||
WHERE watched IN ($involved)");
|
||||
|
||||
# Mark these people as having the role of the person they are watching
|
||||
foreach my $watch (@$userwatchers) {
|
||||
while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
|
||||
$recipients{$watch->[0]}->{$role} |= BIT_WATCHING
|
||||
if $bits & BIT_DIRECT;
|
||||
}
|
||||
push (@{$watching{$watch->[0]}}, $watch->[1]);
|
||||
}
|
||||
}
|
||||
|
||||
# Global watcher
|
||||
my @watchers = split(/[,\s]+/, Bugzilla->params->{'globalwatchers'});
|
||||
foreach (@watchers) {
|
||||
my $watcher_id = login_to_id($_);
|
||||
next unless $watcher_id;
|
||||
$recipients{$watcher_id}->{+REL_GLOBAL_WATCHER} = BIT_DIRECT;
|
||||
}
|
||||
|
||||
# We now have a complete set of all the users, and their relationships to
|
||||
# the bug in question. However, we are not necessarily going to mail them
|
||||
# all - there are preferences, permissions checks and all sorts to do yet.
|
||||
my @sent;
|
||||
my @excluded;
|
||||
|
||||
# Some comments are language specific. We cache them here.
|
||||
my %comments;
|
||||
|
||||
foreach my $user_id (keys %recipients) {
|
||||
my %rels_which_want;
|
||||
my $sent_mail = 0;
|
||||
|
||||
my $user = new Bugzilla::User($user_id);
|
||||
# Deleted users must be excluded.
|
||||
next unless $user;
|
||||
|
||||
# What's the language chosen by this user for email?
|
||||
my $lang = $user->settings->{'lang'}->{'value'};
|
||||
|
||||
if ($user->can_see_bug($id)) {
|
||||
# It's time to format language specific comments.
|
||||
unless (exists $comments{$lang}) {
|
||||
Bugzilla->template_inner($lang);
|
||||
$comments{$lang} = prepare_comments($raw_comments, $count);
|
||||
Bugzilla->template_inner("");
|
||||
}
|
||||
|
||||
# Go through each role the user has and see if they want mail in
|
||||
# that role.
|
||||
foreach my $relationship (keys %{$recipients{$user_id}}) {
|
||||
if ($user->wants_bug_mail($id,
|
||||
$relationship,
|
||||
$diffs,
|
||||
$comments{$lang},
|
||||
$deptext,
|
||||
$changer,
|
||||
!$start))
|
||||
{
|
||||
$rels_which_want{$relationship} =
|
||||
$recipients{$user_id}->{$relationship};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(%rels_which_want)) {
|
||||
# So the user exists, can see the bug, and wants mail in at least
|
||||
# one role. But do we want to send it to them?
|
||||
|
||||
# If we are using insiders, and the comment is private, only send
|
||||
# to insiders
|
||||
my $insider_ok = 1;
|
||||
$insider_ok = 0 if (Bugzilla->params->{"insidergroup"} &&
|
||||
($anyprivate != 0) &&
|
||||
(!$user->groups->{Bugzilla->params->{"insidergroup"}}));
|
||||
|
||||
# We shouldn't send mail if this is a dependency mail (i.e. there
|
||||
# is something in @depbugs), and any of the depending bugs are not
|
||||
# visible to the user. This is to avoid leaking the summaries of
|
||||
# confidential bugs.
|
||||
my $dep_ok = 1;
|
||||
foreach my $dep_id (@depbugs) {
|
||||
if (!$user->can_see_bug($dep_id)) {
|
||||
$dep_ok = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure the user isn't in the nomail list, and the insider and
|
||||
# dep checks passed.
|
||||
if ($user->email_enabled &&
|
||||
$insider_ok &&
|
||||
$dep_ok)
|
||||
{
|
||||
# OK, OK, if we must. Email the user.
|
||||
$sent_mail = sendMail($user,
|
||||
\@headerlist,
|
||||
\%rels_which_want,
|
||||
\%values,
|
||||
\%defmailhead,
|
||||
\%fielddescription,
|
||||
\@diffparts,
|
||||
$comments{$lang},
|
||||
$anyprivate,
|
||||
! $start,
|
||||
$id,
|
||||
exists $watching{$user_id} ?
|
||||
$watching{$user_id} : undef);
|
||||
}
|
||||
}
|
||||
|
||||
if ($sent_mail) {
|
||||
push(@sent, $user->login);
|
||||
}
|
||||
else {
|
||||
push(@excluded, $user->login);
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
|
||||
undef, ($end, $id));
|
||||
|
||||
return {'sent' => \@sent, 'excluded' => \@excluded};
|
||||
}
|
||||
|
||||
sub sendMail {
|
||||
my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,
|
||||
$diffRef, $newcomments, $anyprivate, $isnew,
|
||||
$id, $watchingRef) = @_;
|
||||
|
||||
my %values = %$valueRef;
|
||||
my @headerlist = @$hlRef;
|
||||
my %mailhead = %$dmhRef;
|
||||
my %fielddescription = %$fdRef;
|
||||
my @diffparts = @$diffRef;
|
||||
my $head = "";
|
||||
|
||||
foreach my $f (@headerlist) {
|
||||
if ($mailhead{$f}) {
|
||||
my $value = $values{$f};
|
||||
# If there isn't anything to show, don't include this header
|
||||
if (! $value) {
|
||||
next;
|
||||
}
|
||||
# Only send estimated_time if it is enabled and the user is in the group
|
||||
if (($f ne 'estimated_time' && $f ne 'deadline') ||
|
||||
$user->groups->{Bugzilla->params->{'timetrackinggroup'}}) {
|
||||
|
||||
my $desc = $fielddescription{$f};
|
||||
$head .= multiline_sprintf(FORMAT_DOUBLE, ["$desc:", $value],
|
||||
FORMAT_2_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Build difftext (the actions) by verifying the user should see them
|
||||
my $difftext = "";
|
||||
my $diffheader = "";
|
||||
my $add_diff;
|
||||
|
||||
foreach my $diff (@diffparts) {
|
||||
$add_diff = 0;
|
||||
|
||||
if (exists($diff->{'fieldname'}) &&
|
||||
($diff->{'fieldname'} eq 'estimated_time' ||
|
||||
$diff->{'fieldname'} eq 'remaining_time' ||
|
||||
$diff->{'fieldname'} eq 'work_time' ||
|
||||
$diff->{'fieldname'} eq 'deadline')){
|
||||
if ($user->groups->{Bugzilla->params->{"timetrackinggroup"}}) {
|
||||
$add_diff = 1;
|
||||
}
|
||||
} elsif (($diff->{'isprivate'})
|
||||
&& Bugzilla->params->{'insidergroup'}
|
||||
&& !($user->groups->{Bugzilla->params->{'insidergroup'}})
|
||||
) {
|
||||
$add_diff = 0;
|
||||
} else {
|
||||
$add_diff = 1;
|
||||
}
|
||||
|
||||
if ($add_diff) {
|
||||
if (exists($diff->{'header'}) &&
|
||||
($diffheader ne $diff->{'header'})) {
|
||||
$diffheader = $diff->{'header'};
|
||||
$difftext .= $diffheader;
|
||||
}
|
||||
$difftext .= $diff->{'text'};
|
||||
}
|
||||
}
|
||||
|
||||
if ($difftext eq "" && $newcomments eq "" && !$isnew) {
|
||||
# Whoops, no differences!
|
||||
return 0;
|
||||
}
|
||||
|
||||
# If an attachment was created, then add an URL. (Note: the 'g'lobal
|
||||
# replace should work with comments with multiple attachments.)
|
||||
|
||||
if ( $newcomments =~ /Created an attachment \(/ ) {
|
||||
|
||||
my $showattachurlbase =
|
||||
Bugzilla->params->{'urlbase'} . "attachment.cgi?id=";
|
||||
|
||||
$newcomments =~ s/(Created an attachment \(id=([0-9]+)\))/$1\n --> \(${showattachurlbase}$2\)/g;
|
||||
}
|
||||
|
||||
my $diffs = $difftext . "\n\n" . $newcomments;
|
||||
if ($isnew) {
|
||||
$diffs = $head . ($difftext ? "\n\n" : "") . $diffs;
|
||||
}
|
||||
|
||||
my (@reasons, @reasons_watch);
|
||||
while (my ($relationship, $bits) = each %{$relRef}) {
|
||||
push(@reasons, $relationship) if ($bits & BIT_DIRECT);
|
||||
push(@reasons_watch, $relationship) if ($bits & BIT_WATCHING);
|
||||
}
|
||||
|
||||
my @headerrel = map { REL_NAMES->{$_} } @reasons;
|
||||
my @watchingrel = map { REL_NAMES->{$_} } @reasons_watch;
|
||||
push(@headerrel, 'None') unless @headerrel;
|
||||
push(@watchingrel, 'None') unless @watchingrel;
|
||||
push @watchingrel, map { user_id_to_login($_) } @$watchingRef;
|
||||
|
||||
my $threadingmarker = build_thread_marker($id, $user->id, $isnew);
|
||||
|
||||
my $vars = {
|
||||
isnew => $isnew,
|
||||
to => $user->email,
|
||||
bugid => $id,
|
||||
alias => Bugzilla->params->{'usebugaliases'} ? $values{'alias'} : "",
|
||||
classification => $values{'classification'},
|
||||
product => $values{'product'},
|
||||
comp => $values{'component'},
|
||||
keywords => $values{'keywords'},
|
||||
severity => $values{'bug_severity'},
|
||||
status => $values{'bug_status'},
|
||||
priority => $values{'priority'},
|
||||
assignedto => $values{'assigned_to'},
|
||||
assignedtoname => Bugzilla::User->new({name => $values{'assigned_to'}})->name,
|
||||
targetmilestone => $values{'target_milestone'},
|
||||
changedfields => $values{'changed_fields'},
|
||||
summary => $values{'short_desc'},
|
||||
reasons => \@reasons,
|
||||
reasons_watch => \@reasons_watch,
|
||||
reasonsheader => join(" ", @headerrel),
|
||||
reasonswatchheader => join(" ", @watchingrel),
|
||||
changer => $values{'changer'},
|
||||
changername => $values{'changername'},
|
||||
reporter => $values{'reporter'},
|
||||
reportername => Bugzilla::User->new({name => $values{'reporter'}})->name,
|
||||
diffs => $diffs,
|
||||
threadingmarker => $threadingmarker
|
||||
};
|
||||
|
||||
my $msg;
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
$template->process("email/newchangedmail.txt.tmpl", $vars, \$msg)
|
||||
|| ThrowTemplateError($template->error());
|
||||
Bugzilla->template_inner("");
|
||||
|
||||
MessageToMTA($msg);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
# Get bug comments for the given period.
|
||||
sub get_comments_by_bug {
|
||||
my ($id, $start, $end) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $result = "";
|
||||
my $count = 0;
|
||||
my $anyprivate = 0;
|
||||
|
||||
# $start will be undef for new bugs, and defined for pre-existing bugs.
|
||||
if ($start) {
|
||||
# If $start is not NULL, obtain the count-index
|
||||
# of this comment for the leading "Comment #xxx" line.
|
||||
$count = $dbh->selectrow_array('SELECT COUNT(*) FROM longdescs
|
||||
WHERE bug_id = ? AND bug_when <= ?',
|
||||
undef, ($id, $start));
|
||||
}
|
||||
|
||||
my $raw = 1; # Do not format comments which are not of type CMT_NORMAL.
|
||||
my $comments = Bugzilla::Bug::GetComments($id, "oldest_to_newest", $start, $end, $raw);
|
||||
|
||||
if (Bugzilla->params->{'insidergroup'}) {
|
||||
$anyprivate = 1 if scalar(grep {$_->{'isprivate'} > 0} @$comments);
|
||||
}
|
||||
|
||||
return ($comments, $anyprivate, $count);
|
||||
}
|
||||
|
||||
# Prepare comments for the given language.
|
||||
sub prepare_comments {
|
||||
my ($raw_comments, $count) = @_;
|
||||
|
||||
my $result = "";
|
||||
foreach my $comment (@$raw_comments) {
|
||||
if ($count) {
|
||||
$result .= "\n\n--- Comment #$count from " . $comment->{'author'}->identity .
|
||||
" " . format_time($comment->{'time'}) . " ---\n";
|
||||
}
|
||||
# Format language specific comments. We don't update $comment->{'body'}
|
||||
# directly, otherwise it would grow everytime you call format_comment()
|
||||
# with a different language as some text may be appended to the existing one.
|
||||
my $body = Bugzilla::Bug::format_comment($comment);
|
||||
$result .= ($comment->{'already_wrapped'} ? $body : wrap_comment($body));
|
||||
$count++;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,385 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::CGI;
|
||||
|
||||
BEGIN {
|
||||
if ($^O =~ /MSWin32/i) {
|
||||
# Help CGI find the correct temp directory as the default list
|
||||
# isn't Windows friendly (Bug 248988)
|
||||
$ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
|
||||
}
|
||||
}
|
||||
|
||||
use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH);
|
||||
|
||||
use base qw(CGI);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
# We need to disable output buffering - see bug 179174
|
||||
$| = 1;
|
||||
|
||||
# Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
|
||||
# their browser window while a script is running, the web server sends these
|
||||
# signals, and we don't want to die half way through a write.
|
||||
$::SIG{TERM} = 'IGNORE';
|
||||
$::SIG{PIPE} = 'IGNORE';
|
||||
|
||||
# CGI.pm uses AUTOLOAD, but explicitly defines a DESTROY sub.
|
||||
# We need to do so, too, otherwise perl dies when the object is destroyed
|
||||
# and we don't have a DESTROY method (because CGI.pm's AUTOLOAD will |die|
|
||||
# on getting an unknown sub to try to call)
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
$self->SUPER::DESTROY(@_);
|
||||
};
|
||||
|
||||
sub new {
|
||||
my ($invocant, @args) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
my $self = $class->SUPER::new(@args);
|
||||
|
||||
# Make sure our outgoing cookie list is empty on each invocation
|
||||
$self->{Bugzilla_cookie_list} = [];
|
||||
|
||||
# Send appropriate charset
|
||||
$self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
|
||||
|
||||
# Check for errors
|
||||
# All of the Bugzilla code wants to do this, so do it here instead of
|
||||
# in each script
|
||||
|
||||
my $err = $self->cgi_error;
|
||||
|
||||
if ($err) {
|
||||
# Note that this error block is only triggered by CGI.pm for malformed
|
||||
# multipart requests, and so should never happen unless there is a
|
||||
# browser bug.
|
||||
|
||||
print $self->header(-status => $err);
|
||||
|
||||
# ThrowCodeError wants to print the header, so it grabs Bugzilla->cgi
|
||||
# which creates a new Bugzilla::CGI object, which fails again, which
|
||||
# ends up here, and calls ThrowCodeError, and then recurses forever.
|
||||
# So don't use it.
|
||||
# In fact, we can't use templates at all, because we need a CGI object
|
||||
# to determine the template lang as well as the current url (from the
|
||||
# template)
|
||||
# Since this is an internal error which indicates a severe browser bug,
|
||||
# just die.
|
||||
die "CGI parsing error: $err";
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# We want this sorted plus the ability to exclude certain params
|
||||
sub canonicalise_query {
|
||||
my ($self, @exclude) = @_;
|
||||
|
||||
# Reconstruct the URL by concatenating the sorted param=value pairs
|
||||
my @parameters;
|
||||
foreach my $key (sort($self->param())) {
|
||||
# Leave this key out if it's in the exclude list
|
||||
next if lsearch(\@exclude, $key) != -1;
|
||||
|
||||
my $esc_key = url_quote($key);
|
||||
|
||||
foreach my $value ($self->param($key)) {
|
||||
if (defined($value)) {
|
||||
my $esc_value = url_quote($value);
|
||||
|
||||
push(@parameters, "$esc_key=$esc_value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return join("&", @parameters);
|
||||
}
|
||||
|
||||
sub clean_search_url {
|
||||
my $self = shift;
|
||||
# Delete any empty URL parameter
|
||||
my @cgi_params = $self->param;
|
||||
|
||||
foreach my $param (@cgi_params) {
|
||||
if (defined $self->param($param) && $self->param($param) eq '') {
|
||||
$self->delete($param);
|
||||
$self->delete("${param}_type");
|
||||
}
|
||||
|
||||
# Boolean Chart stuff is empty if it's "noop"
|
||||
if ($param =~ /\d-\d-\d/ && defined $self->param($param)
|
||||
&& $self->param($param) eq 'noop')
|
||||
{
|
||||
$self->delete($param);
|
||||
}
|
||||
}
|
||||
|
||||
# Delete certain parameters if the associated parameter is empty.
|
||||
$self->delete('bugidtype') if !$self->param('bug_id');
|
||||
$self->delete('emailtype1') if !$self->param('email1');
|
||||
$self->delete('emailtype2') if !$self->param('email2');
|
||||
}
|
||||
|
||||
# Overwrite to ensure nph doesn't get set, and unset HEADERS_ONCE
|
||||
sub multipart_init {
|
||||
my $self = shift;
|
||||
|
||||
# Keys are case-insensitive, map to lowercase
|
||||
my %args = @_;
|
||||
my %param;
|
||||
foreach my $key (keys %args) {
|
||||
$param{lc $key} = $args{$key};
|
||||
}
|
||||
|
||||
# Set the MIME boundary and content-type
|
||||
my $boundary = $param{'-boundary'} || '------- =_aaaaaaaaaa0';
|
||||
delete $param{'-boundary'};
|
||||
$self->{'separator'} = "\r\n--$boundary\r\n";
|
||||
$self->{'final_separator'} = "\r\n--$boundary--\r\n";
|
||||
$param{'-type'} = SERVER_PUSH($boundary);
|
||||
|
||||
# Note: CGI.pm::multipart_init up to v3.04 explicitly set nph to 0
|
||||
# CGI.pm::multipart_init v3.05 explicitly sets nph to 1
|
||||
# CGI.pm's header() sets nph according to a param or $CGI::NPH, which
|
||||
# is the desired behaviour.
|
||||
|
||||
return $self->header(
|
||||
%param,
|
||||
) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
|
||||
}
|
||||
|
||||
# Have to add the cookies in.
|
||||
sub multipart_start {
|
||||
my $self = shift;
|
||||
|
||||
my %args = @_;
|
||||
|
||||
# CGI.pm::multipart_start doesn't honour its own charset information, so
|
||||
# we do it ourselves here
|
||||
if (defined $self->charset() && defined $args{-type}) {
|
||||
# Remove any existing charset specifier
|
||||
$args{-type} =~ s/;.*$//;
|
||||
# and add the specified one
|
||||
$args{-type} .= '; charset=' . $self->charset();
|
||||
}
|
||||
|
||||
my $headers = $self->SUPER::multipart_start(%args);
|
||||
# Eliminate the one extra CRLF at the end.
|
||||
$headers =~ s/$CGI::CRLF$//;
|
||||
# Add the cookies. We have to do it this way instead of
|
||||
# passing them to multpart_start, because CGI.pm's multipart_start
|
||||
# doesn't understand a '-cookie' argument pointing to an arrayref.
|
||||
foreach my $cookie (@{$self->{Bugzilla_cookie_list}}) {
|
||||
$headers .= "Set-Cookie: ${cookie}${CGI::CRLF}";
|
||||
}
|
||||
$headers .= $CGI::CRLF;
|
||||
return $headers;
|
||||
}
|
||||
|
||||
# Override header so we can add the cookies in
|
||||
sub header {
|
||||
my $self = shift;
|
||||
|
||||
# Add the cookies in if we have any
|
||||
if (scalar(@{$self->{Bugzilla_cookie_list}})) {
|
||||
if (scalar(@_) == 1) {
|
||||
# if there's only one parameter, then it's a Content-Type.
|
||||
# Since we're adding parameters we have to name it.
|
||||
unshift(@_, '-type' => shift(@_));
|
||||
}
|
||||
unshift(@_, '-cookie' => $self->{Bugzilla_cookie_list});
|
||||
}
|
||||
|
||||
return $self->SUPER::header(@_) || "";
|
||||
}
|
||||
|
||||
# CGI.pm is not utf8-aware and passes data as bytes instead of UTF-8 strings.
|
||||
sub param {
|
||||
my $self = shift;
|
||||
if (Bugzilla->params->{'utf8'} && scalar(@_) == 1) {
|
||||
if (wantarray) {
|
||||
return map { _fix_utf8($_) } $self->SUPER::param(@_);
|
||||
}
|
||||
else {
|
||||
return _fix_utf8(scalar $self->SUPER::param(@_));
|
||||
}
|
||||
}
|
||||
return $self->SUPER::param(@_);
|
||||
}
|
||||
|
||||
sub _fix_utf8 {
|
||||
my $input = shift;
|
||||
# The is_utf8 is here in case CGI gets smart about utf8 someday.
|
||||
utf8::decode($input) if defined $input && !utf8::is_utf8($input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
# The various parts of Bugzilla which create cookies don't want to have to
|
||||
# pass them around to all of the callers. Instead, store them locally here,
|
||||
# and then output as required from |header|.
|
||||
sub send_cookie {
|
||||
my $self = shift;
|
||||
|
||||
# Move the param list into a hash for easier handling.
|
||||
my %paramhash;
|
||||
my @paramlist;
|
||||
my ($key, $value);
|
||||
while ($key = shift) {
|
||||
$value = shift;
|
||||
$paramhash{$key} = $value;
|
||||
}
|
||||
|
||||
# Complain if -value is not given or empty (bug 268146).
|
||||
if (!exists($paramhash{'-value'}) || !$paramhash{'-value'}) {
|
||||
ThrowCodeError('cookies_need_value');
|
||||
}
|
||||
|
||||
# Add the default path and the domain in.
|
||||
$paramhash{'-path'} = Bugzilla->params->{'cookiepath'};
|
||||
$paramhash{'-domain'} = Bugzilla->params->{'cookiedomain'}
|
||||
if Bugzilla->params->{'cookiedomain'};
|
||||
|
||||
# Move the param list back into an array for the call to cookie().
|
||||
foreach (keys(%paramhash)) {
|
||||
unshift(@paramlist, $_ => $paramhash{$_});
|
||||
}
|
||||
|
||||
push(@{$self->{'Bugzilla_cookie_list'}}, $self->cookie(@paramlist));
|
||||
}
|
||||
|
||||
# Cookies are removed by setting an expiry date in the past.
|
||||
# This method is a send_cookie wrapper doing exactly this.
|
||||
sub remove_cookie {
|
||||
my $self = shift;
|
||||
my ($cookiename) = (@_);
|
||||
|
||||
# Expire the cookie, giving a non-empty dummy value (bug 268146).
|
||||
$self->send_cookie('-name' => $cookiename,
|
||||
'-expires' => 'Tue, 15-Sep-1998 21:49:00 GMT',
|
||||
'-value' => 'X');
|
||||
}
|
||||
|
||||
# Redirect to https if required
|
||||
sub require_https {
|
||||
my ($self, $url) = @_;
|
||||
# Do not create query string if data submitted via XMLRPC
|
||||
# since we want the data to be resubmitted over POST method.
|
||||
my $query = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 0 : 1;
|
||||
# XMLRPC clients (SOAP::Lite at least) requires 301 to redirect properly
|
||||
# and do not work with 302.
|
||||
my $status = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 301 : 302;
|
||||
if (defined $url) {
|
||||
$url .= $self->url('-path_info' => 1, '-query' => $query, '-relative' => 1);
|
||||
} else {
|
||||
$url = $self->self_url;
|
||||
$url =~ s/^http:/https:/i;
|
||||
}
|
||||
print $self->redirect(-location => $url, -status => $status);
|
||||
# When using XML-RPC with mod_perl, we need the headers sent immediately.
|
||||
$self->r->rflush if $ENV{MOD_PERL};
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::CGI - CGI handling for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::CGI;
|
||||
|
||||
my $cgi = new Bugzilla::CGI();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package inherits from the standard CGI module, to provide additional
|
||||
Bugzilla-specific functionality. In general, see L<the CGI.pm docs|CGI> for
|
||||
documention.
|
||||
|
||||
=head1 CHANGES FROM L<CGI.PM|CGI>
|
||||
|
||||
Bugzilla::CGI has some differences from L<CGI.pm|CGI>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<cgi_error> is automatically checked
|
||||
|
||||
After creating the CGI object, C<Bugzilla::CGI> automatically checks
|
||||
I<cgi_error>, and throws a CodeError if a problem is detected.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ADDITIONAL FUNCTIONS
|
||||
|
||||
I<Bugzilla::CGI> also includes additional functions.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<canonicalise_query(@exclude)>
|
||||
|
||||
This returns a sorted string of the parameters, suitable for use in a url.
|
||||
Values in C<@exclude> are not included in the result.
|
||||
|
||||
=item C<send_cookie>
|
||||
|
||||
This routine is identical to the cookie generation part of CGI.pm's C<cookie>
|
||||
routine, except that it knows about Bugzilla's cookie_path and cookie_domain
|
||||
parameters and takes them into account if necessary.
|
||||
This should be used by all Bugzilla code (instead of C<cookie> or the C<-cookie>
|
||||
argument to C<header>), so that under mod_perl the headers can be sent
|
||||
correctly, using C<print> or the mod_perl APIs as appropriate.
|
||||
|
||||
To remove (expire) a cookie, use C<remove_cookie>.
|
||||
|
||||
=item C<remove_cookie>
|
||||
|
||||
This is a wrapper around send_cookie, setting an expiry date in the past,
|
||||
effectively removing the cookie.
|
||||
|
||||
As its only argument, it takes the name of the cookie to expire.
|
||||
|
||||
=item C<require_https($baseurl)>
|
||||
|
||||
This routine redirects the client to a different location using the https protocol.
|
||||
If the client is using XMLRPC, it will not retain the QUERY_STRING since XMLRPC uses POST.
|
||||
|
||||
It takes an optional argument which will be used as the base URL. If $baseurl
|
||||
is not provided, the current URL is used.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<CGI|CGI>, L<CGI::Cookie|CGI::Cookie>
|
||||
@@ -1,446 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
||||
# Albert Ting <altlst@sonic.net>
|
||||
# A. Karl Kornel <karl@kornel.name>
|
||||
|
||||
use strict;
|
||||
|
||||
# This module represents a chart.
|
||||
#
|
||||
# Note that it is perfectly legal for the 'lines' member variable of this
|
||||
# class (which is an array of Bugzilla::Series objects) to have empty members
|
||||
# in it. If this is true, the 'labels' array will also have empty members at
|
||||
# the same points.
|
||||
package Bugzilla::Chart;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Series;
|
||||
|
||||
use Date::Format;
|
||||
use Date::Parse;
|
||||
use List::Util qw(max);
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
|
||||
if ($#_ == 0) {
|
||||
# Construct from a CGI object.
|
||||
$self->init($_[0]);
|
||||
}
|
||||
else {
|
||||
die("CGI object not passed in - invalid number of args \($#_\)($_)");
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
my $cgi = shift;
|
||||
|
||||
# The data structure is a list of lists (lines) of Series objects.
|
||||
# There is a separate list for the labels.
|
||||
#
|
||||
# The URL encoding is:
|
||||
# line0=67&line0=73&line1=81&line2=67...
|
||||
# &label0=B+/+R+/+NEW&label1=...
|
||||
# &select0=1&select3=1...
|
||||
# &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
|
||||
# >=1&labelgt=Grand+Total
|
||||
foreach my $param ($cgi->param()) {
|
||||
# Store all the lines
|
||||
if ($param =~ /^line(\d+)$/) {
|
||||
foreach my $series_id ($cgi->param($param)) {
|
||||
detaint_natural($series_id)
|
||||
|| ThrowCodeError("invalid_series_id");
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
push(@{$self->{'lines'}[$1]}, $series) if $series;
|
||||
}
|
||||
}
|
||||
|
||||
# Store all the labels
|
||||
if ($param =~ /^label(\d+)$/) {
|
||||
$self->{'labels'}[$1] = $cgi->param($param);
|
||||
}
|
||||
}
|
||||
|
||||
# Store the miscellaneous metadata
|
||||
$self->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0;
|
||||
$self->{'gt'} = $cgi->param('gt') ? 1 : 0;
|
||||
$self->{'labelgt'} = $cgi->param('labelgt');
|
||||
$self->{'datefrom'} = $cgi->param('datefrom');
|
||||
$self->{'dateto'} = $cgi->param('dateto');
|
||||
|
||||
# If we are cumulating, a grand total makes no sense
|
||||
$self->{'gt'} = 0 if $self->{'cumulate'};
|
||||
|
||||
# Make sure the dates are ones we are able to interpret
|
||||
foreach my $date ('datefrom', 'dateto') {
|
||||
if ($self->{$date}) {
|
||||
$self->{$date} = str2time($self->{$date})
|
||||
|| ThrowUserError("illegal_date", { date => $self->{$date}});
|
||||
}
|
||||
}
|
||||
|
||||
# datefrom can't be after dateto
|
||||
if ($self->{'datefrom'} && $self->{'dateto'} &&
|
||||
$self->{'datefrom'} > $self->{'dateto'})
|
||||
{
|
||||
ThrowUserError("misarranged_dates",
|
||||
{'datefrom' => $cgi->param('datefrom'),
|
||||
'dateto' => $cgi->param('dateto')});
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selected series are added to it.
|
||||
sub add {
|
||||
my $self = shift;
|
||||
my @series_ids = @_;
|
||||
|
||||
# Get the current size of the series; required for adding Grand Total later
|
||||
my $current_size = scalar($self->getSeriesIDs());
|
||||
|
||||
# Count the number of added series
|
||||
my $added = 0;
|
||||
# Create new Series and push them on to the list of lines.
|
||||
# Note that new lines have no label; the display template is responsible
|
||||
# for inventing something sensible.
|
||||
foreach my $series_id (@series_ids) {
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
if ($series) {
|
||||
push(@{$self->{'lines'}}, [$series]);
|
||||
push(@{$self->{'labels'}}, "");
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
|
||||
# If we are going from < 2 to >= 2 series, add the Grand Total line.
|
||||
if (!$self->{'gt'}) {
|
||||
if ($current_size < 2 &&
|
||||
$current_size + $added >= 2)
|
||||
{
|
||||
$self->{'gt'} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are removed from it.
|
||||
sub remove {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
foreach my $line_id (@line_ids) {
|
||||
if ($line_id == 65536) {
|
||||
# Magic value - delete Grand Total.
|
||||
$self->{'gt'} = 0;
|
||||
}
|
||||
else {
|
||||
delete($self->{'lines'}->[$line_id]);
|
||||
delete($self->{'labels'}->[$line_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are summed.
|
||||
sub sum {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
# We can't add the Grand Total to things.
|
||||
@line_ids = grep(!/^65536$/, @line_ids);
|
||||
|
||||
# We can't add less than two things.
|
||||
return if scalar(@line_ids) < 2;
|
||||
|
||||
my @series;
|
||||
my $label = "";
|
||||
my $biggestlength = 0;
|
||||
|
||||
# We rescue the Series objects of all the series involved in the sum.
|
||||
foreach my $line_id (@line_ids) {
|
||||
my @line = @{$self->{'lines'}->[$line_id]};
|
||||
|
||||
foreach my $series (@line) {
|
||||
push(@series, $series);
|
||||
}
|
||||
|
||||
# We keep the label that labels the line with the most series.
|
||||
if (scalar(@line) > $biggestlength) {
|
||||
$biggestlength = scalar(@line);
|
||||
$label = $self->{'labels'}->[$line_id];
|
||||
}
|
||||
}
|
||||
|
||||
$self->remove(@line_ids);
|
||||
|
||||
push(@{$self->{'lines'}}, \@series);
|
||||
push(@{$self->{'labels'}}, $label);
|
||||
}
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
$self->{'_data'} ||= $self->readData();
|
||||
return $self->{'_data'};
|
||||
}
|
||||
|
||||
# Convert the Chart's data into a plottable form in $self->{'_data'}.
|
||||
sub readData {
|
||||
my $self = shift;
|
||||
my @data;
|
||||
my @maxvals;
|
||||
|
||||
# Note: you get a bad image if getSeriesIDs returns nothing
|
||||
# We need to handle errors better.
|
||||
my $series_ids = join(",", $self->getSeriesIDs());
|
||||
|
||||
return [] unless $series_ids;
|
||||
|
||||
# Work out the date boundaries for our data.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The date used is the one given if it's in a sensible range; otherwise,
|
||||
# it's the earliest or latest date in the database as appropriate.
|
||||
my $datefrom = $dbh->selectrow_array("SELECT MIN(series_date) " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id IN ($series_ids)");
|
||||
$datefrom = str2time($datefrom);
|
||||
|
||||
if ($self->{'datefrom'} && $self->{'datefrom'} > $datefrom) {
|
||||
$datefrom = $self->{'datefrom'};
|
||||
}
|
||||
|
||||
my $dateto = $dbh->selectrow_array("SELECT MAX(series_date) " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id IN ($series_ids)");
|
||||
$dateto = str2time($dateto);
|
||||
|
||||
if ($self->{'dateto'} && $self->{'dateto'} < $dateto) {
|
||||
$dateto = $self->{'dateto'};
|
||||
}
|
||||
|
||||
# Convert UNIX times back to a date format usable for SQL queries.
|
||||
my $sql_from = time2str('%Y-%m-%d', $datefrom);
|
||||
my $sql_to = time2str('%Y-%m-%d', $dateto);
|
||||
|
||||
# Prepare the query which retrieves the data for each series
|
||||
my $query = "SELECT " . $dbh->sql_to_days('series_date') . " - " .
|
||||
$dbh->sql_to_days('?') . ", series_value " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id = ? " .
|
||||
"AND series_date >= ?";
|
||||
if ($dateto) {
|
||||
$query .= " AND series_date <= ?";
|
||||
}
|
||||
|
||||
my $sth = $dbh->prepare($query);
|
||||
|
||||
my $gt_index = $self->{'gt'} ? scalar(@{$self->{'lines'}}) : undef;
|
||||
my $line_index = 0;
|
||||
|
||||
$maxvals[$gt_index] = 0 if $gt_index;
|
||||
|
||||
my @datediff_total;
|
||||
|
||||
foreach my $line (@{$self->{'lines'}}) {
|
||||
# Even if we end up with no data, we need an empty arrayref to prevent
|
||||
# errors in the PNG-generating code
|
||||
$data[$line_index] = [];
|
||||
$maxvals[$line_index] = 0;
|
||||
|
||||
foreach my $series (@$line) {
|
||||
|
||||
# Get the data for this series and add it on
|
||||
if ($dateto) {
|
||||
$sth->execute($sql_from, $series->{'series_id'}, $sql_from, $sql_to);
|
||||
}
|
||||
else {
|
||||
$sth->execute($sql_from, $series->{'series_id'}, $sql_from);
|
||||
}
|
||||
my $points = $sth->fetchall_arrayref();
|
||||
|
||||
foreach my $point (@$points) {
|
||||
my ($datediff, $value) = @$point;
|
||||
$data[$line_index][$datediff] ||= 0;
|
||||
$data[$line_index][$datediff] += $value;
|
||||
if ($data[$line_index][$datediff] > $maxvals[$line_index]) {
|
||||
$maxvals[$line_index] = $data[$line_index][$datediff];
|
||||
}
|
||||
|
||||
$datediff_total[$datediff] += $value;
|
||||
|
||||
# Add to the grand total, if we are doing that
|
||||
if ($gt_index) {
|
||||
$data[$gt_index][$datediff] ||= 0;
|
||||
$data[$gt_index][$datediff] += $value;
|
||||
if ($data[$gt_index][$datediff] > $maxvals[$gt_index]) {
|
||||
$maxvals[$gt_index] = $data[$gt_index][$datediff];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# We are done with the series making up this line, go to the next one
|
||||
$line_index++;
|
||||
}
|
||||
|
||||
# calculate maximum y value
|
||||
if ($self->{'cumulate'}) {
|
||||
# Make sure we do not try to take the max of an array with undef values
|
||||
my @processed_datediff;
|
||||
while (@datediff_total) {
|
||||
my $datediff = shift @datediff_total;
|
||||
push @processed_datediff, $datediff if defined($datediff);
|
||||
}
|
||||
$self->{'y_max_value'} = max(@processed_datediff);
|
||||
}
|
||||
else {
|
||||
$self->{'y_max_value'} = max(@maxvals);
|
||||
}
|
||||
$self->{'y_max_value'} |= 1; # For log()
|
||||
|
||||
# Align the max y value:
|
||||
# For one- or two-digit numbers, increase y_max_value until divisible by 8
|
||||
# For larger numbers, see the comments below to figure out what's going on
|
||||
if ($self->{'y_max_value'} < 100) {
|
||||
do {
|
||||
++$self->{'y_max_value'};
|
||||
} while ($self->{'y_max_value'} % 8 != 0);
|
||||
}
|
||||
else {
|
||||
# First, get the # of digits in the y_max_value
|
||||
my $num_digits = 1+int(log($self->{'y_max_value'})/log(10));
|
||||
|
||||
# We want to zero out all but the top 2 digits
|
||||
my $mask_length = $num_digits - 2;
|
||||
$self->{'y_max_value'} /= 10**$mask_length;
|
||||
$self->{'y_max_value'} = int($self->{'y_max_value'});
|
||||
$self->{'y_max_value'} *= 10**$mask_length;
|
||||
|
||||
# Add 10^$mask_length to the max value
|
||||
# Continue to increase until it's divisible by 8 * 10^($mask_length-1)
|
||||
# (Throwing in the -1 keeps at least the smallest digit at zero)
|
||||
do {
|
||||
$self->{'y_max_value'} += 10**$mask_length;
|
||||
} while ($self->{'y_max_value'} % (8*(10**($mask_length-1))) != 0);
|
||||
}
|
||||
|
||||
|
||||
# Add the x-axis labels into the data structure
|
||||
my $date_progression = generateDateProgression($datefrom, $dateto);
|
||||
unshift(@data, $date_progression);
|
||||
|
||||
if ($self->{'gt'}) {
|
||||
# Add Grand Total to label list
|
||||
push(@{$self->{'labels'}}, $self->{'labelgt'});
|
||||
|
||||
$data[$gt_index] ||= [];
|
||||
}
|
||||
|
||||
return \@data;
|
||||
}
|
||||
|
||||
# Flatten the data structure into a list of series_ids
|
||||
sub getSeriesIDs {
|
||||
my $self = shift;
|
||||
my @series_ids;
|
||||
|
||||
foreach my $line (@{$self->{'lines'}}) {
|
||||
foreach my $series (@$line) {
|
||||
push(@series_ids, $series->{'series_id'});
|
||||
}
|
||||
}
|
||||
|
||||
return @series_ids;
|
||||
}
|
||||
|
||||
# Class method to get the data necessary to populate the "select series"
|
||||
# widgets on various pages.
|
||||
sub getVisibleSeries {
|
||||
my %cats;
|
||||
|
||||
# List of groups the user is in; use -1 to make sure it's not empty.
|
||||
my $grouplist = join(", ", (-1, values(%{Bugzilla->user->groups})));
|
||||
|
||||
# Get all visible series
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $serieses = $dbh->selectall_arrayref("SELECT cc1.name, cc2.name, " .
|
||||
"series.name, series.series_id " .
|
||||
"FROM series " .
|
||||
"INNER JOIN series_categories AS cc1 " .
|
||||
" ON series.category = cc1.id " .
|
||||
"INNER JOIN series_categories AS cc2 " .
|
||||
" ON series.subcategory = cc2.id " .
|
||||
"LEFT JOIN category_group_map AS cgm " .
|
||||
" ON series.category = cgm.category_id " .
|
||||
" AND cgm.group_id NOT IN($grouplist) " .
|
||||
"WHERE creator = " . Bugzilla->user->id . " OR " .
|
||||
" cgm.category_id IS NULL " .
|
||||
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name'));
|
||||
foreach my $series (@$serieses) {
|
||||
my ($cat, $subcat, $name, $series_id) = @$series;
|
||||
$cats{$cat}{$subcat}{$name} = $series_id;
|
||||
}
|
||||
|
||||
return \%cats;
|
||||
}
|
||||
|
||||
sub generateDateProgression {
|
||||
my ($datefrom, $dateto) = @_;
|
||||
my @progression;
|
||||
|
||||
$dateto = $dateto || time();
|
||||
my $oneday = 60 * 60 * 24;
|
||||
|
||||
# When the from and to dates are converted by str2time(), you end up with
|
||||
# a time figure representing midnight at the beginning of that day. We
|
||||
# adjust the times by 1/3 and 2/3 of a day respectively to prevent
|
||||
# edge conditions in time2str().
|
||||
$datefrom += $oneday / 3;
|
||||
$dateto += (2 * $oneday) / 3;
|
||||
|
||||
while ($datefrom < $dateto) {
|
||||
push (@progression, time2str("%Y-%m-%d", $datefrom));
|
||||
$datefrom += $oneday;
|
||||
}
|
||||
|
||||
return \@progression;
|
||||
}
|
||||
|
||||
sub dump {
|
||||
my $self = shift;
|
||||
|
||||
# Make sure we've read in our data
|
||||
my $data = $self->data;
|
||||
|
||||
require Data::Dumper;
|
||||
print "<pre>Bugzilla::Chart object:\n";
|
||||
print Data::Dumper::Dumper($self);
|
||||
print "</pre>";
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,252 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
#
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Classification;
|
||||
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Product;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
classifications.id
|
||||
classifications.name
|
||||
classifications.description
|
||||
classifications.sortkey
|
||||
);
|
||||
|
||||
our $columns = join(", ", DB_COLUMNS);
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
return $self->_init(@_);
|
||||
}
|
||||
|
||||
sub _init {
|
||||
my $self = shift;
|
||||
my ($param) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $id = $param unless (ref $param eq 'HASH');
|
||||
my $classification;
|
||||
|
||||
if (defined $id) {
|
||||
detaint_natural($id)
|
||||
|| ThrowCodeError('param_must_be_numeric',
|
||||
{function => 'Bugzilla::Classification::_init'});
|
||||
|
||||
$classification = $dbh->selectrow_hashref(qq{
|
||||
SELECT $columns FROM classifications
|
||||
WHERE id = ?}, undef, $id);
|
||||
|
||||
} elsif (defined $param->{'name'}) {
|
||||
|
||||
trick_taint($param->{'name'});
|
||||
$classification = $dbh->selectrow_hashref(qq{
|
||||
SELECT $columns FROM classifications
|
||||
WHERE name = ?}, undef, $param->{'name'});
|
||||
} else {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'param',
|
||||
function => 'Bugzilla::Classification::_init'});
|
||||
}
|
||||
|
||||
return undef unless (defined $classification);
|
||||
|
||||
foreach my $field (keys %$classification) {
|
||||
$self->{$field} = $classification->{$field};
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub product_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'product_count'}) {
|
||||
$self->{'product_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM products
|
||||
WHERE classification_id = ?}, undef, $self->id) || 0;
|
||||
}
|
||||
return $self->{'product_count'};
|
||||
}
|
||||
|
||||
sub products {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!$self->{'products'}) {
|
||||
my $product_ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM products
|
||||
WHERE classification_id = ?
|
||||
ORDER BY name}, undef, $self->id);
|
||||
|
||||
$self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
|
||||
}
|
||||
return $self->{'products'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ####
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ####
|
||||
###############################
|
||||
|
||||
sub get_all_classifications {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM classifications ORDER BY sortkey, name});
|
||||
|
||||
my @classifications;
|
||||
foreach my $id (@$ids) {
|
||||
push @classifications, new Bugzilla::Classification($id);
|
||||
}
|
||||
return @classifications;
|
||||
}
|
||||
|
||||
sub check_classification {
|
||||
my ($class_name) = @_;
|
||||
|
||||
unless ($class_name) {
|
||||
ThrowUserError("classification_not_specified");
|
||||
}
|
||||
|
||||
my $classification =
|
||||
new Bugzilla::Classification({name => $class_name});
|
||||
|
||||
unless ($classification) {
|
||||
ThrowUserError("classification_doesnt_exist",
|
||||
{ name => $class_name });
|
||||
}
|
||||
|
||||
return $classification;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Classification - Bugzilla classification class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Classification;
|
||||
|
||||
my $classification = new Bugzilla::Classification(1);
|
||||
my $classification = new Bugzilla::Classification({name => 'Acme'});
|
||||
|
||||
my $id = $classification->id;
|
||||
my $name = $classification->name;
|
||||
my $description = $classification->description;
|
||||
my $product_count = $classification->product_count;
|
||||
my $products = $classification->products;
|
||||
|
||||
my $hash_ref = Bugzilla::Classification::get_all_classifications();
|
||||
my $classification = $hash_ref->{1};
|
||||
|
||||
my $classification =
|
||||
Bugzilla::Classification::check_classification('AcmeClass');
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Classification.pm represents a Classification object.
|
||||
|
||||
A Classification is a higher-level grouping of Products.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($param)>
|
||||
|
||||
Description: The constructor is used to load an existing
|
||||
classification by passing a classification
|
||||
id or classification name using a hash.
|
||||
|
||||
Params: $param - If you pass an integer, the integer is the
|
||||
classification_id from the database that we
|
||||
want to read in. If you pass in a hash with
|
||||
'name' key, then the value of the name key
|
||||
is the name of a classification from the DB.
|
||||
|
||||
Returns: A Bugzilla::Classification object.
|
||||
|
||||
=item C<product_count()>
|
||||
|
||||
Description: Returns the total number of products that belong to
|
||||
the classification.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer - The total of products inside the classification.
|
||||
|
||||
=item C<products>
|
||||
|
||||
Description: Returns all products of the classification.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A reference to an array of Bugzilla::Product objects.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_all_classifications()>
|
||||
|
||||
Description: Returns all classifications.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Bugzilla::Classification object list.
|
||||
|
||||
=item C<check_classification($classification_name)>
|
||||
|
||||
Description: Checks if the classification name passed in is a
|
||||
valid classification.
|
||||
|
||||
Params: $classification_name - String with a classification name.
|
||||
|
||||
Returns: Bugzilla::Classification object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,647 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Akamai Technologies <bugzilla-dev@akamai.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Component;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::FlagType;
|
||||
use Bugzilla::Series;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'components';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
name
|
||||
product_id
|
||||
initialowner
|
||||
initialqacontact
|
||||
description
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(
|
||||
name
|
||||
product
|
||||
initialowner
|
||||
description
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
initialowner
|
||||
initialqacontact
|
||||
description
|
||||
);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
product => \&_check_product,
|
||||
initialowner => \&_check_initialowner,
|
||||
initialqacontact => \&_check_initialqacontact,
|
||||
description => \&_check_description,
|
||||
initial_cc => \&_check_cc_list,
|
||||
};
|
||||
|
||||
use constant UPDATE_VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
};
|
||||
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND name = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
my $component = $class->SUPER::new(@_);
|
||||
# Add the product object as attribute only if the component exists.
|
||||
$component->{product} = $product if ($component && $product);
|
||||
return $component;
|
||||
}
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
$class->check_required_create_fields(@_);
|
||||
my $params = $class->run_create_validators(@_);
|
||||
my $cc_list = delete $params->{initial_cc};
|
||||
|
||||
my $component = $class->insert_create_data($params);
|
||||
|
||||
# We still have to fill the component_cc table.
|
||||
$component->_update_cc_list($cc_list);
|
||||
|
||||
# Create series for the new component.
|
||||
$component->_create_series();
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
return $component;
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
my $product = delete $params->{product};
|
||||
$params->{product_id} = $product->id;
|
||||
$params->{name} = $class->_check_name($params->{name}, $product);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
# Update the component_cc table if necessary.
|
||||
if (defined $self->{cc_ids}) {
|
||||
my $diff = $self->_update_cc_list($self->{cc_ids});
|
||||
$changes->{cc_list} = $diff if defined $diff;
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
if ($self->bug_count) {
|
||||
if (Bugzilla->params->{'allowbugdeletion'}) {
|
||||
require Bugzilla::Bug;
|
||||
foreach my $bug_id (@{$self->bug_ids}) {
|
||||
# Note: We allow admins to delete bugs even if they can't
|
||||
# see them, as long as they can see the product.
|
||||
my $bug = new Bugzilla::Bug($bug_id);
|
||||
$bug->remove_from_db();
|
||||
}
|
||||
} else {
|
||||
ThrowUserError('component_has_bugs', {nb => $self->bug_count});
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM flaginclusions WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM flagexclusions WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM component_cc WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM components WHERE id = ?', undef, $self->id);
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
################################
|
||||
# Validators
|
||||
################################
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name, $product) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError('component_blank_name');
|
||||
|
||||
if (length($name) > MAX_COMPONENT_SIZE) {
|
||||
ThrowUserError('component_name_too_long', {'name' => $name});
|
||||
}
|
||||
|
||||
$product = $invocant->product if (ref $invocant);
|
||||
my $component = new Bugzilla::Component({product => $product, name => $name});
|
||||
if ($component && (!ref $invocant || $component->id != $invocant->id)) {
|
||||
ThrowUserError('component_already_exists', { name => $component->name,
|
||||
product => $product });
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $description) = @_;
|
||||
|
||||
$description = trim($description);
|
||||
$description || ThrowUserError('component_blank_description');
|
||||
return $description;
|
||||
}
|
||||
|
||||
sub _check_initialowner {
|
||||
my ($invocant, $owner) = @_;
|
||||
|
||||
$owner || ThrowUserError('component_need_initialowner');
|
||||
my $owner_id = Bugzilla::User->check($owner)->id;
|
||||
return $owner_id;
|
||||
}
|
||||
|
||||
sub _check_initialqacontact {
|
||||
my ($invocant, $qa_contact) = @_;
|
||||
|
||||
my $qa_contact_id;
|
||||
if (Bugzilla->params->{'useqacontact'}) {
|
||||
$qa_contact_id = Bugzilla::User->check($qa_contact)->id if $qa_contact;
|
||||
}
|
||||
elsif (ref $invocant) {
|
||||
$qa_contact_id = $invocant->{initialqacontact};
|
||||
}
|
||||
return $qa_contact_id;
|
||||
}
|
||||
|
||||
sub _check_product {
|
||||
my ($invocant, $product) = @_;
|
||||
return Bugzilla->user->check_can_admin_product($product->name);
|
||||
}
|
||||
|
||||
sub _check_cc_list {
|
||||
my ($invocant, $cc_list) = @_;
|
||||
|
||||
my %cc_ids;
|
||||
foreach my $cc (@$cc_list) {
|
||||
my $id = login_to_id($cc, THROW_ERROR);
|
||||
$cc_ids{$id} = 1;
|
||||
}
|
||||
return [keys %cc_ids];
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub _update_cc_list {
|
||||
my ($self, $cc_list) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $old_cc_list =
|
||||
$dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
||||
WHERE component_id = ?', undef, $self->id);
|
||||
|
||||
my ($removed, $added) = diff_arrays($old_cc_list, $cc_list);
|
||||
my $diff;
|
||||
if (scalar @$removed || scalar @$added) {
|
||||
$diff = [join(', ', @$removed), join(', ', @$added)];
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM component_cc WHERE component_id = ?', undef, $self->id);
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO component_cc
|
||||
(user_id, component_id) VALUES (?, ?)');
|
||||
$sth->execute($_, $self->id) foreach (@$cc_list);
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
sub _create_series {
|
||||
my $self = shift;
|
||||
|
||||
# Insert default charting queries for this product.
|
||||
# If they aren't using charting, this won't do any harm.
|
||||
my $prodcomp = "&product=" . url_quote($self->product->name) .
|
||||
"&component=" . url_quote($self->name);
|
||||
|
||||
my $open_query = 'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.' .
|
||||
$prodcomp;
|
||||
my $nonopen_query = 'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.' .
|
||||
$prodcomp;
|
||||
|
||||
my @series = ([get_text('series_all_open'), $open_query],
|
||||
[get_text('series_all_closed'), $nonopen_query]);
|
||||
|
||||
foreach my $sdata (@series) {
|
||||
my $series = new Bugzilla::Series(undef, $self->product->name,
|
||||
$self->name, $sdata->[0],
|
||||
Bugzilla->user->id, 1, $sdata->[1], 1);
|
||||
$series->writeToDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_default_assignee {
|
||||
my ($self, $owner) = @_;
|
||||
|
||||
$self->set('initialowner', $owner);
|
||||
# Reset the default owner object.
|
||||
delete $self->{default_assignee};
|
||||
}
|
||||
sub set_default_qa_contact {
|
||||
my ($self, $qa_contact) = @_;
|
||||
|
||||
$self->set('initialqacontact', $qa_contact);
|
||||
# Reset the default QA contact object.
|
||||
delete $self->{default_qa_contact};
|
||||
}
|
||||
sub set_cc_list {
|
||||
my ($self, $cc_list) = @_;
|
||||
|
||||
$self->{cc_ids} = $self->_check_cc_list($cc_list);
|
||||
# Reset the list of CC user objects.
|
||||
delete $self->{initial_cc};
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE component_id = ?}, undef, $self->id) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub bug_ids {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bugs_ids'}) {
|
||||
$self->{'bugs_ids'} = $dbh->selectcol_arrayref(q{
|
||||
SELECT bug_id FROM bugs
|
||||
WHERE component_id = ?}, undef, $self->id);
|
||||
}
|
||||
return $self->{'bugs_ids'};
|
||||
}
|
||||
|
||||
sub default_assignee {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'default_assignee'}) {
|
||||
$self->{'default_assignee'} =
|
||||
new Bugzilla::User($self->{'initialowner'});
|
||||
}
|
||||
return $self->{'default_assignee'};
|
||||
}
|
||||
|
||||
sub default_qa_contact {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'default_qa_contact'}) {
|
||||
$self->{'default_qa_contact'} =
|
||||
new Bugzilla::User($self->{'initialqacontact'});
|
||||
}
|
||||
return $self->{'default_qa_contact'};
|
||||
}
|
||||
|
||||
sub flag_types {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_types'}) {
|
||||
$self->{'flag_types'} = {};
|
||||
$self->{'flag_types'}->{'bug'} =
|
||||
Bugzilla::FlagType::match({ 'target_type' => 'bug',
|
||||
'product_id' => $self->product_id,
|
||||
'component_id' => $self->id });
|
||||
|
||||
$self->{'flag_types'}->{'attachment'} =
|
||||
Bugzilla::FlagType::match({ 'target_type' => 'attachment',
|
||||
'product_id' => $self->product_id,
|
||||
'component_id' => $self->id });
|
||||
}
|
||||
return $self->{'flag_types'};
|
||||
}
|
||||
|
||||
sub initial_cc {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'initial_cc'}) {
|
||||
# If set_cc_list() has been called but data are not yet written
|
||||
# into the DB, we want the new values defined by it.
|
||||
my $cc_ids = $self->{cc_ids}
|
||||
|| $dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
||||
WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
|
||||
$self->{'initial_cc'} = Bugzilla::User->new_from_list($cc_ids);
|
||||
}
|
||||
return $self->{'initial_cc'};
|
||||
}
|
||||
|
||||
sub product {
|
||||
my $self = shift;
|
||||
if (!defined $self->{'product'}) {
|
||||
require Bugzilla::Product; # We cannot |use| it.
|
||||
$self->{'product'} = new Bugzilla::Product($self->product_id);
|
||||
}
|
||||
return $self->{'product'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ####
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ####
|
||||
###############################
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Component - Bugzilla product component class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Component;
|
||||
|
||||
my $component = new Bugzilla::Component($comp_id);
|
||||
my $component = new Bugzilla::Component({ product => $product, name => $name });
|
||||
|
||||
my $bug_count = $component->bug_count();
|
||||
my $bug_ids = $component->bug_ids();
|
||||
my $id = $component->id;
|
||||
my $name = $component->name;
|
||||
my $description = $component->description;
|
||||
my $product_id = $component->product_id;
|
||||
my $default_assignee = $component->default_assignee;
|
||||
my $default_qa_contact = $component->default_qa_contact;
|
||||
my $initial_cc = $component->initial_cc;
|
||||
my $product = $component->product;
|
||||
my $bug_flag_types = $component->flag_types->{'bug'};
|
||||
my $attach_flag_types = $component->flag_types->{'attachment'};
|
||||
|
||||
my $component = Bugzilla::Component->check({ product => $product, name => $name });
|
||||
|
||||
my $component =
|
||||
Bugzilla::Component->create({ name => $name,
|
||||
product => $product,
|
||||
initialowner => $user_login1,
|
||||
initialqacontact => $user_login2,
|
||||
description => $description});
|
||||
|
||||
$component->set_name($new_name);
|
||||
$component->set_description($new_description);
|
||||
$component->set_default_assignee($new_login_name);
|
||||
$component->set_default_qa_contact($new_login_name);
|
||||
$component->set_cc_list(\@new_login_names);
|
||||
$component->update();
|
||||
|
||||
$component->remove_from_db;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Component.pm represents a Product Component object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($param)>
|
||||
|
||||
Description: The constructor is used to load an existing component
|
||||
by passing a component ID or a hash with the product
|
||||
object the component belongs to and the component name.
|
||||
|
||||
Params: $param - If you pass an integer, the integer is the
|
||||
component ID from the database that we want to
|
||||
read in. If you pass in a hash with the 'name'
|
||||
and 'product' keys, then the value of the name
|
||||
key is the name of a component being in the given
|
||||
product.
|
||||
|
||||
Returns: A Bugzilla::Component object.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<bugs_ids()>
|
||||
|
||||
Description: Returns all bug IDs that belong to the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A reference to an array of bug IDs.
|
||||
|
||||
=item C<default_assignee()>
|
||||
|
||||
Description: Returns a user object that represents the default assignee for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::User object.
|
||||
|
||||
=item C<default_qa_contact()>
|
||||
|
||||
Description: Returns a user object that represents the default QA contact for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::User object.
|
||||
|
||||
=item C<initial_cc>
|
||||
|
||||
Description: Returns a list of user objects representing users being
|
||||
in the initial CC list.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An arrayref of L<Bugzilla::User> objects.
|
||||
|
||||
=item C<flag_types()>
|
||||
|
||||
Description: Returns all bug and attachment flagtypes available for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Two references to an array of flagtype objects.
|
||||
|
||||
=item C<product()>
|
||||
|
||||
Description: Returns the product the component belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::Product object.
|
||||
|
||||
=item C<set_name($new_name)>
|
||||
|
||||
Description: Changes the name of the component.
|
||||
|
||||
Params: $new_name - new name of the component (string). This name
|
||||
must be unique within the product.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_description($new_desc)>
|
||||
|
||||
Description: Changes the description of the component.
|
||||
|
||||
Params: $new_desc - new description of the component (string).
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_default_assignee($new_assignee)>
|
||||
|
||||
Description: Changes the default assignee of the component.
|
||||
|
||||
Params: $new_owner - login name of the new default assignee of
|
||||
the component (string). This user account
|
||||
must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_default_qa_contact($new_qa_contact)>
|
||||
|
||||
Description: Changes the default QA contact of the component.
|
||||
|
||||
Params: $new_qa_contact - login name of the new QA contact of
|
||||
the component (string). This user
|
||||
account must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_cc_list(\@cc_list)>
|
||||
|
||||
Description: Changes the list of users being in the CC list by default.
|
||||
|
||||
Params: \@cc_list - list of login names (string). All the user
|
||||
accounts must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<update()>
|
||||
|
||||
Description: Write changes made to the component into the DB.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hashref with changes made to the component object.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Deletes the current component from the DB. The object itself
|
||||
is not destroyed.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<create(\%params)>
|
||||
|
||||
Description: Create a new component for the given product.
|
||||
|
||||
Params: The hashref must have the following keys:
|
||||
name - name of the new component (string). This name
|
||||
must be unique within the product.
|
||||
product - a Bugzilla::Product object to which
|
||||
the Component is being added.
|
||||
description - description of the new component (string).
|
||||
initialowner - login name of the default assignee (string).
|
||||
The following keys are optional:
|
||||
initiaqacontact - login name of the default QA contact (string),
|
||||
or an empty string to clear it.
|
||||
initial_cc - an arrayref of login names to add to the
|
||||
CC list by default.
|
||||
|
||||
Returns: A Bugzilla::Component object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,404 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jake <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Config;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
use Bugzilla::Constants;
|
||||
use Data::Dumper;
|
||||
use File::Temp;
|
||||
|
||||
# Don't export localvars by default - people should have to explicitly
|
||||
# ask for it, as a (probably futile) attempt to stop code using it
|
||||
# when it shouldn't
|
||||
%Bugzilla::Config::EXPORT_TAGS =
|
||||
(
|
||||
admin => [qw(update_params SetParam write_params)],
|
||||
);
|
||||
Exporter::export_ok_tags('admin');
|
||||
|
||||
use vars qw(@param_list);
|
||||
|
||||
# INITIALISATION CODE
|
||||
# Perl throws a warning if we use bz_locations() directly after do.
|
||||
our %params;
|
||||
# Load in the param definitions
|
||||
sub _load_params {
|
||||
my $panels = param_panels();
|
||||
foreach my $panel (keys %$panels) {
|
||||
my $module = $panels->{$panel};
|
||||
eval("require $module") || die $@;
|
||||
my @new_param_list = "$module"->get_param_list();
|
||||
foreach my $item (@new_param_list) {
|
||||
$params{$item->{'name'}} = $item;
|
||||
}
|
||||
push(@param_list, @new_param_list);
|
||||
}
|
||||
}
|
||||
# END INIT CODE
|
||||
|
||||
# Subroutines go here
|
||||
|
||||
sub param_panels {
|
||||
my $param_panels = {};
|
||||
my $libpath = bz_locations()->{'libpath'};
|
||||
foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
|
||||
$item =~ m#/([^/]+)\.pm$#;
|
||||
my $module = $1;
|
||||
$param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common';
|
||||
}
|
||||
# Now check for any hooked params
|
||||
Bugzilla::Hook::process('config', { config => $param_panels });
|
||||
return $param_panels;
|
||||
}
|
||||
|
||||
sub SetParam {
|
||||
my ($name, $value) = @_;
|
||||
|
||||
_load_params unless %params;
|
||||
die "Unknown param $name" unless (exists $params{$name});
|
||||
|
||||
my $entry = $params{$name};
|
||||
|
||||
# sanity check the value
|
||||
|
||||
# XXX - This runs the checks. Which would be good, except that
|
||||
# check_shadowdb creates the database as a side effect, and so the
|
||||
# checker fails the second time around...
|
||||
if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
|
||||
my $err = $entry->{'checker'}->($value, $entry);
|
||||
die "Param $name is not valid: $err" unless $err eq '';
|
||||
}
|
||||
|
||||
Bugzilla->params->{$name} = $value;
|
||||
}
|
||||
|
||||
sub update_params {
|
||||
my ($params) = @_;
|
||||
my $answer = Bugzilla->installation_answers;
|
||||
|
||||
my $param = read_param_file();
|
||||
|
||||
# If we didn't return any param values, then this is a new installation.
|
||||
my $new_install = !(keys %$param);
|
||||
|
||||
# --- UPDATE OLD PARAMS ---
|
||||
|
||||
# Old Bugzilla versions stored the version number in the params file
|
||||
# We don't want it, so get rid of it
|
||||
delete $param->{'version'};
|
||||
|
||||
# Change from usebrowserinfo to defaultplatform/defaultopsys combo
|
||||
if (exists $param->{'usebrowserinfo'}) {
|
||||
if (!$param->{'usebrowserinfo'}) {
|
||||
if (!exists $param->{'defaultplatform'}) {
|
||||
$param->{'defaultplatform'} = 'Other';
|
||||
}
|
||||
if (!exists $param->{'defaultopsys'}) {
|
||||
$param->{'defaultopsys'} = 'Other';
|
||||
}
|
||||
}
|
||||
delete $param->{'usebrowserinfo'};
|
||||
}
|
||||
|
||||
# Change from a boolean for quips to multi-state
|
||||
if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
|
||||
$param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
|
||||
delete $param->{'usequip'};
|
||||
}
|
||||
|
||||
# Change from old product groups to controls for group_control_map
|
||||
# 2002-10-14 bug 147275 bugreport@peshkin.net
|
||||
if (exists $param->{'usebuggroups'} &&
|
||||
!exists $param->{'makeproductgroups'})
|
||||
{
|
||||
$param->{'makeproductgroups'} = $param->{'usebuggroups'};
|
||||
}
|
||||
if (exists $param->{'usebuggroupsentry'}
|
||||
&& !exists $param->{'useentrygroupdefault'}) {
|
||||
$param->{'useentrygroupdefault'} = $param->{'usebuggroupsentry'};
|
||||
}
|
||||
|
||||
# Modularise auth code
|
||||
if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
|
||||
$param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
|
||||
}
|
||||
|
||||
# set verify method to whatever loginmethod was
|
||||
if (exists $param->{'loginmethod'}
|
||||
&& !exists $param->{'user_verify_class'})
|
||||
{
|
||||
$param->{'user_verify_class'} = $param->{'loginmethod'};
|
||||
delete $param->{'loginmethod'};
|
||||
}
|
||||
|
||||
# Remove quip-display control from parameters
|
||||
# and give it to users via User Settings (Bug 41972)
|
||||
if ( exists $param->{'enablequips'}
|
||||
&& !exists $param->{'quip_list_entry_control'})
|
||||
{
|
||||
my $new_value;
|
||||
($param->{'enablequips'} eq 'on') && do {$new_value = 'open';};
|
||||
($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
|
||||
($param->{'enablequips'} eq 'frozen') && do {$new_value = 'closed';};
|
||||
($param->{'enablequips'} eq 'off') && do {$new_value = 'closed';};
|
||||
$param->{'quip_list_entry_control'} = $new_value;
|
||||
delete $param->{'enablequips'};
|
||||
}
|
||||
|
||||
# Old mail_delivery_method choices contained no uppercase characters
|
||||
if (exists $param->{'mail_delivery_method'}
|
||||
&& $param->{'mail_delivery_method'} !~ /[A-Z]/) {
|
||||
my $method = $param->{'mail_delivery_method'};
|
||||
my %translation = (
|
||||
'sendmail' => 'Sendmail',
|
||||
'smtp' => 'SMTP',
|
||||
'qmail' => 'Qmail',
|
||||
'testfile' => 'Test',
|
||||
'none' => 'None');
|
||||
$param->{'mail_delivery_method'} = $translation{$method};
|
||||
}
|
||||
|
||||
# --- DEFAULTS FOR NEW PARAMS ---
|
||||
|
||||
_load_params unless %params;
|
||||
foreach my $item (@param_list) {
|
||||
my $name = $item->{'name'};
|
||||
unless (exists $param->{$name}) {
|
||||
print "New parameter: $name\n" unless $new_install;
|
||||
$param->{$name} = $answer->{$name} || $item->{'default'};
|
||||
}
|
||||
}
|
||||
|
||||
$param->{'utf8'} = 1 if $new_install;
|
||||
|
||||
# --- REMOVE OLD PARAMS ---
|
||||
|
||||
my %oldparams;
|
||||
# Remove any old params, put them in old-params.txt
|
||||
foreach my $item (keys %$param) {
|
||||
if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
|
||||
$oldparams{$item} = $param->{$item};
|
||||
delete $param->{$item};
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(keys %oldparams)) {
|
||||
my $op_file = new IO::File('old-params.txt', '>>', 0600)
|
||||
|| die "old-params.txt: $!";
|
||||
|
||||
print "The following parameters are no longer used in Bugzilla,",
|
||||
" and so have been\nmoved from your parameters file into",
|
||||
" old-params.txt:\n";
|
||||
|
||||
local $Data::Dumper::Terse = 1;
|
||||
local $Data::Dumper::Indent = 0;
|
||||
|
||||
my $comma = "";
|
||||
foreach my $item (keys %oldparams) {
|
||||
print $op_file "\n\n$item:\n" . Data::Dumper->Dump([$oldparams{$item}]) . "\n";
|
||||
print "${comma}$item";
|
||||
$comma = ", ";
|
||||
}
|
||||
print "\n";
|
||||
$op_file->close;
|
||||
}
|
||||
|
||||
if (ON_WINDOWS && !-e SENDMAIL_EXE
|
||||
&& $param->{'mail_delivery_method'} eq 'Sendmail')
|
||||
{
|
||||
my $smtp = $answer->{'SMTP_SERVER'};
|
||||
if (!$smtp) {
|
||||
print "\nBugzilla requires an SMTP server to function on",
|
||||
" Windows.\nPlease enter your SMTP server's hostname: ";
|
||||
$smtp = <STDIN>;
|
||||
chomp $smtp;
|
||||
if ($smtp) {
|
||||
$param->{'smtpserver'} = $smtp;
|
||||
}
|
||||
else {
|
||||
print "\nWarning: No SMTP Server provided, defaulting to",
|
||||
" localhost\n";
|
||||
}
|
||||
}
|
||||
|
||||
$param->{'mail_delivery_method'} = 'SMTP';
|
||||
}
|
||||
|
||||
write_params($param);
|
||||
|
||||
# Return deleted params and values so that checksetup.pl has a chance
|
||||
# to convert old params to new data.
|
||||
return %oldparams;
|
||||
}
|
||||
|
||||
sub write_params {
|
||||
my ($param_data) = @_;
|
||||
$param_data ||= Bugzilla->params;
|
||||
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
my $param_file = "$datadir/params";
|
||||
|
||||
local $Data::Dumper::Sortkeys = 1;
|
||||
|
||||
my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
|
||||
DIR => $datadir );
|
||||
|
||||
print $fh (Data::Dumper->Dump([$param_data], ['*param']))
|
||||
|| die "Can't write param file: $!";
|
||||
|
||||
close $fh;
|
||||
|
||||
rename $tmpname, $param_file
|
||||
|| die "Can't rename $tmpname to $param_file: $!";
|
||||
|
||||
ChmodDataFile($param_file, 0666);
|
||||
|
||||
# And now we have to reset the params cache so that Bugzilla will re-read
|
||||
# them.
|
||||
delete Bugzilla->request_cache->{params};
|
||||
}
|
||||
|
||||
# Some files in the data directory must be world readable if and only if
|
||||
# we don't have a webserver group. Call this function to do this.
|
||||
# This will become a private function once all the datafile handling stuff
|
||||
# moves into this package
|
||||
|
||||
# This sub is not perldoc'd for that reason - noone should know about it
|
||||
sub ChmodDataFile {
|
||||
my ($file, $mask) = @_;
|
||||
my $perm = 0770;
|
||||
if ((stat(bz_locations()->{'datadir'}))[2] & 0002) {
|
||||
$perm = 0777;
|
||||
}
|
||||
$perm = $perm & $mask;
|
||||
chmod $perm,$file;
|
||||
}
|
||||
|
||||
sub read_param_file {
|
||||
my %params;
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-e "$datadir/params") {
|
||||
# Note that checksetup.pl sets file permissions on '$datadir/params'
|
||||
|
||||
# Using Safe mode is _not_ a guarantee of safety if someone does
|
||||
# manage to write to the file. However, it won't hurt...
|
||||
# See bug 165144 for not needing to eval this at all
|
||||
my $s = new Safe;
|
||||
|
||||
$s->rdo("$datadir/params");
|
||||
die "Error reading $datadir/params: $!" if $!;
|
||||
die "Error evaluating $datadir/params: $@" if $@;
|
||||
|
||||
# Now read the param back out from the sandbox
|
||||
%params = %{$s->varglob('param')};
|
||||
}
|
||||
elsif ($ENV{'SERVER_SOFTWARE'}) {
|
||||
# We're in a CGI, but the params file doesn't exist. We can't
|
||||
# Template Toolkit, or even install_string, since checksetup
|
||||
# might not have thrown an error. Bugzilla::CGI->new
|
||||
# hasn't even been called yet, so we manually use CGI::Carp here
|
||||
# so that the user sees the error.
|
||||
require CGI::Carp;
|
||||
CGI::Carp->import('fatalsToBrowser');
|
||||
die "The $datadir/params file does not exist."
|
||||
. ' You probably need to run checksetup.pl.',
|
||||
}
|
||||
return \%params;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Config - Configuration parameters for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# Administration functions
|
||||
use Bugzilla::Config qw(:admin);
|
||||
|
||||
update_params();
|
||||
SetParam($param, $value);
|
||||
write_params();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package contains ways to access Bugzilla configuration parameters.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=head2 Parameters
|
||||
|
||||
Parameters can be set, retrieved, and updated.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<SetParam($name, $value)>
|
||||
|
||||
Sets the param named $name to $value. Values are checked using the checker
|
||||
function for the given param if one exists.
|
||||
|
||||
=item C<update_params()>
|
||||
|
||||
Updates the parameters, by transitioning old params to new formats, setting
|
||||
defaults for new params, and removing obsolete ones. Used by F<checksetup.pl>
|
||||
in the process of an installation or upgrade.
|
||||
|
||||
Prints out information about what it's doing, if it makes any changes.
|
||||
|
||||
May prompt the user for input, if certain required parameters are not
|
||||
specified.
|
||||
|
||||
=item C<write_params($params)>
|
||||
|
||||
Description: Writes the parameters to disk.
|
||||
|
||||
Params: C<$params> (optional) - A hashref to write to the disk
|
||||
instead of C<Bugzilla->params>. Used only by
|
||||
C<update_params>.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<read_param_file()>
|
||||
|
||||
Description: Most callers should never need this. This is used
|
||||
by C<Bugzilla->params> to directly read C<$datadir/params>
|
||||
and load it into memory. Use C<Bugzilla->params> instead.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: A hashref containing the current params in C<$datadir/params>.
|
||||
|
||||
=back
|
||||
@@ -1,69 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Admin;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Admin::sortkey = "01";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'allowbugdeletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'allowemailchange',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'allowuserdeletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'supportwatchers',
|
||||
type => 'b',
|
||||
default => 0
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,91 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Attachment;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Attachment::sortkey = "025";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'allow_attachment_deletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'allow_attach_url',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'maxpatchsize',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxattachmentsize',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
# The maximum size (in bytes) for patches and non-patch attachments.
|
||||
# The default limit is 1000KB, which is 24KB less than mysql's default
|
||||
# maximum packet size (which determines how much data can be sent in a
|
||||
# single mysql packet and thus how much data can be inserted into the
|
||||
# database) to provide breathing space for the data in other fields of
|
||||
# the attachment record as well as any mysql packet overhead (I don't
|
||||
# know of any, but I suspect there may be some.)
|
||||
|
||||
{
|
||||
name => 'maxlocalattachment',
|
||||
type => 't',
|
||||
default => '0',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'convert_uncompressed_images',
|
||||
type => 'b',
|
||||
default => 0,
|
||||
checker => \&check_image_converter
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,135 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Auth;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Auth::sortkey = "02";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'auth_env_id',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'auth_env_email',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'auth_env_realname',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
# XXX in the future:
|
||||
#
|
||||
# user_verify_class and user_info_class should have choices gathered from
|
||||
# whatever sits in their respective directories
|
||||
#
|
||||
# rather than comma-separated lists, these two should eventually become
|
||||
# arrays, but that requires alterations to editparams first
|
||||
|
||||
{
|
||||
name => 'user_info_class',
|
||||
type => 's',
|
||||
choices => [ 'CGI', 'Env', 'Env,CGI' ],
|
||||
default => 'CGI',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'user_verify_class',
|
||||
type => 'o',
|
||||
choices => [ 'DB', 'RADIUS', 'LDAP' ],
|
||||
default => 'DB',
|
||||
checker => \&check_user_verify_class
|
||||
},
|
||||
|
||||
{
|
||||
name => 'rememberlogin',
|
||||
type => 's',
|
||||
choices => ['on', 'defaulton', 'defaultoff', 'off'],
|
||||
default => 'on',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'loginnetmask',
|
||||
type => 't',
|
||||
default => '0',
|
||||
checker => \&check_netmask
|
||||
},
|
||||
|
||||
{
|
||||
name => 'requirelogin',
|
||||
type => 'b',
|
||||
default => '0'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailregexp',
|
||||
type => 't',
|
||||
default => q:^[\\w\\.\\+\\-=]+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
|
||||
checker => \&check_regexp
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailregexpdesc',
|
||||
type => 'l',
|
||||
default => 'A legal address must contain exactly one \'@\', and at least ' .
|
||||
'one \'.\' after the @.'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailsuffix',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'createemailregexp',
|
||||
type => 't',
|
||||
default => q:.*:,
|
||||
checker => \&check_regexp
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,115 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugChange;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Status;
|
||||
|
||||
$Bugzilla::Config::BugChange::sortkey = "03";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
# Hardcoded bug statuses which existed before Bugzilla 3.1.
|
||||
my @closed_bug_statuses = ('RESOLVED', 'VERIFIED', 'CLOSED');
|
||||
|
||||
# If we are upgrading from 3.0 or older, bug statuses are not customisable
|
||||
# and bug_status.is_open is not yet defined (hence the eval), so we use
|
||||
# the bug statuses above as they are still hardcoded.
|
||||
eval {
|
||||
my @current_closed_states = map {$_->name} closed_bug_statuses();
|
||||
# If no closed state was found, use the default list above.
|
||||
@closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
|
||||
};
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'duplicate_or_move_bug_status',
|
||||
type => 's',
|
||||
choices => \@closed_bug_statuses,
|
||||
default => $closed_bug_statuses[0],
|
||||
checker => \&check_bug_status
|
||||
},
|
||||
|
||||
{
|
||||
name => 'letsubmitterchoosepriority',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'letsubmitterchoosemilestone',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'musthavemilestoneonaccept',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonclearresolution',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonchange_resolution',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonreassignbycomponent',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonduplicate',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'noresolveonopenblockers',
|
||||
type => 'b',
|
||||
default => 0,
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,126 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugFields;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Field;
|
||||
|
||||
$Bugzilla::Config::BugFields::sortkey = "04";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
my @legal_priorities = @{get_legal_field_values('priority')};
|
||||
my @legal_severities = @{get_legal_field_values('bug_severity')};
|
||||
my @legal_platforms = @{get_legal_field_values('rep_platform')};
|
||||
my @legal_OS = @{get_legal_field_values('op_sys')};
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'useclassification',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'showallproducts',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usetargetmilestone',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'useqacontact',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usestatuswhiteboard',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usevotes',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usebugaliases',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultpriority',
|
||||
type => 's',
|
||||
choices => \@legal_priorities,
|
||||
default => $legal_priorities[-1],
|
||||
checker => \&check_priority
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultseverity',
|
||||
type => 's',
|
||||
choices => \@legal_severities,
|
||||
default => $legal_severities[-1],
|
||||
checker => \&check_severity
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultplatform',
|
||||
type => 's',
|
||||
choices => ['', @legal_platforms],
|
||||
default => '',
|
||||
checker => \&check_platform
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultopsys',
|
||||
type => 's',
|
||||
choices => ['', @legal_OS],
|
||||
default => '',
|
||||
checker => \&check_opsys
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,93 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugMove;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::BugMove::sortkey = "05";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'move-enabled',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-button-text',
|
||||
type => 't',
|
||||
default => 'Move To Bugscape'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-to-url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-to-address',
|
||||
type => 't',
|
||||
default => 'bugzilla-import'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-from-address',
|
||||
type => 't',
|
||||
default => 'bugzilla-admin'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'movers',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-default-product',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-default-component',
|
||||
type => 't',
|
||||
default => ''
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,449 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Common;
|
||||
|
||||
use strict;
|
||||
|
||||
use Socket;
|
||||
use Time::Zone;
|
||||
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Status;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Config::Common::EXPORT =
|
||||
qw(check_multi check_numeric check_regexp check_url check_group
|
||||
check_sslbase check_priority check_severity check_platform
|
||||
check_opsys check_shadowdb check_urlbase check_webdotbase
|
||||
check_netmask check_user_verify_class check_image_converter
|
||||
check_mail_delivery_method check_notification check_timezone check_utf8
|
||||
check_bug_status check_smtp_auth
|
||||
);
|
||||
|
||||
# Checking functions for the various values
|
||||
|
||||
sub check_multi {
|
||||
my ($value, $param) = (@_);
|
||||
|
||||
if ($param->{'type'} eq "s") {
|
||||
unless (scalar(grep {$_ eq $value} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$value' for single-select list param '$param->{'name'}'";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
|
||||
foreach my $chkParam (split(',', $value)) {
|
||||
unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return "Invalid param type '$param->{'type'}' for check_multi(); " .
|
||||
"contact your Bugzilla administrator";
|
||||
}
|
||||
}
|
||||
|
||||
sub check_numeric {
|
||||
my ($value) = (@_);
|
||||
if ($value !~ /^[0-9]+$/) {
|
||||
return "must be a numeric value";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_regexp {
|
||||
my ($value) = (@_);
|
||||
eval { qr/$value/ };
|
||||
return $@;
|
||||
}
|
||||
|
||||
sub check_sslbase {
|
||||
my $url = shift;
|
||||
if ($url ne '') {
|
||||
if ($url !~ m#^https://([^/]+).*/$#) {
|
||||
return "must be a legal URL, that starts with https and ends with a slash.";
|
||||
}
|
||||
my $host = $1;
|
||||
# Fall back to port 443 if for some reason getservbyname() fails.
|
||||
my $port = getservbyname('https', 'tcp') || 443;
|
||||
if ($host =~ /^(.+):(\d+)$/) {
|
||||
$host = $1;
|
||||
$port = $2;
|
||||
}
|
||||
local *SOCK;
|
||||
my $proto = getprotobyname('tcp');
|
||||
socket(SOCK, PF_INET, SOCK_STREAM, $proto);
|
||||
my $iaddr = inet_aton($host) || return "The host $host cannot be resolved";
|
||||
my $sin = sockaddr_in($port, $iaddr);
|
||||
if (!connect(SOCK, $sin)) {
|
||||
return "Failed to connect to $host:$port; unable to enable SSL";
|
||||
}
|
||||
close(SOCK);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_utf8 {
|
||||
my $utf8 = shift;
|
||||
# You cannot turn off the UTF-8 parameter if you've already converted
|
||||
# your tables to utf-8.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
if ($dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8) {
|
||||
return "You cannot disable UTF-8 support, because your MySQL database"
|
||||
. " is encoded in UTF-8";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_priority {
|
||||
my ($value) = (@_);
|
||||
my $legal_priorities = get_legal_field_values('priority');
|
||||
if (lsearch($legal_priorities, $value) < 0) {
|
||||
return "Must be a legal priority value: one of " .
|
||||
join(", ", @$legal_priorities);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_severity {
|
||||
my ($value) = (@_);
|
||||
my $legal_severities = get_legal_field_values('bug_severity');
|
||||
if (lsearch($legal_severities, $value) < 0) {
|
||||
return "Must be a legal severity value: one of " .
|
||||
join(", ", @$legal_severities);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_platform {
|
||||
my ($value) = (@_);
|
||||
my $legal_platforms = get_legal_field_values('rep_platform');
|
||||
if (lsearch(['', @$legal_platforms], $value) < 0) {
|
||||
return "Must be empty or a legal platform value: one of " .
|
||||
join(", ", @$legal_platforms);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_opsys {
|
||||
my ($value) = (@_);
|
||||
my $legal_OS = get_legal_field_values('op_sys');
|
||||
if (lsearch(['', @$legal_OS], $value) < 0) {
|
||||
return "Must be empty or a legal operating system value: one of " .
|
||||
join(", ", @$legal_OS);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_bug_status {
|
||||
my $bug_status = shift;
|
||||
my @closed_bug_statuses = map {$_->name} closed_bug_statuses();
|
||||
if (lsearch(\@closed_bug_statuses, $bug_status) < 0) {
|
||||
return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_group {
|
||||
my $group_name = shift;
|
||||
return "" unless $group_name;
|
||||
my $group = new Bugzilla::Group({'name' => $group_name});
|
||||
unless (defined $group) {
|
||||
return "Must be an existing group name";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_shadowdb {
|
||||
my ($value) = (@_);
|
||||
$value = trim($value);
|
||||
if ($value eq "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!Bugzilla->params->{'shadowdbhost'}) {
|
||||
return "You need to specify a host when using a shadow database";
|
||||
}
|
||||
|
||||
# Can't test existence of this because ConnectToDatabase uses the param,
|
||||
# but we can't set this before testing....
|
||||
# This can really only be fixed after we can use the DBI more openly
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_urlbase {
|
||||
my ($url) = (@_);
|
||||
if ($url && $url !~ m:^http.*/$:) {
|
||||
return "must be a legal URL, that starts with http and ends with a slash.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_url {
|
||||
my ($url) = (@_);
|
||||
return '' if $url eq ''; # Allow empty URLs
|
||||
if ($url !~ m:/$:) {
|
||||
return 'must be a legal URL, absolute or relative, ending with a slash.';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
sub check_webdotbase {
|
||||
my ($value) = (@_);
|
||||
$value = trim($value);
|
||||
if ($value eq "") {
|
||||
return "";
|
||||
}
|
||||
if($value !~ /^https?:/) {
|
||||
if(! -x $value) {
|
||||
return "The file path \"$value\" is not a valid executable. Please specify the complete file path to 'dot' if you intend to generate graphs locally.";
|
||||
}
|
||||
# Check .htaccess allows access to generated images
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
if(-e "$webdotdir/.htaccess") {
|
||||
open HTACCESS, "$webdotdir/.htaccess";
|
||||
if(! grep(/ \\\.png\$/,<HTACCESS>)) {
|
||||
return "Dependency graph images are not accessible.\nAssuming that you have not modified the file, delete $webdotdir/.htaccess and re-run checksetup.pl to rectify.\n";
|
||||
}
|
||||
close HTACCESS;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_netmask {
|
||||
my ($mask) = @_;
|
||||
my $res = check_numeric($mask);
|
||||
return $res if $res;
|
||||
if ($mask < 0 || $mask > 32) {
|
||||
return "an IPv4 netmask must be between 0 and 32 bits";
|
||||
}
|
||||
# Note that if we changed the netmask from anything apart from 32, then
|
||||
# existing logincookies which aren't for a single IP won't work
|
||||
# any more. We can't know which ones they are, though, so they'll just
|
||||
# take space until they're periodically cleared, later.
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_user_verify_class {
|
||||
# doeditparams traverses the list of params, and for each one it checks,
|
||||
# then updates. This means that if one param checker wants to look at
|
||||
# other params, it must be below that other one. So you can't have two
|
||||
# params mutually dependent on each other.
|
||||
# This means that if someone clears the LDAP config params after setting
|
||||
# the login method as LDAP, we won't notice, but all logins will fail.
|
||||
# So don't do that.
|
||||
|
||||
my ($list, $entry) = @_;
|
||||
$list || return 'You need to specify at least one authentication mechanism';
|
||||
for my $class (split /,\s*/, $list) {
|
||||
my $res = check_multi($class, $entry);
|
||||
return $res if $res;
|
||||
if ($class eq 'DB') {
|
||||
# No params
|
||||
}
|
||||
elsif ($class eq 'RADIUS') {
|
||||
eval "require Authen::Radius";
|
||||
return "Error requiring Authen::Radius: '$@'" if $@;
|
||||
return "RADIUS servername (RADIUS_server) is missing" unless Bugzilla->params->{"RADIUS_server"};
|
||||
return "RADIUS_secret is empty" unless Bugzilla->params->{"RADIUS_secret"};
|
||||
}
|
||||
elsif ($class eq 'LDAP') {
|
||||
eval "require Net::LDAP";
|
||||
return "Error requiring Net::LDAP: '$@'" if $@;
|
||||
return "LDAP servername (LDAPserver) is missing" unless Bugzilla->params->{"LDAPserver"};
|
||||
return "LDAPBaseDN is empty" unless Bugzilla->params->{"LDAPBaseDN"};
|
||||
}
|
||||
else {
|
||||
return "Unknown user_verify_class '$class' in check_user_verify_class";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_image_converter {
|
||||
my ($value, $hash) = @_;
|
||||
if ($value == 1){
|
||||
eval "require Image::Magick";
|
||||
return "Error requiring Image::Magick: '$@'" if $@;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_mail_delivery_method {
|
||||
my $check = check_multi(@_);
|
||||
return $check if $check;
|
||||
my $mailer = shift;
|
||||
if ($mailer eq 'sendmail' && $^O =~ /MSWin32/i) {
|
||||
# look for sendmail.exe
|
||||
return "Failed to locate " . SENDMAIL_EXE
|
||||
unless -e SENDMAIL_EXE;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_notification {
|
||||
my $option = shift;
|
||||
my @current_version =
|
||||
(BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
if ($current_version[1] % 2 && $option eq 'stable_branch_release') {
|
||||
return "You are currently running a development snapshot, and so your " .
|
||||
"installation is not based on a branch. If you want to be notified " .
|
||||
"about the next stable release, you should select " .
|
||||
"'latest_stable_release' instead";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_timezone {
|
||||
my $tz = shift;
|
||||
unless (defined(tz_offset($tz))) {
|
||||
return "must be empty or a legal timezone name, such as PDT or JST";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_smtp_auth {
|
||||
my $username = shift;
|
||||
if ($username) {
|
||||
eval "require Authen::SASL";
|
||||
return "Error requiring Authen::SASL: '$@'" if $@;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
# OK, here are the parameter definitions themselves.
|
||||
#
|
||||
# Each definition is a hash with keys:
|
||||
#
|
||||
# name - name of the param
|
||||
# desc - description of the param (for editparams.cgi)
|
||||
# type - see below
|
||||
# choices - (optional) see below
|
||||
# default - default value for the param
|
||||
# checker - (optional) checking function for validating parameter entry
|
||||
# It is called with the value of the param as the first arg and a
|
||||
# reference to the param's hash as the second argument
|
||||
#
|
||||
# The type value can be one of the following:
|
||||
#
|
||||
# t -- A short text entry field (suitable for a single line)
|
||||
# p -- A short text entry field (as with type = 't'), but the string is
|
||||
# replaced by asterisks (appropriate for passwords)
|
||||
# l -- A long text field (suitable for many lines)
|
||||
# b -- A boolean value (either 1 or 0)
|
||||
# m -- A list of values, with many selectable (shows up as a select box)
|
||||
# To specify the list of values, make the 'choices' key be an array
|
||||
# reference of the valid choices. The 'default' key should be a string
|
||||
# with a list of selected values (as a comma-separated list), i.e.:
|
||||
# {
|
||||
# name => 'multiselect',
|
||||
# desc => 'A list of options, choose many',
|
||||
# type => 'm',
|
||||
# choices => [ 'a', 'b', 'c', 'd' ],
|
||||
# default => [ 'a', 'd' ],
|
||||
# checker => \&check_multi
|
||||
# }
|
||||
#
|
||||
# Here, 'a' and 'd' are the default options, and the user may pick any
|
||||
# combination of a, b, c, and d as valid options.
|
||||
#
|
||||
# &check_multi should always be used as the param verification function
|
||||
# for list (single and multiple) parameter types.
|
||||
#
|
||||
# o -- A list of values, orderable, and with many selectable (shows up as a
|
||||
# JavaScript-enhanced select box if JavaScript is enabled, and a text
|
||||
# entry field if not)
|
||||
# Set up in the same way as type m.
|
||||
#
|
||||
# s -- A list of values, with one selectable (shows up as a select box)
|
||||
# To specify the list of values, make the 'choices' key be an array
|
||||
# reference of the valid choices. The 'default' key should be one of
|
||||
# those values, i.e.:
|
||||
# {
|
||||
# name => 'singleselect',
|
||||
# desc => 'A list of options, choose one',
|
||||
# type => 's',
|
||||
# choices => [ 'a', 'b', 'c' ],
|
||||
# default => 'b',
|
||||
# checker => \&check_multi
|
||||
# }
|
||||
#
|
||||
# Here, 'b' is the default option, and 'a' and 'c' are other possible
|
||||
# options, but only one at a time!
|
||||
#
|
||||
# &check_multi should always be used as the param verification function
|
||||
# for list (single and multiple) parameter types.
|
||||
|
||||
sub get_param_list {
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Config::Common - Parameter checking functions
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
All parameter checking functions are called with two parameters:
|
||||
|
||||
=head2 Functions
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_multi>
|
||||
|
||||
Checks that a multi-valued parameter (ie types C<s>, C<o> or C<m>) satisfies
|
||||
its contraints.
|
||||
|
||||
=item C<check_numeric>
|
||||
|
||||
Checks that the value is a valid number
|
||||
|
||||
=item C<check_regexp>
|
||||
|
||||
Checks that the value is a valid regexp
|
||||
|
||||
=back
|
||||
@@ -1,133 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Core;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Core::sortkey = "00";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'maintainer',
|
||||
type => 't',
|
||||
default => 'THE MAINTAINER HAS NOT YET BEEN SET'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'urlbase',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_urlbase
|
||||
},
|
||||
|
||||
{
|
||||
name => 'docs_urlbase',
|
||||
type => 't',
|
||||
default => 'docs/%lang%/html/',
|
||||
checker => \&check_url
|
||||
},
|
||||
|
||||
{
|
||||
name => 'sslbase',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_sslbase
|
||||
},
|
||||
|
||||
{
|
||||
name => 'ssl',
|
||||
type => 's',
|
||||
choices => ['never', 'authenticated sessions', 'always'],
|
||||
default => 'never'
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
name => 'cookiedomain',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'cookiepath',
|
||||
type => 't',
|
||||
default => '/'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'timezone',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_timezone
|
||||
},
|
||||
|
||||
{
|
||||
name => 'utf8',
|
||||
type => 'b',
|
||||
default => '0',
|
||||
checker => \&check_utf8
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shutdownhtml',
|
||||
type => 'l',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'announcehtml',
|
||||
type => 'l',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'proxy_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'upgrade_notification',
|
||||
type => 's',
|
||||
choices => ['development_snapshot', 'latest_stable_release',
|
||||
'stable_branch_release', 'disabled'],
|
||||
default => 'latest_stable_release',
|
||||
checker => \&check_notification
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::DependencyGraph;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::DependencyGraph::sortkey = "06";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'webdotbase',
|
||||
type => 't',
|
||||
default => 'http://www.research.att.com/~north/cgi-bin/webdot.cgi/%urlbase%',
|
||||
checker => \&check_webdotbase
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,108 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::GroupSecurity;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Group;
|
||||
|
||||
$Bugzilla::Config::GroupSecurity::sortkey = "07";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'makeproductgroups',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'useentrygroupdefault',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'chartgroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'insidergroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => '',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'timetrackinggroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'querysharegroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usevisibilitygroups',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'strict_isolation',
|
||||
type => 'b',
|
||||
default => 0
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
sub _get_all_group_names {
|
||||
my @group_names = map {$_->name} Bugzilla::Group->get_all;
|
||||
unshift(@group_names, '');
|
||||
return \@group_names;
|
||||
}
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::LDAP;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::LDAP::sortkey = "09";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'LDAPserver',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPstarttls',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPbinddn',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPBaseDN',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPuidattribute',
|
||||
type => 't',
|
||||
default => 'uid'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPmailattribute',
|
||||
type => 't',
|
||||
default => 'mail'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPfilter',
|
||||
type => 't',
|
||||
default => '',
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,102 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::MTA;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Email::Send;
|
||||
|
||||
$Bugzilla::Config::MTA::sortkey = "10";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'mail_delivery_method',
|
||||
type => 's',
|
||||
# Bugzilla is not ready yet to send mails to newsgroups, and 'IO'
|
||||
# is of no use for now as we already have our own 'Test' mode.
|
||||
choices => [grep {$_ ne 'NNTP' && $_ ne 'IO'} Email::Send->new()->all_mailers(), 'None'],
|
||||
default => 'Sendmail',
|
||||
checker => \&check_mail_delivery_method
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mailfrom',
|
||||
type => 't',
|
||||
default => 'bugzilla-daemon'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'sendmailnow',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'smtpserver',
|
||||
type => 't',
|
||||
default => 'localhost'
|
||||
},
|
||||
{
|
||||
name => 'smtp_username',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_smtp_auth
|
||||
},
|
||||
{
|
||||
name => 'smtp_password',
|
||||
type => 'p',
|
||||
default => ''
|
||||
},
|
||||
{
|
||||
name => 'smtp_debug',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'whinedays',
|
||||
type => 't',
|
||||
default => 7,
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'globalwatchers',
|
||||
type => 't',
|
||||
default => '',
|
||||
}, );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,75 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::PatchViewer;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::PatchViewer::sortkey = "11";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'cvsroot',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'cvsroot_get',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'bonsai_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'lxr_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'lxr_root',
|
||||
type => 't',
|
||||
default => '',
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Query;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Query::sortkey = "12";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'quip_list_entry_control',
|
||||
type => 's',
|
||||
choices => ['open', 'moderated', 'closed'],
|
||||
default => 'open',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mostfreqthreshold',
|
||||
type => 't',
|
||||
default => '2',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mybugstemplate',
|
||||
type => 't',
|
||||
default => 'buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailreporter1=1&emailtype1=exact&email1=%userid%&field0-0-0=bug_status&type0-0-0=notequals&value0-0-0=UNCONFIRMED&field0-0-1=reporter&type0-0-1=equals&value0-0-1=%userid%'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultquery',
|
||||
type => 't',
|
||||
default => 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&order=Importance&long_desc_type=substring'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'quicksearch_comment_cutoff',
|
||||
type => 't',
|
||||
default => '4',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'specific_search_allow_empty_words',
|
||||
type => 'b',
|
||||
default => 0
|
||||
}
|
||||
|
||||
);
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::RADIUS;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::RADIUS::sortkey = "09";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'RADIUS_server',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_secret',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_NAS_IP',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_email_suffix',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
);
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,73 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::ShadowDB;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::ShadowDB::sortkey = "13";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'shadowdbhost',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shadowdbport',
|
||||
type => 't',
|
||||
default => '3306',
|
||||
checker => \&check_numeric,
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shadowdbsock',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
# This entry must be _after_ the shadowdb{host,port,sock} settings so that
|
||||
# they can be used in the validation here
|
||||
{
|
||||
name => 'shadowdb',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_shadowdb
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,71 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::UserMatch;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::UserMatch::sortkey = "14";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'usemenuforusers',
|
||||
type => 'b',
|
||||
default => '0'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usermatchmode',
|
||||
type => 's',
|
||||
choices => ['off', 'wildcard', 'search'],
|
||||
default => 'off'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxusermatches',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'confirmuniqueusermatch',
|
||||
type => 'b',
|
||||
default => 1,
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,480 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jake <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Constants;
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
# For bz_locations
|
||||
use File::Basename;
|
||||
|
||||
@Bugzilla::Constants::EXPORT = qw(
|
||||
BUGZILLA_VERSION
|
||||
|
||||
bz_locations
|
||||
|
||||
IS_NULL
|
||||
NOT_NULL
|
||||
|
||||
CONTROLMAPNA
|
||||
CONTROLMAPSHOWN
|
||||
CONTROLMAPDEFAULT
|
||||
CONTROLMAPMANDATORY
|
||||
|
||||
AUTH_OK
|
||||
AUTH_NODATA
|
||||
AUTH_ERROR
|
||||
AUTH_LOGINFAILED
|
||||
AUTH_DISABLED
|
||||
AUTH_NO_SUCH_USER
|
||||
|
||||
USER_PASSWORD_MIN_LENGTH
|
||||
USER_PASSWORD_MAX_LENGTH
|
||||
|
||||
LOGIN_OPTIONAL
|
||||
LOGIN_NORMAL
|
||||
LOGIN_REQUIRED
|
||||
|
||||
LOGOUT_ALL
|
||||
LOGOUT_CURRENT
|
||||
LOGOUT_KEEP_CURRENT
|
||||
|
||||
GRANT_DIRECT
|
||||
GRANT_REGEXP
|
||||
|
||||
GROUP_MEMBERSHIP
|
||||
GROUP_BLESS
|
||||
GROUP_VISIBLE
|
||||
|
||||
MAILTO_USER
|
||||
MAILTO_GROUP
|
||||
|
||||
DEFAULT_COLUMN_LIST
|
||||
DEFAULT_QUERY_NAME
|
||||
|
||||
QUERY_LIST
|
||||
LIST_OF_BUGS
|
||||
|
||||
COMMENT_COLS
|
||||
MAX_COMMENT_LENGTH
|
||||
|
||||
CMT_NORMAL
|
||||
CMT_DUPE_OF
|
||||
CMT_HAS_DUPE
|
||||
CMT_POPULAR_VOTES
|
||||
CMT_MOVED_TO
|
||||
|
||||
THROW_ERROR
|
||||
|
||||
RELATIONSHIPS
|
||||
REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER REL_GLOBAL_WATCHER
|
||||
REL_ANY
|
||||
|
||||
POS_EVENTS
|
||||
EVT_OTHER EVT_ADDED_REMOVED EVT_COMMENT EVT_ATTACHMENT EVT_ATTACHMENT_DATA
|
||||
EVT_PROJ_MANAGEMENT EVT_OPENED_CLOSED EVT_KEYWORD EVT_CC EVT_DEPEND_BLOCK
|
||||
|
||||
NEG_EVENTS
|
||||
EVT_UNCONFIRMED EVT_CHANGED_BY_ME
|
||||
|
||||
GLOBAL_EVENTS
|
||||
EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
|
||||
|
||||
FULLTEXT_BUGLIST_LIMIT
|
||||
|
||||
ADMIN_GROUP_NAME
|
||||
PER_PRODUCT_PRIVILEGES
|
||||
|
||||
SENDMAIL_EXE
|
||||
SENDMAIL_PATH
|
||||
|
||||
FIELD_TYPE_UNKNOWN
|
||||
FIELD_TYPE_FREETEXT
|
||||
FIELD_TYPE_SINGLE_SELECT
|
||||
FIELD_TYPE_MULTI_SELECT
|
||||
FIELD_TYPE_TEXTAREA
|
||||
FIELD_TYPE_DATETIME
|
||||
|
||||
USAGE_MODE_BROWSER
|
||||
USAGE_MODE_CMDLINE
|
||||
USAGE_MODE_WEBSERVICE
|
||||
USAGE_MODE_EMAIL
|
||||
|
||||
ERROR_MODE_WEBPAGE
|
||||
ERROR_MODE_DIE
|
||||
ERROR_MODE_DIE_SOAP_FAULT
|
||||
|
||||
INSTALLATION_MODE_INTERACTIVE
|
||||
INSTALLATION_MODE_NON_INTERACTIVE
|
||||
|
||||
DB_MODULE
|
||||
ROOT_USER
|
||||
ON_WINDOWS
|
||||
|
||||
MAX_TOKEN_AGE
|
||||
|
||||
SAFE_PROTOCOLS
|
||||
|
||||
MIN_SMALLINT
|
||||
MAX_SMALLINT
|
||||
|
||||
MAX_LEN_QUERY_NAME
|
||||
MAX_MILESTONE_SIZE
|
||||
MAX_COMPONENT_SIZE
|
||||
MAX_FREETEXT_LENGTH
|
||||
);
|
||||
|
||||
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
|
||||
|
||||
# CONSTANTS
|
||||
#
|
||||
# Bugzilla version
|
||||
use constant BUGZILLA_VERSION => "3.2";
|
||||
|
||||
# These are unique values that are unlikely to match a string or a number,
|
||||
# to be used in criteria for match() functions and other things. They start
|
||||
# and end with spaces because most Bugzilla stuff has trim() called on it,
|
||||
# so this is unlikely to match anything we get out of the DB.
|
||||
#
|
||||
# We can't use a reference, because Template Toolkit doesn't work with
|
||||
# them properly (constants.IS_NULL => {} just returns an empty string instead
|
||||
# of the reference).
|
||||
use constant IS_NULL => ' __IS_NULL__ ';
|
||||
use constant NOT_NULL => ' __NOT_NULL__ ';
|
||||
|
||||
#
|
||||
# ControlMap constants for group_control_map.
|
||||
# membercontol:othercontrol => meaning
|
||||
# Na:Na => Bugs in this product may not be restricted to this
|
||||
# group.
|
||||
# Shown:Na => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Shown:Shown => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Anyone who can enter bugs in this product may initially
|
||||
# restrict bugs in this product to this group.
|
||||
# Shown:Mandatory => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Non-members who can enter bug in this product
|
||||
# will be forced to restrict it.
|
||||
# Default:Na => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default.
|
||||
# Default:Default => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default and
|
||||
# nonmembers have this option on entry.
|
||||
# Default:Mandatory => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default.
|
||||
# Non-members who can enter bug in this product
|
||||
# will be forced to restrict it.
|
||||
# Mandatory:Mandatory => Bug will be forced into this group regardless.
|
||||
# All other combinations are illegal.
|
||||
|
||||
use constant CONTROLMAPNA => 0;
|
||||
use constant CONTROLMAPSHOWN => 1;
|
||||
use constant CONTROLMAPDEFAULT => 2;
|
||||
use constant CONTROLMAPMANDATORY => 3;
|
||||
|
||||
# See Bugzilla::Auth for docs on AUTH_*, LOGIN_* and LOGOUT_*
|
||||
|
||||
use constant AUTH_OK => 0;
|
||||
use constant AUTH_NODATA => 1;
|
||||
use constant AUTH_ERROR => 2;
|
||||
use constant AUTH_LOGINFAILED => 3;
|
||||
use constant AUTH_DISABLED => 4;
|
||||
use constant AUTH_NO_SUCH_USER => 5;
|
||||
|
||||
# The minimum and maximum lengths a password must have.
|
||||
use constant USER_PASSWORD_MIN_LENGTH => 3;
|
||||
use constant USER_PASSWORD_MAX_LENGTH => 16;
|
||||
|
||||
use constant LOGIN_OPTIONAL => 0;
|
||||
use constant LOGIN_NORMAL => 1;
|
||||
use constant LOGIN_REQUIRED => 2;
|
||||
|
||||
use constant LOGOUT_ALL => 0;
|
||||
use constant LOGOUT_CURRENT => 1;
|
||||
use constant LOGOUT_KEEP_CURRENT => 2;
|
||||
|
||||
use constant contenttypes =>
|
||||
{
|
||||
"html"=> "text/html" ,
|
||||
"rdf" => "application/rdf+xml" ,
|
||||
"atom"=> "application/atom+xml" ,
|
||||
"xml" => "application/xml" ,
|
||||
"js" => "application/x-javascript" ,
|
||||
"csv" => "text/csv" ,
|
||||
"png" => "image/png" ,
|
||||
"ics" => "text/calendar" ,
|
||||
};
|
||||
|
||||
use constant GRANT_DIRECT => 0;
|
||||
use constant GRANT_REGEXP => 2;
|
||||
|
||||
use constant GROUP_MEMBERSHIP => 0;
|
||||
use constant GROUP_BLESS => 1;
|
||||
use constant GROUP_VISIBLE => 2;
|
||||
|
||||
use constant MAILTO_USER => 0;
|
||||
use constant MAILTO_GROUP => 1;
|
||||
|
||||
# The default list of columns for buglist.cgi
|
||||
use constant DEFAULT_COLUMN_LIST => (
|
||||
"bug_severity", "priority", "op_sys","assigned_to",
|
||||
"bug_status", "resolution", "short_desc"
|
||||
);
|
||||
|
||||
# Used by query.cgi and buglist.cgi as the named-query name
|
||||
# for the default settings.
|
||||
use constant DEFAULT_QUERY_NAME => '(Default query)';
|
||||
|
||||
# The possible types for saved searches.
|
||||
use constant QUERY_LIST => 0;
|
||||
use constant LIST_OF_BUGS => 1;
|
||||
|
||||
# The column length for displayed (and wrapped) bug comments.
|
||||
use constant COMMENT_COLS => 80;
|
||||
# Used in _check_comment(). Gives the max length allowed for a comment.
|
||||
use constant MAX_COMMENT_LENGTH => 65535;
|
||||
|
||||
# The type of bug comments.
|
||||
use constant CMT_NORMAL => 0;
|
||||
use constant CMT_DUPE_OF => 1;
|
||||
use constant CMT_HAS_DUPE => 2;
|
||||
use constant CMT_POPULAR_VOTES => 3;
|
||||
use constant CMT_MOVED_TO => 4;
|
||||
|
||||
# Determine whether a validation routine should return 0 or throw
|
||||
# an error when the validation fails.
|
||||
use constant THROW_ERROR => 1;
|
||||
|
||||
use constant REL_ASSIGNEE => 0;
|
||||
use constant REL_QA => 1;
|
||||
use constant REL_REPORTER => 2;
|
||||
use constant REL_CC => 3;
|
||||
use constant REL_VOTER => 4;
|
||||
use constant REL_GLOBAL_WATCHER => 5;
|
||||
|
||||
use constant RELATIONSHIPS => REL_ASSIGNEE, REL_QA, REL_REPORTER, REL_CC,
|
||||
REL_VOTER, REL_GLOBAL_WATCHER;
|
||||
|
||||
# Used for global events like EVT_FLAG_REQUESTED
|
||||
use constant REL_ANY => 100;
|
||||
|
||||
# There are two sorts of event - positive and negative. Positive events are
|
||||
# those for which the user says "I want mail if this happens." Negative events
|
||||
# are those for which the user says "I don't want mail if this happens."
|
||||
#
|
||||
# Exactly when each event fires is defined in wants_bug_mail() in User.pm; I'm
|
||||
# not commenting them here in case the comments and the code get out of sync.
|
||||
use constant EVT_OTHER => 0;
|
||||
use constant EVT_ADDED_REMOVED => 1;
|
||||
use constant EVT_COMMENT => 2;
|
||||
use constant EVT_ATTACHMENT => 3;
|
||||
use constant EVT_ATTACHMENT_DATA => 4;
|
||||
use constant EVT_PROJ_MANAGEMENT => 5;
|
||||
use constant EVT_OPENED_CLOSED => 6;
|
||||
use constant EVT_KEYWORD => 7;
|
||||
use constant EVT_CC => 8;
|
||||
use constant EVT_DEPEND_BLOCK => 9;
|
||||
|
||||
use constant POS_EVENTS => EVT_OTHER, EVT_ADDED_REMOVED, EVT_COMMENT,
|
||||
EVT_ATTACHMENT, EVT_ATTACHMENT_DATA,
|
||||
EVT_PROJ_MANAGEMENT, EVT_OPENED_CLOSED, EVT_KEYWORD,
|
||||
EVT_CC, EVT_DEPEND_BLOCK;
|
||||
|
||||
use constant EVT_UNCONFIRMED => 50;
|
||||
use constant EVT_CHANGED_BY_ME => 51;
|
||||
|
||||
use constant NEG_EVENTS => EVT_UNCONFIRMED, EVT_CHANGED_BY_ME;
|
||||
|
||||
# These are the "global" flags, which aren't tied to a particular relationship.
|
||||
# and so use REL_ANY.
|
||||
use constant EVT_FLAG_REQUESTED => 100; # Flag has been requested of me
|
||||
use constant EVT_REQUESTED_FLAG => 101; # I have requested a flag
|
||||
|
||||
use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
|
||||
|
||||
# Number of bugs to return in a buglist when performing
|
||||
# a fulltext search.
|
||||
use constant FULLTEXT_BUGLIST_LIMIT => 200;
|
||||
|
||||
# Default administration group name.
|
||||
use constant ADMIN_GROUP_NAME => 'admin';
|
||||
|
||||
# Privileges which can be per-product.
|
||||
use constant PER_PRODUCT_PRIVILEGES => ('editcomponents', 'editbugs', 'canconfirm');
|
||||
|
||||
# Path to sendmail.exe (Windows only)
|
||||
use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
|
||||
# Paths to search for the sendmail binary (non-Windows)
|
||||
use constant SENDMAIL_PATH => '/usr/lib:/usr/sbin:/usr/ucblib';
|
||||
|
||||
# Field types. Match values in fielddefs.type column. These are purposely
|
||||
# not named after database column types, since Bugzilla fields comprise not
|
||||
# only storage but also logic. For example, we might add a "user" field type
|
||||
# whose values are stored in an integer column in the database but for which
|
||||
# we do more than we would do for a standard integer type (f.e. we might
|
||||
# display a user picker).
|
||||
|
||||
use constant FIELD_TYPE_UNKNOWN => 0;
|
||||
use constant FIELD_TYPE_FREETEXT => 1;
|
||||
use constant FIELD_TYPE_SINGLE_SELECT => 2;
|
||||
use constant FIELD_TYPE_MULTI_SELECT => 3;
|
||||
use constant FIELD_TYPE_TEXTAREA => 4;
|
||||
use constant FIELD_TYPE_DATETIME => 5;
|
||||
|
||||
# The maximum number of days a token will remain valid.
|
||||
use constant MAX_TOKEN_AGE => 3;
|
||||
|
||||
# Protocols which are considered as safe.
|
||||
use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
|
||||
'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
|
||||
'view-source', 'wais');
|
||||
|
||||
# Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
|
||||
use constant USAGE_MODE_BROWSER => 0;
|
||||
use constant USAGE_MODE_CMDLINE => 1;
|
||||
use constant USAGE_MODE_WEBSERVICE => 2;
|
||||
use constant USAGE_MODE_EMAIL => 3;
|
||||
|
||||
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
|
||||
# usually). Use with Bugzilla->error_mode.
|
||||
use constant ERROR_MODE_WEBPAGE => 0;
|
||||
use constant ERROR_MODE_DIE => 1;
|
||||
use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
|
||||
|
||||
# The various modes that checksetup.pl can run in.
|
||||
use constant INSTALLATION_MODE_INTERACTIVE => 0;
|
||||
use constant INSTALLATION_MODE_NON_INTERACTIVE => 1;
|
||||
|
||||
# Data about what we require for different databases.
|
||||
use constant DB_MODULE => {
|
||||
'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '4.1.2',
|
||||
dbd => {
|
||||
package => 'DBD-mysql',
|
||||
module => 'DBD::mysql',
|
||||
# Disallow development versions
|
||||
blacklist => ['_'],
|
||||
# For UTF-8 support
|
||||
version => '4.00',
|
||||
},
|
||||
name => 'MySQL'},
|
||||
'pg' => {db => 'Bugzilla::DB::Pg', db_version => '8.00.0000',
|
||||
dbd => {
|
||||
package => 'DBD-Pg',
|
||||
module => 'DBD::Pg',
|
||||
version => '1.45',
|
||||
},
|
||||
name => 'PostgreSQL'},
|
||||
'oracle'=> {db => 'Bugzilla::DB::Oracle', db_version => '10.02.0',
|
||||
dbd => {
|
||||
package => 'DBD-Oracle',
|
||||
module => 'DBD::Oracle',
|
||||
version => '1.19',
|
||||
},
|
||||
name => 'Oracle'},
|
||||
};
|
||||
|
||||
# The user who should be considered "root" when we're giving
|
||||
# instructions to Bugzilla administrators.
|
||||
use constant ROOT_USER => $^O =~ /MSWin32/i ? 'Administrator' : 'root';
|
||||
|
||||
# True if we're on Win32.
|
||||
use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
|
||||
|
||||
use constant MIN_SMALLINT => -32768;
|
||||
use constant MAX_SMALLINT => 32767;
|
||||
|
||||
# The longest that a saved search name can be.
|
||||
use constant MAX_LEN_QUERY_NAME => 64;
|
||||
|
||||
# The longest milestone name allowed.
|
||||
use constant MAX_MILESTONE_SIZE => 20;
|
||||
|
||||
# The longest component name allowed.
|
||||
use constant MAX_COMPONENT_SIZE => 64;
|
||||
|
||||
# Maximum length allowed for free text fields.
|
||||
use constant MAX_FREETEXT_LENGTH => 255;
|
||||
|
||||
sub bz_locations {
|
||||
# We know that Bugzilla/Constants.pm must be in %INC at this point.
|
||||
# So the only question is, what's the name of the directory
|
||||
# above it? This is the most reliable way to get our current working
|
||||
# directory under both mod_cgi and mod_perl. We call dirname twice
|
||||
# to get the name of the directory above the "Bugzilla/" directory.
|
||||
#
|
||||
# Calling dirname twice like that won't work on VMS or AmigaOS
|
||||
# but I doubt anybody runs Bugzilla on those.
|
||||
#
|
||||
# On mod_cgi this will be a relative path. On mod_perl it will be an
|
||||
# absolute path.
|
||||
my $libpath = dirname(dirname($INC{'Bugzilla/Constants.pm'}));
|
||||
# We have to detaint $libpath, but we can't use Bugzilla::Util here.
|
||||
$libpath =~ /(.*)/;
|
||||
$libpath = $1;
|
||||
|
||||
my ($project, $localconfig, $datadir);
|
||||
if ($ENV{'PROJECT'} && $ENV{'PROJECT'} =~ /^(\w+)$/) {
|
||||
$project = $1;
|
||||
$localconfig = "localconfig.$project";
|
||||
$datadir = "data/$project";
|
||||
} else {
|
||||
$localconfig = "localconfig";
|
||||
$datadir = "data";
|
||||
}
|
||||
|
||||
# We have to return absolute paths for mod_perl.
|
||||
# That means that if you modify these paths, they must be absolute paths.
|
||||
return {
|
||||
'libpath' => $libpath,
|
||||
'ext_libpath' => "$libpath/lib",
|
||||
# If you put the libraries in a different location than the CGIs,
|
||||
# make sure this still points to the CGIs.
|
||||
'cgi_path' => $libpath,
|
||||
'templatedir' => "$libpath/template",
|
||||
'project' => $project,
|
||||
'localconfig' => "$libpath/$localconfig",
|
||||
'datadir' => "$libpath/$datadir",
|
||||
'attachdir' => "$libpath/$datadir/attachments",
|
||||
'skinsdir' => "$libpath/skins",
|
||||
# $webdotdir must be in the web server's tree somewhere. Even if you use a
|
||||
# local dot, we output images to there. Also, if $webdotdir is
|
||||
# not relative to the bugzilla root directory, you'll need to
|
||||
# change showdependencygraph.cgi to set image_url to the correct
|
||||
# location.
|
||||
# The script should really generate these graphs directly...
|
||||
'webdotdir' => "$libpath/$datadir/webdot",
|
||||
'extensionsdir' => "$libpath/extensions",
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,959 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Dave Miller <davem00@aol.com>
|
||||
# Gayathri Swaminath <gayathrik00@aol.com>
|
||||
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Mysql - Bugzilla database compatibility layer for MySQL
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with MySQL specific
|
||||
implementation. It is instantiated by the Bugzilla::DB module and should never
|
||||
be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Mysql;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::DB::Schema::Mysql;
|
||||
|
||||
use List::Util qw(max);
|
||||
|
||||
# This is how many comments of MAX_COMMENT_LENGTH we expect on a single bug.
|
||||
# In reality, you could have a LOT more comments than this, because
|
||||
# MAX_COMMENT_LENGTH is big.
|
||||
use constant MAX_COMMENTS => 50;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port, $sock) = @_;
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:mysql:host=$host;database=$dbname";
|
||||
$dsn .= ";port=$port" if $port;
|
||||
$dsn .= ";mysql_socket=$sock" if $sock;
|
||||
|
||||
my $attrs = { mysql_enable_utf8 => Bugzilla->params->{'utf8'} };
|
||||
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
|
||||
# This makes sure that if the tables are encoded as UTF-8, we
|
||||
# return their data correctly.
|
||||
$self->do("SET NAMES utf8") if Bugzilla->params->{'utf8'};
|
||||
|
||||
# all class local variables stored in DBI derived class needs to have
|
||||
# a prefix 'private_'. See DBI documentation.
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
# Bug 321645 - disable MySQL strict mode, if set
|
||||
my ($var, $sql_mode) = $self->selectrow_array(
|
||||
"SHOW VARIABLES LIKE 'sql\\_mode'");
|
||||
|
||||
if ($sql_mode) {
|
||||
# STRICT_TRANS_TABLE or STRICT_ALL_TABLES enable MySQL strict mode,
|
||||
# causing bug 321645. TRADITIONAL sets these modes (among others) as
|
||||
# well, so it has to be stipped as well
|
||||
my $new_sql_mode =
|
||||
join(",", grep {$_ !~ /^STRICT_(?:TRANS|ALL)_TABLES|TRADITIONAL$/}
|
||||
split(/,/, $sql_mode));
|
||||
|
||||
if ($sql_mode ne $new_sql_mode) {
|
||||
$self->do("SET SESSION sql_mode = ?", undef, $new_sql_mode);
|
||||
}
|
||||
}
|
||||
|
||||
# The "comments" field of the bugs_fulltext table could easily exceed
|
||||
# MySQL's default max_allowed_packet. Also, MySQL should never have
|
||||
# a max_allowed_packet smaller than our max_attachment_size. However,
|
||||
# if we've already set a max_allowed_packet in MySQL bigger than all
|
||||
# of those, we should keep it.
|
||||
my (undef, $current_max_allowed) = $self->selectrow_array(
|
||||
q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
|
||||
my $min_max_allowed_packet = MAX_COMMENTS * MAX_COMMENT_LENGTH;
|
||||
my $max_allowed_packet = max($min_max_allowed_packet,
|
||||
$current_max_allowed,
|
||||
# This parameter is not yet defined when the DB
|
||||
# is being built for the very first time.
|
||||
Bugzilla->params->{'maxattachmentsize'} || 0);
|
||||
$self->do("SET SESSION max_allowed_packet = $max_allowed_packet");
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# when last_insert_id() is supported on MySQL by lowest DBI/DBD version
|
||||
# required by Bugzilla, this implementation can be removed.
|
||||
sub bz_last_key {
|
||||
my ($self) = @_;
|
||||
|
||||
my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_group_concat {
|
||||
my ($self, $column, $separator) = @_;
|
||||
my $sep_sql;
|
||||
if ($separator) {
|
||||
$sep_sql = " SEPARATOR $separator";
|
||||
}
|
||||
return "GROUP_CONCAT($column$sep_sql)";
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr REGEXP $pattern";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr NOT REGEXP $pattern";
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if (defined($offset)) {
|
||||
return "LIMIT $offset, $limit";
|
||||
} else {
|
||||
return "LIMIT $limit";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
return 'CONCAT(' . join(', ', @params) . ')';
|
||||
}
|
||||
|
||||
sub sql_fulltext_search {
|
||||
my ($self, $column, $text) = @_;
|
||||
|
||||
# Add the boolean mode modifier if the search string contains
|
||||
# boolean operators.
|
||||
my $mode = ($text =~ /[+\-<>()~*"]/ ? "IN BOOLEAN MODE" : "");
|
||||
|
||||
# quote the text for use in the MATCH AGAINST expression
|
||||
$text = $self->quote($text);
|
||||
|
||||
# untaint the text, since it's safe to use now that we've quoted it
|
||||
trick_taint($text);
|
||||
|
||||
return "MATCH($column) AGAINST($text $mode)";
|
||||
}
|
||||
|
||||
sub sql_istring {
|
||||
my ($self, $string) = @_;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub sql_from_days {
|
||||
my ($self, $days) = @_;
|
||||
|
||||
return "FROM_DAYS($days)";
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return "TO_DAYS($date)";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
return "DATE_FORMAT($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
|
||||
return "INTERVAL $interval $units";
|
||||
}
|
||||
|
||||
sub sql_iposition {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
return "INSTR($text, $fragment)";
|
||||
}
|
||||
|
||||
sub sql_position {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
|
||||
return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
|
||||
}
|
||||
|
||||
sub sql_group_by {
|
||||
my ($self, $needed_columns, $optional_columns) = @_;
|
||||
|
||||
# MySQL allows you to specify the minimal subset of columns to get
|
||||
# a unique result. While it does allow specifying all columns as
|
||||
# ANSI SQL requires, according to MySQL documentation, the fewer
|
||||
# columns you specify, the faster the query runs.
|
||||
return "GROUP BY $needed_columns";
|
||||
}
|
||||
|
||||
|
||||
sub _bz_get_initial_schema {
|
||||
my ($self) = @_;
|
||||
return $self->_bz_build_schema_from_disk();
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my ($self) = @_;
|
||||
|
||||
# Make sure the installation has InnoDB turned on, or we're going to be
|
||||
# doing silly things like making foreign keys on MyISAM tables, which is
|
||||
# hard to fix later. We do this up here because none of the code below
|
||||
# works if InnoDB is off. (Particularly if we've already converted the
|
||||
# tables to InnoDB.)
|
||||
my ($innodb_on) = @{$self->selectcol_arrayref(
|
||||
q{SHOW VARIABLES LIKE '%have_innodb%'}, {Columns=>[2]})};
|
||||
if ($innodb_on ne 'YES') {
|
||||
print <<EOT;
|
||||
InnoDB is disabled in your MySQL installation.
|
||||
Bugzilla requires InnoDB to be enabled.
|
||||
Please enable it and then re-run checksetup.pl.
|
||||
|
||||
EOT
|
||||
exit 3;
|
||||
}
|
||||
|
||||
|
||||
# Figure out if any existing tables are of type ISAM and convert them
|
||||
# to type MyISAM if so. ISAM tables are deprecated in MySQL 3.23,
|
||||
# which Bugzilla now requires, and they don't support more than 16
|
||||
# indexes per table, which Bugzilla needs.
|
||||
my $table_status = $self->selectall_arrayref("SHOW TABLE STATUS");
|
||||
my @isam_tables;
|
||||
foreach my $row (@$table_status) {
|
||||
my ($name, $type) = @$row;
|
||||
push(@isam_tables, $name) if $type eq "ISAM";
|
||||
}
|
||||
|
||||
if(scalar(@isam_tables)) {
|
||||
print "One or more of the tables in your existing MySQL database are\n"
|
||||
. "of type ISAM. ISAM tables are deprecated in MySQL 3.23 and\n"
|
||||
. "don't support more than 16 indexes per table, which \n"
|
||||
. "Bugzilla needs.\n Converting your ISAM tables to type"
|
||||
. " MyISAM:\n\n";
|
||||
foreach my $table (@isam_tables) {
|
||||
print "Converting table $table... ";
|
||||
$self->do("ALTER TABLE $table TYPE = MYISAM");
|
||||
print "done.\n";
|
||||
}
|
||||
print "\nISAM->MyISAM table conversion done.\n\n";
|
||||
}
|
||||
|
||||
my ($sd_index_deleted, $longdescs_index_deleted);
|
||||
my @tables = $self->bz_table_list_real();
|
||||
# We want to convert tables to InnoDB, but it's possible that they have
|
||||
# fulltext indexes on them, and conversion will fail unless we remove
|
||||
# the indexes.
|
||||
if (grep($_ eq 'bugs', @tables)) {
|
||||
if ($self->bz_index_info_real('bugs', 'short_desc')) {
|
||||
$self->bz_drop_index_raw('bugs', 'short_desc');
|
||||
}
|
||||
if ($self->bz_index_info_real('bugs', 'bugs_short_desc_idx')) {
|
||||
$self->bz_drop_index_raw('bugs', 'bugs_short_desc_idx');
|
||||
$sd_index_deleted = 1; # Used for later schema cleanup.
|
||||
}
|
||||
}
|
||||
if (grep($_ eq 'longdescs', @tables)) {
|
||||
if ($self->bz_index_info_real('longdescs', 'thetext')) {
|
||||
$self->bz_drop_index_raw('longdescs', 'thetext');
|
||||
}
|
||||
if ($self->bz_index_info_real('longdescs', 'longdescs_thetext_idx')) {
|
||||
$self->bz_drop_index_raw('longdescs', 'longdescs_thetext_idx');
|
||||
$longdescs_index_deleted = 1; # For later schema cleanup.
|
||||
}
|
||||
}
|
||||
|
||||
# Upgrade tables from MyISAM to InnoDB
|
||||
my @myisam_tables;
|
||||
foreach my $row (@$table_status) {
|
||||
my ($name, $type) = @$row;
|
||||
if ($type =~ /^MYISAM$/i
|
||||
&& !grep($_ eq $name, Bugzilla::DB::Schema::Mysql::MYISAM_TABLES))
|
||||
{
|
||||
push(@myisam_tables, $name) ;
|
||||
}
|
||||
}
|
||||
if (scalar @myisam_tables) {
|
||||
print "Bugzilla now uses the InnoDB storage engine in MySQL for",
|
||||
" most tables.\nConverting tables to InnoDB:\n";
|
||||
foreach my $table (@myisam_tables) {
|
||||
print "Converting table $table... ";
|
||||
$self->do("ALTER TABLE $table TYPE = InnoDB");
|
||||
print "done.\n";
|
||||
}
|
||||
}
|
||||
|
||||
$self->_after_table_status(\@tables);
|
||||
|
||||
# Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
|
||||
# not provide explicit names for the table indexes. This means
|
||||
# that our upgrades will not be reliable, because we look for the name
|
||||
# of the index, not what fields it is on, when doing upgrades.
|
||||
# (using the name is much better for cross-database compatibility
|
||||
# and general reliability). It's also very important that our
|
||||
# Schema object be consistent with what is on the disk.
|
||||
#
|
||||
# While we're at it, we also fix some inconsistent index naming
|
||||
# from the original checkin of Bugzilla::DB::Schema.
|
||||
|
||||
# We check for the existence of a particular "short name" index that
|
||||
# has existed at least since Bugzilla 2.8, and probably earlier.
|
||||
# For fixing the inconsistent naming of Schema indexes,
|
||||
# we also check for one of those inconsistently-named indexes.
|
||||
if (grep($_ eq 'bugs', @tables)
|
||||
&& ($self->bz_index_info_real('bugs', 'assigned_to')
|
||||
|| $self->bz_index_info_real('flags', 'flags_bidattid_idx')) )
|
||||
{
|
||||
|
||||
# This is a check unrelated to the indexes, to see if people are
|
||||
# upgrading from 2.18 or below, but somehow have a bz_schema table
|
||||
# already. This only happens if they have done a mysqldump into
|
||||
# a database without doing a DROP DATABASE first.
|
||||
# We just do the check here since this check is a reliable way
|
||||
# of telling that we are upgrading from a version pre-2.20.
|
||||
if (grep($_ eq 'bz_schema', $self->bz_table_list_real())) {
|
||||
die("\nYou are upgrading from a version before 2.20, but the"
|
||||
. " bz_schema\ntable already exists. This means that you"
|
||||
. " restored a mysqldump into\nthe Bugzilla database without"
|
||||
. " first dropping the already-existing\nBugzilla database,"
|
||||
. " at some point. Whenever you restore a Bugzilla\ndatabase"
|
||||
. " backup, you must always drop the entire database first.\n\n"
|
||||
. "Please drop your Bugzilla database and restore it from a"
|
||||
. " backup that\ndoes not contain the bz_schema table. If for"
|
||||
. " some reason you cannot\ndo this, you can connect to your"
|
||||
. " MySQL database and drop the bz_schema\ntable, as a last"
|
||||
. " resort.\n");
|
||||
}
|
||||
|
||||
my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
|
||||
# We estimate one minute for each 3000 bugs, plus 3 minutes just
|
||||
# to handle basic MySQL stuff.
|
||||
my $rename_time = int($bug_count / 3000) + 3;
|
||||
# And 45 minutes for every 15,000 attachments, per some experiments.
|
||||
my ($attachment_count) =
|
||||
$self->selectrow_array("SELECT COUNT(*) FROM attachments");
|
||||
$rename_time += int(($attachment_count * 45) / 15000);
|
||||
# If we're going to take longer than 5 minutes, we let the user know
|
||||
# and allow them to abort.
|
||||
if ($rename_time > 5) {
|
||||
print "\nWe are about to rename old indexes.\n"
|
||||
. "The estimated time to complete renaming is "
|
||||
. "$rename_time minutes.\n"
|
||||
. "You cannot interrupt this action once it has begun.\n"
|
||||
. "If you would like to cancel, press Ctrl-C now..."
|
||||
. " (Waiting 45 seconds...)\n\n";
|
||||
# Wait 45 seconds for them to respond.
|
||||
sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
|
||||
}
|
||||
print "Renaming indexes...\n";
|
||||
|
||||
# We can't be interrupted, because of how the "if"
|
||||
# works above.
|
||||
local $SIG{INT} = 'IGNORE';
|
||||
local $SIG{TERM} = 'IGNORE';
|
||||
local $SIG{PIPE} = 'IGNORE';
|
||||
|
||||
# Certain indexes had names in Schema that did not easily conform
|
||||
# to a standard. We store those names here, so that they
|
||||
# can be properly renamed.
|
||||
# Also, sometimes an old mysqldump would incorrectly rename
|
||||
# unique indexes to "PRIMARY", so we address that here, also.
|
||||
my $bad_names = {
|
||||
# 'when' is a possible leftover from Bugzillas before 2.8
|
||||
bugs_activity => ['when', 'bugs_activity_bugid_idx',
|
||||
'bugs_activity_bugwhen_idx'],
|
||||
cc => ['PRIMARY'],
|
||||
longdescs => ['longdescs_bugid_idx',
|
||||
'longdescs_bugwhen_idx'],
|
||||
flags => ['flags_bidattid_idx'],
|
||||
flaginclusions => ['flaginclusions_tpcid_idx'],
|
||||
flagexclusions => ['flagexclusions_tpc_id_idx'],
|
||||
keywords => ['PRIMARY'],
|
||||
milestones => ['PRIMARY'],
|
||||
profiles_activity => ['profiles_activity_when_idx'],
|
||||
group_control_map => ['group_control_map_gid_idx', 'PRIMARY'],
|
||||
user_group_map => ['PRIMARY'],
|
||||
group_group_map => ['PRIMARY'],
|
||||
email_setting => ['PRIMARY'],
|
||||
bug_group_map => ['PRIMARY'],
|
||||
category_group_map => ['PRIMARY'],
|
||||
watch => ['PRIMARY'],
|
||||
namedqueries => ['PRIMARY'],
|
||||
series_data => ['PRIMARY'],
|
||||
# series_categories is dealt with below, not here.
|
||||
};
|
||||
|
||||
# The series table is broken and needs to have one index
|
||||
# dropped before we begin the renaming, because it had a
|
||||
# useless index on it that would cause a naming conflict here.
|
||||
if (grep($_ eq 'series', @tables)) {
|
||||
my $dropname;
|
||||
# This is what the bad index was called before Schema.
|
||||
if ($self->bz_index_info_real('series', 'creator_2')) {
|
||||
$dropname = 'creator_2';
|
||||
}
|
||||
# This is what the bad index is called in Schema.
|
||||
elsif ($self->bz_index_info_real('series', 'series_creator_idx')) {
|
||||
$dropname = 'series_creator_idx';
|
||||
}
|
||||
$self->bz_drop_index_raw('series', $dropname) if $dropname;
|
||||
}
|
||||
|
||||
# The email_setting table also had the same problem.
|
||||
if( grep($_ eq 'email_setting', @tables)
|
||||
&& $self->bz_index_info_real('email_setting',
|
||||
'email_settings_user_id_idx') )
|
||||
{
|
||||
$self->bz_drop_index_raw('email_setting',
|
||||
'email_settings_user_id_idx');
|
||||
}
|
||||
|
||||
# Go through all the tables.
|
||||
foreach my $table (@tables) {
|
||||
# Will contain the names of old indexes as keys, and the
|
||||
# definition of the new indexes as a value. The values
|
||||
# include an extra hash key, NAME, with the new name of
|
||||
# the index.
|
||||
my %rename_indexes;
|
||||
# And go through all the columns on each table.
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
|
||||
# We also want to fix the silly naming of unique indexes
|
||||
# that happened when we first checked-in Bugzilla::DB::Schema.
|
||||
if ($table eq 'series_categories') {
|
||||
# The series_categories index had a nonstandard name.
|
||||
push(@columns, 'series_cats_unique_idx');
|
||||
}
|
||||
elsif ($table eq 'email_setting') {
|
||||
# The email_setting table had a similar problem.
|
||||
push(@columns, 'email_settings_unique_idx');
|
||||
}
|
||||
else {
|
||||
push(@columns, "${table}_unique_idx");
|
||||
}
|
||||
# And this is how we fix the other inconsistent Schema naming.
|
||||
push(@columns, @{$bad_names->{$table}})
|
||||
if (exists $bad_names->{$table});
|
||||
foreach my $column (@columns) {
|
||||
# If we have an index named after this column, it's an
|
||||
# old-style-name index.
|
||||
if (my $index = $self->bz_index_info_real($table, $column)) {
|
||||
# Fix the name to fit in with the new naming scheme.
|
||||
$index->{NAME} = $table . "_" .
|
||||
$index->{FIELDS}->[0] . "_idx";
|
||||
print "Renaming index $column to "
|
||||
. $index->{NAME} . "...\n";
|
||||
$rename_indexes{$column} = $index;
|
||||
} # if
|
||||
} # foreach column
|
||||
|
||||
my @rename_sql = $self->_bz_schema->get_rename_indexes_ddl(
|
||||
$table, %rename_indexes);
|
||||
$self->do($_) foreach (@rename_sql);
|
||||
|
||||
} # foreach table
|
||||
} # if old-name indexes
|
||||
|
||||
# If there are no tables, but the DB isn't utf8 and it should be,
|
||||
# then we should alter the database to be utf8. We know it should be
|
||||
# if the utf8 parameter is true or there are no params at all.
|
||||
# This kind of situation happens when people create the database
|
||||
# themselves, and if we don't do this they will get the big
|
||||
# scary WARNING statement about conversion to UTF8.
|
||||
if ( !$self->bz_db_is_utf8 && !@tables
|
||||
&& (Bugzilla->params->{'utf8'} || !scalar keys %{Bugzilla->params}) )
|
||||
{
|
||||
$self->_alter_db_charset_to_utf8();
|
||||
}
|
||||
|
||||
# And now we create the tables and the Schema object.
|
||||
$self->SUPER::bz_setup_database();
|
||||
|
||||
if ($sd_index_deleted) {
|
||||
$self->_bz_real_schema->delete_index('bugs', 'bugs_short_desc_idx');
|
||||
$self->_bz_store_real_schema;
|
||||
}
|
||||
if ($longdescs_index_deleted) {
|
||||
$self->_bz_real_schema->delete_index('longdescs',
|
||||
'longdescs_thetext_idx');
|
||||
$self->_bz_store_real_schema;
|
||||
}
|
||||
|
||||
# The old timestamp fields need to be adjusted here instead of in
|
||||
# checksetup. Otherwise the UPDATE statements inside of bz_add_column
|
||||
# will cause accidental timestamp updates.
|
||||
# The code that does this was moved here from checksetup.
|
||||
|
||||
# 2002-08-14 - bbaetz@student.usyd.edu.au - bug 153578
|
||||
# attachments creation time needs to be a datetime, not a timestamp
|
||||
my $attach_creation =
|
||||
$self->bz_column_info("attachments", "creation_ts");
|
||||
if ($attach_creation && $attach_creation->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
print "Fixing creation time on attachments...\n";
|
||||
|
||||
my $sth = $self->prepare("SELECT COUNT(attach_id) FROM attachments");
|
||||
$sth->execute();
|
||||
my ($attach_count) = $sth->fetchrow_array();
|
||||
|
||||
if ($attach_count > 1000) {
|
||||
print "This may take a while...\n";
|
||||
}
|
||||
my $i = 0;
|
||||
|
||||
# This isn't just as simple as changing the field type, because
|
||||
# the creation_ts was previously updated when an attachment was made
|
||||
# obsolete from the attachment creation screen. So we have to go
|
||||
# and recreate these times from the comments..
|
||||
$sth = $self->prepare("SELECT bug_id, attach_id, submitter_id " .
|
||||
"FROM attachments");
|
||||
$sth->execute();
|
||||
|
||||
# Restrict this as much as possible in order to avoid false
|
||||
# positives, and keep the db search time down
|
||||
my $sth2 = $self->prepare("SELECT bug_when FROM longdescs
|
||||
WHERE bug_id=? AND who=?
|
||||
AND thetext LIKE ?
|
||||
ORDER BY bug_when " . $self->sql_limit(1));
|
||||
while (my ($bug_id, $attach_id, $submitter_id)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
$sth2->execute($bug_id, $submitter_id,
|
||||
"Created an attachment (id=$attach_id)%");
|
||||
my ($when) = $sth2->fetchrow_array();
|
||||
if ($when) {
|
||||
$self->do("UPDATE attachments " .
|
||||
"SET creation_ts='$when' " .
|
||||
"WHERE attach_id=$attach_id");
|
||||
} else {
|
||||
print "Warning - could not determine correct creation"
|
||||
. " time for attachment $attach_id on bug $bug_id\n";
|
||||
}
|
||||
++$i;
|
||||
print "Converted $i of $attach_count attachments\n" if !($i % 1000);
|
||||
}
|
||||
print "Done - converted $i attachments\n";
|
||||
|
||||
$self->bz_alter_column("attachments", "creation_ts",
|
||||
{TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2004-08-29 - Tomas.Kopal@altap.cz, bug 257303
|
||||
# Change logincookies.lastused type from timestamp to datetime
|
||||
my $login_lastused = $self->bz_column_info("logincookies", "lastused");
|
||||
if ($login_lastused && $login_lastused->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
$self->bz_alter_column('logincookies', 'lastused',
|
||||
{ TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2005-01-17 - Tomas.Kopal@altap.cz, bug 257315
|
||||
# Change bugs.delta_ts type from timestamp to datetime
|
||||
my $bugs_deltats = $self->bz_column_info("bugs", "delta_ts");
|
||||
if ($bugs_deltats && $bugs_deltats->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
$self->bz_alter_column('bugs', 'delta_ts',
|
||||
{TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2005-09-24 - bugreport@peshkin.net, bug 307602
|
||||
# Make sure that default 4G table limit is overridden
|
||||
my $row = $self->selectrow_hashref("SHOW TABLE STATUS LIKE 'attach_data'");
|
||||
if ($$row{'Create_options'} !~ /MAX_ROWS/i) {
|
||||
print "Converting attach_data maximum size to 100G...\n";
|
||||
$self->do("ALTER TABLE attach_data
|
||||
AVG_ROW_LENGTH=1000000,
|
||||
MAX_ROWS=100000");
|
||||
}
|
||||
|
||||
# Convert the database to UTF-8 if the utf8 parameter is on.
|
||||
# We check if any table isn't utf8, because lots of crazy
|
||||
# partial-conversion situations can happen, and this handles anything
|
||||
# that could come up (including having the DB charset be utf8 but not
|
||||
# the table charsets.
|
||||
my $utf_table_status =
|
||||
$self->selectall_arrayref("SHOW TABLE STATUS", {Slice=>{}});
|
||||
$self->_after_table_status([map($_->{Name}, @$utf_table_status)]);
|
||||
my @non_utf8_tables = grep($_->{Collation} !~ /^utf8/, @$utf_table_status);
|
||||
|
||||
if (Bugzilla->params->{'utf8'} && scalar @non_utf8_tables) {
|
||||
print <<EOT;
|
||||
|
||||
WARNING: We are about to convert your table storage format to UTF8. This
|
||||
allows Bugzilla to correctly store and sort international characters.
|
||||
However, if you have any non-UTF-8 data in your database,
|
||||
it ***WILL BE DELETED*** by this process. So, before
|
||||
you continue with checksetup.pl, if you have any non-UTF-8
|
||||
data (or even if you're not sure) you should press Ctrl-C now
|
||||
to interrupt checksetup.pl, and run contrib/recode.pl to make all
|
||||
the data in your database into UTF-8. You should also back up your
|
||||
database before continuing. This will affect every single table
|
||||
in the database, even non-Bugzilla tables.
|
||||
|
||||
If you ever used a version of Bugzilla before 2.22, we STRONGLY
|
||||
recommend that you stop checksetup.pl NOW and run contrib/recode.pl.
|
||||
|
||||
EOT
|
||||
|
||||
if (!Bugzilla->installation_answers->{NO_PAUSE}) {
|
||||
if (Bugzilla->installation_mode ==
|
||||
INSTALLATION_MODE_NON_INTERACTIVE)
|
||||
{
|
||||
print <<EOT;
|
||||
Re-run checksetup.pl in interactive mode (without an 'answers' file)
|
||||
to continue.
|
||||
EOT
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
print " Press Enter to continue or Ctrl-C to exit...";
|
||||
getc;
|
||||
}
|
||||
}
|
||||
|
||||
print "Converting table storage format to UTF-8. This may take a",
|
||||
" while.\n";
|
||||
foreach my $table ($self->bz_table_list_real) {
|
||||
my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
|
||||
$info_sth->execute();
|
||||
while (my $column = $info_sth->fetchrow_hashref) {
|
||||
# Our conversion code doesn't work on enum fields, but they
|
||||
# all go away later in checksetup anyway.
|
||||
next if $column->{Type} =~ /enum/i;
|
||||
|
||||
# If this particular column isn't stored in utf-8
|
||||
if ($column->{Collation}
|
||||
&& $column->{Collation} ne 'NULL'
|
||||
&& $column->{Collation} !~ /utf8/)
|
||||
{
|
||||
my $name = $column->{Field};
|
||||
|
||||
# The code below doesn't work on a field with a FULLTEXT
|
||||
# index. So we drop it, which we'd do later anyway.
|
||||
if ($table eq 'longdescs' && $name eq 'thetext') {
|
||||
$self->bz_drop_index('longdescs',
|
||||
'longdescs_thetext_idx');
|
||||
}
|
||||
if ($table eq 'bugs' && $name eq 'short_desc') {
|
||||
$self->bz_drop_index('bugs', 'bugs_short_desc_idx');
|
||||
}
|
||||
my %ft_indexes;
|
||||
if ($table eq 'bugs_fulltext') {
|
||||
%ft_indexes = $self->_bz_real_schema->get_indexes_on_column_abstract(
|
||||
'bugs_fulltext', $name);
|
||||
foreach my $index (keys %ft_indexes) {
|
||||
$self->bz_drop_index('bugs_fulltext', $index);
|
||||
}
|
||||
}
|
||||
|
||||
print "Converting $table.$name to be stored as UTF-8...\n";
|
||||
my $col_info =
|
||||
$self->bz_column_info_real($table, $name);
|
||||
|
||||
# CHANGE COLUMN doesn't take PRIMARY KEY
|
||||
delete $col_info->{PRIMARYKEY};
|
||||
|
||||
my $sql_def = $self->_bz_schema->get_type_ddl($col_info);
|
||||
# We don't want MySQL to actually try to *convert*
|
||||
# from our current charset to UTF-8, we just want to
|
||||
# transfer the bytes directly. This is how we do that.
|
||||
|
||||
# The CHARACTER SET part of the definition has to come
|
||||
# right after the type, which will always come first.
|
||||
my ($binary, $utf8) = ($sql_def, $sql_def);
|
||||
my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
|
||||
$binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
|
||||
$utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET utf8/;
|
||||
$self->do("ALTER TABLE $table CHANGE COLUMN $name $name
|
||||
$binary");
|
||||
$self->do("ALTER TABLE $table CHANGE COLUMN $name $name
|
||||
$utf8");
|
||||
|
||||
if ($table eq 'bugs_fulltext') {
|
||||
foreach my $index (keys %ft_indexes) {
|
||||
$self->bz_add_index('bugs_fulltext', $index,
|
||||
$ft_indexes{$index});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
|
||||
} # foreach my $table (@tables)
|
||||
}
|
||||
|
||||
# Sometimes you can have a situation where all the tables are utf8,
|
||||
# but the database isn't. (This tends to happen when you've done
|
||||
# a mysqldump.) So we have this change outside of the above block,
|
||||
# so that it just happens silently if no actual *table* conversion
|
||||
# needs to happen.
|
||||
if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
|
||||
$self->_alter_db_charset_to_utf8();
|
||||
}
|
||||
}
|
||||
|
||||
# There is a bug in MySQL 4.1.0 - 4.1.15 that makes certain SELECT
|
||||
# statements fail after a SHOW TABLE STATUS:
|
||||
# http://bugs.mysql.com/bug.php?id=13535
|
||||
# This is a workaround, a dummy SELECT to reset the LAST_INSERT_ID.
|
||||
sub _after_table_status {
|
||||
my ($self, $tables) = @_;
|
||||
if (grep($_ eq 'bugs', @$tables)
|
||||
&& $self->bz_column_info_real("bugs", "bug_id"))
|
||||
{
|
||||
$self->do('SELECT 1 FROM bugs WHERE bug_id IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
sub _alter_db_charset_to_utf8 {
|
||||
my $self = shift;
|
||||
my $db_name = Bugzilla->localconfig->{db_name};
|
||||
$self->do("ALTER DATABASE $db_name CHARACTER SET utf8");
|
||||
}
|
||||
|
||||
sub bz_db_is_utf8 {
|
||||
my $self = shift;
|
||||
my $db_collation = $self->selectrow_arrayref(
|
||||
"SHOW VARIABLES LIKE 'character_set_database'");
|
||||
# First column holds the variable name, second column holds the value.
|
||||
return $db_collation->[1] =~ /utf8/ ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
sub bz_enum_initial_values {
|
||||
my ($self) = @_;
|
||||
my %enum_values = %{$self->ENUM_DEFAULTS};
|
||||
# Get a complete description of the 'bugs' table; with DBD::MySQL
|
||||
# there isn't a column-by-column way of doing this. Could use
|
||||
# $dbh->column_info, but it would go slower and we would have to
|
||||
# use the undocumented mysql_type_name accessor to get the type
|
||||
# of each row.
|
||||
my $sth = $self->prepare("DESCRIBE bugs");
|
||||
$sth->execute();
|
||||
# Look for the particular columns we are interested in.
|
||||
while (my ($thiscol, $thistype) = $sth->fetchrow_array()) {
|
||||
if (defined $enum_values{$thiscol}) {
|
||||
# this is a column of interest.
|
||||
my @value_list;
|
||||
if ($thistype and ($thistype =~ /^enum\(/)) {
|
||||
# it has an enum type; get the set of values.
|
||||
while ($thistype =~ /'([^']*)'(.*)/) {
|
||||
push(@value_list, $1);
|
||||
$thistype = $2;
|
||||
}
|
||||
}
|
||||
if (@value_list) {
|
||||
# record the enum values found.
|
||||
$enum_values{$thiscol} = \@value_list;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \%enum_values;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# MySQL-specific Database-Reading Methods
|
||||
#####################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 MYSQL-SPECIFIC DATABASE-READING METHODS
|
||||
|
||||
These methods read information about the database from the disk,
|
||||
instead of from a Schema object. They are only reliable for MySQL
|
||||
(see bug 285111 for the reasons why not all DBs use/have functions
|
||||
like this), but that's OK because we only need them for
|
||||
backwards-compatibility anyway, for versions of Bugzilla before 2.20.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<bz_column_info_real($table, $column)>
|
||||
|
||||
Description: Returns an abstract column definition for a column
|
||||
as it actually exists on disk in the database.
|
||||
Params: $table - The name of the table the column is on.
|
||||
$column - The name of the column you want info about.
|
||||
Returns: An abstract column definition.
|
||||
If the column does not exist, returns undef.
|
||||
|
||||
=cut
|
||||
|
||||
sub bz_column_info_real {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
# DBD::mysql does not support selecting a specific column,
|
||||
# so we have to get all the columns on the table and find
|
||||
# the one we want.
|
||||
my $info_sth = $self->column_info(undef, undef, $table, '%');
|
||||
|
||||
# Don't use fetchall_hashref as there's a Win32 DBI bug (292821)
|
||||
my $col_data;
|
||||
while ($col_data = $info_sth->fetchrow_hashref) {
|
||||
last if $col_data->{'COLUMN_NAME'} eq $column;
|
||||
}
|
||||
|
||||
if (!defined $col_data) {
|
||||
return undef;
|
||||
}
|
||||
return $self->_bz_schema->column_info_to_column($col_data);
|
||||
}
|
||||
|
||||
=item C<bz_index_info_real($table, $index)>
|
||||
|
||||
Description: Returns information about an index on a table in the database.
|
||||
Params: $table = name of table containing the index
|
||||
$index = name of an index
|
||||
Returns: An abstract index definition, always in hashref format.
|
||||
If the index does not exist, the function returns undef.
|
||||
=cut
|
||||
sub bz_index_info_real {
|
||||
my ($self, $table, $index) = @_;
|
||||
|
||||
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
||||
$sth->execute;
|
||||
|
||||
my @fields;
|
||||
my $index_type;
|
||||
# $raw_def will be an arrayref containing the following information:
|
||||
# 0 = name of the table that the index is on
|
||||
# 1 = 0 if unique, 1 if not unique
|
||||
# 2 = name of the index
|
||||
# 3 = seq_in_index (The order of the current field in the index).
|
||||
# 4 = Name of ONE column that the index is on
|
||||
# 5 = 'Collation' of the index. Usually 'A'.
|
||||
# 6 = Cardinality. Either a number or undef.
|
||||
# 7 = sub_part. Usually undef. Sometimes 1.
|
||||
# 8 = "packed". Usually undef.
|
||||
# 9 = Null. Sometimes undef, sometimes 'YES'.
|
||||
# 10 = Index_type. The type of the index. Usually either 'BTREE' or 'FULLTEXT'
|
||||
# 11 = 'Comment.' Usually undef.
|
||||
while (my $raw_def = $sth->fetchrow_arrayref) {
|
||||
if ($raw_def->[2] eq $index) {
|
||||
push(@fields, $raw_def->[4]);
|
||||
# No index can be both UNIQUE and FULLTEXT, that's why
|
||||
# this is written this way.
|
||||
$index_type = $raw_def->[1] ? '' : 'UNIQUE';
|
||||
$index_type = $raw_def->[10] eq 'FULLTEXT'
|
||||
? 'FULLTEXT' : $index_type;
|
||||
}
|
||||
}
|
||||
|
||||
my $retval;
|
||||
if (scalar(@fields)) {
|
||||
$retval = {FIELDS => \@fields, TYPE => $index_type};
|
||||
}
|
||||
return $retval;
|
||||
}
|
||||
|
||||
=item C<bz_index_list_real($table)>
|
||||
|
||||
Description: Returns a list of index names on a table in
|
||||
the database, as it actually exists on disk.
|
||||
Params: $table - The name of the table you want info about.
|
||||
Returns: An array of index names.
|
||||
|
||||
=cut
|
||||
|
||||
sub bz_index_list_real {
|
||||
my ($self, $table) = @_;
|
||||
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
||||
# Column 3 of a SHOW INDEX statement contains the name of the index.
|
||||
return @{ $self->selectcol_arrayref($sth, {Columns => [3]}) };
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# MySQL-Specific "Schema Builder"
|
||||
#####################################################################
|
||||
|
||||
=back
|
||||
|
||||
=head1 MYSQL-SPECIFIC "SCHEMA BUILDER"
|
||||
|
||||
MySQL needs to be able to read in a legacy database (from before
|
||||
Schema existed) and create a Schema object out of it. That's what
|
||||
this code does.
|
||||
|
||||
=end private
|
||||
|
||||
=cut
|
||||
|
||||
# This sub itself is actually written generically, but the subroutines
|
||||
# that it depends on are database-specific. In particular, the
|
||||
# bz_column_info_real function would be very difficult to create
|
||||
# properly for any other DB besides MySQL.
|
||||
sub _bz_build_schema_from_disk {
|
||||
my ($self) = @_;
|
||||
|
||||
print "Building Schema object from database...\n";
|
||||
|
||||
my $schema = $self->_bz_schema->get_empty_schema();
|
||||
|
||||
my @tables = $self->bz_table_list_real();
|
||||
foreach my $table (@tables) {
|
||||
$schema->add_table($table);
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
foreach my $column (@columns) {
|
||||
my $type_info = $self->bz_column_info_real($table, $column);
|
||||
$schema->set_column($table, $column, $type_info);
|
||||
}
|
||||
|
||||
my @indexes = $self->bz_index_list_real($table);
|
||||
foreach my $index (@indexes) {
|
||||
unless ($index eq 'PRIMARY') {
|
||||
my $index_info = $self->bz_index_info_real($table, $index);
|
||||
($index_info = $index_info->{FIELDS})
|
||||
if (!$index_info->{TYPE});
|
||||
$schema->set_index($table, $index, $index_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
1;
|
||||
@@ -1,608 +0,0 @@
|
||||
# -*- 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 Oracle Corporation.
|
||||
# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
|
||||
# Xiaoou Wu <xiaoou.wu@oracle.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Oracle - Bugzilla database compatibility layer for Oracle
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with Oracle
|
||||
specific implementation. It is instantiated by the Bugzilla::DB module
|
||||
and should never be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Oracle;
|
||||
|
||||
use strict;
|
||||
|
||||
use DBD::Oracle;
|
||||
use DBD::Oracle qw(:ora_types);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
#####################################################################
|
||||
# Constants
|
||||
#####################################################################
|
||||
use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
|
||||
use constant ISOLATION_LEVEL => 'READ COMMITTED';
|
||||
use constant BLOB_TYPE => { ora_type => ORA_BLOB };
|
||||
use constant GROUPBY_REGEXP => '((CASE\s+WHEN.+END)|(TO_CHAR\(.+\))|(\(SCORE.+\))|(\(MATCH.+\))|(\w+(\.\w+)?))(\s+AS\s+)?(.*)?$';
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port) = @_;
|
||||
|
||||
# You can never connect to Oracle without a DB name,
|
||||
# and there is no default DB.
|
||||
$dbname ||= Bugzilla->localconfig->{db_name};
|
||||
|
||||
# Set the language enviroment
|
||||
$ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:Oracle:host=$host;sid=$dbname";
|
||||
$dsn .= ";port=$port" if $port;
|
||||
my $attrs = { FetchHashKeyName => 'NAME_lc',
|
||||
LongReadLen => ( Bugzilla->params->{'maxattachmentsize'}
|
||||
|| 1000 ) * 1024,
|
||||
};
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
# Set the session's default date format to match MySQL
|
||||
$self->do("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
||||
$self->do("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
||||
$self->do("ALTER SESSION SET NLS_LENGTH_SEMANTICS='CHAR'")
|
||||
if Bugzilla->params->{'utf8'};
|
||||
# To allow case insensitive query.
|
||||
$self->do("ALTER SESSION SET NLS_COMP='ANSI'");
|
||||
$self->do("ALTER SESSION SET NLS_SORT='BINARY_AI'");
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub bz_last_key {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
my $seq = $table . "_" . $column . "_SEQ";
|
||||
my ($last_insert_id) = $self->selectrow_array("SELECT $seq.CURRVAL "
|
||||
. " FROM DUAL");
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "REGEXP_LIKE($expr, $pattern)";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "NOT REGEXP_LIKE($expr, $pattern)"
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if(defined $offset) {
|
||||
return "/* LIMIT $limit $offset */";
|
||||
}
|
||||
return "/* LIMIT $limit */";
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
return 'CONCAT(' . join(', ', @params) . ')';
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return " TO_CHAR(TO_DATE($date),'J') ";
|
||||
}
|
||||
sub sql_from_days{
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return " TO_DATE($date,'J') ";
|
||||
}
|
||||
sub sql_fulltext_search {
|
||||
my ($self, $column, $text, $label) = @_;
|
||||
$text = $self->quote($text);
|
||||
trick_taint($text);
|
||||
return "CONTAINS($column,$text,$label)", "SCORE($label)";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
$format =~ s/\%Y/YYYY/g;
|
||||
$format =~ s/\%y/YY/g;
|
||||
$format =~ s/\%m/MM/g;
|
||||
$format =~ s/\%d/DD/g;
|
||||
$format =~ s/\%a/Dy/g;
|
||||
$format =~ s/\%H/HH24/g;
|
||||
$format =~ s/\%i/MI/g;
|
||||
$format =~ s/\%s/SS/g;
|
||||
|
||||
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
if ($units =~ /YEAR|MONTH/i) {
|
||||
return "NUMTOYMINTERVAL($interval,'$units')";
|
||||
} else{
|
||||
return "NUMTODSINTERVAL($interval,'$units')";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_position {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
return "INSTR($text, $fragment)";
|
||||
}
|
||||
|
||||
sub sql_in {
|
||||
my ($self, $column_name, $in_list_ref) = @_;
|
||||
my @in_list = @$in_list_ref;
|
||||
return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
|
||||
my @in_str;
|
||||
while (@in_list) {
|
||||
my $length = $#in_list + 1;
|
||||
my $splice = $length > 1000 ? 1000 : $length;
|
||||
my @sub_in_list = splice(@in_list, 0, $splice);
|
||||
push(@in_str,
|
||||
$self->SUPER::sql_in($column_name, \@sub_in_list));
|
||||
}
|
||||
return "( " . join(" OR ", @in_str) . " )";
|
||||
}
|
||||
|
||||
sub bz_drop_table {
|
||||
my ($self, $name) = @_;
|
||||
my $table_exists = $self->bz_table_info($name);
|
||||
if ($table_exists) {
|
||||
$self->_bz_drop_fks($name);
|
||||
$self->SUPER::bz_drop_table($name);
|
||||
}
|
||||
}
|
||||
|
||||
# Dropping all FKs for a specified table.
|
||||
sub _bz_drop_fks {
|
||||
my ($self, $table) = @_;
|
||||
my @columns = $self->_bz_real_schema->get_table_columns($table);
|
||||
foreach my $column (@columns) {
|
||||
$self->bz_drop_fk($table, $column);
|
||||
}
|
||||
}
|
||||
|
||||
sub _fix_empty {
|
||||
my ($string) = @_;
|
||||
$string = '' if $string eq EMPTY_STRING;
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub _fix_arrayref {
|
||||
my ($row) = @_;
|
||||
return undef if !defined $row;
|
||||
foreach my $field (@$row) {
|
||||
$field = _fix_empty($field) if defined $field;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
sub _fix_hashref {
|
||||
my ($row) = @_;
|
||||
return undef if !defined $row;
|
||||
foreach my $value (values %$row) {
|
||||
$value = _fix_empty($value) if defined $value;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
sub adjust_statement {
|
||||
my ($sql) = @_;
|
||||
|
||||
# We can't just assume any occurrence of "''" in $sql is an empty
|
||||
# string, since "''" can occur inside a string literal as a way of
|
||||
# escaping a single "'" in the literal. Therefore we must be trickier...
|
||||
|
||||
# split the statement into parts by single-quotes. The negative value
|
||||
# at the end to the split operator from dropping trailing empty strings
|
||||
# (e.g., when $sql ends in "''")
|
||||
my @parts = split /'/, $sql, -1;
|
||||
|
||||
if( !(@parts % 2) ) {
|
||||
# Either the string is empty or the quotes are mismatched
|
||||
# Returning input unmodified.
|
||||
return $sql;
|
||||
}
|
||||
|
||||
# We already verified that we have an odd number of parts. If we take
|
||||
# the first part off now, we know we're entering the loop with an even
|
||||
# number of parts
|
||||
my @result;
|
||||
my $part = shift @parts;
|
||||
|
||||
# Oracle requires a FROM clause in all SELECT statements, so append
|
||||
# "FROM dual" to queries without one (e.g., "SELECT NOW()")
|
||||
my $is_select = ($part =~ m/^\s*SELECT\b/io);
|
||||
my $has_from = ($part =~ m/\bFROM\b/io) if $is_select;
|
||||
|
||||
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
||||
$part =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
||||
|
||||
# Oracle use SUBSTR instead of SUBSTRING
|
||||
$part =~ s/\bSUBSTRING\b/SUBSTR/io;
|
||||
|
||||
# Oracle need no 'AS'
|
||||
$part =~ s/\bAS\b//ig;
|
||||
|
||||
# Oracle doesn't have LIMIT, so if we find the LIMIT comment, wrap the
|
||||
# query with "SELECT * FROM (...) WHERE rownum < $limit"
|
||||
my ($limit,$offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o);
|
||||
|
||||
push @result, $part;
|
||||
while( @parts ) {
|
||||
my $string = shift @parts;
|
||||
my $nonstring = shift @parts;
|
||||
|
||||
# if the non-string part is zero-length and there are more parts left,
|
||||
# then this is an escaped quote inside a string literal
|
||||
while( !(length $nonstring) && @parts ) {
|
||||
# we know it's safe to remove two parts at a time, since we
|
||||
# entered the loop with an even number of parts
|
||||
$string .= "''" . shift @parts;
|
||||
$nonstring = shift @parts;
|
||||
}
|
||||
|
||||
# Look for a FROM if this is a SELECT and we haven't found one yet
|
||||
$has_from = ($nonstring =~ m/\bFROM\b/io)
|
||||
if ($is_select and !$has_from);
|
||||
|
||||
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
||||
$nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
||||
|
||||
# Oracle use SUBSTR instead of SUBSTRING
|
||||
$nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
|
||||
|
||||
# Oracle need no 'AS'
|
||||
$nonstring =~ s/\bAS\b//ig;
|
||||
|
||||
# Look for a LIMIT clause
|
||||
($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
|
||||
|
||||
if(!length($string)){
|
||||
push @result, EMPTY_STRING;
|
||||
push @result, $nonstring;
|
||||
} else {
|
||||
push @result, $string;
|
||||
push @result, $nonstring;
|
||||
}
|
||||
}
|
||||
|
||||
my $new_sql = join "'", @result;
|
||||
|
||||
# Append "FROM dual" if this is a SELECT without a FROM clause
|
||||
$new_sql .= " FROM DUAL" if ($is_select and !$has_from);
|
||||
|
||||
# Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
|
||||
|
||||
if (defined($limit)) {
|
||||
if ($new_sql !~ /\bWHERE\b/) {
|
||||
$new_sql = $new_sql." WHERE 1=1";
|
||||
}
|
||||
my ($before_where, $after_where) = split /\bWHERE\b/i,$new_sql;
|
||||
if (defined($offset)) {
|
||||
if ($new_sql =~ /(.*\s+)FROM(\s+.*)/i) {
|
||||
my ($before_from,$after_from) = ($1,$2);
|
||||
$before_where = "$before_from FROM ($before_from,"
|
||||
. " ROW_NUMBER() OVER (ORDER BY 1) R "
|
||||
. " FROM $after_from ) ";
|
||||
$after_where = " R BETWEEN $offset+1 AND $limit+$offset";
|
||||
}
|
||||
} else {
|
||||
$after_where = " rownum <=$limit AND ".$after_where;
|
||||
}
|
||||
|
||||
$new_sql = $before_where." WHERE ".$after_where;
|
||||
}
|
||||
return $new_sql;
|
||||
}
|
||||
|
||||
sub do {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
$sql = adjust_statement($sql);
|
||||
unshift @_, $sql;
|
||||
return $self->SUPER::do(@_);
|
||||
}
|
||||
|
||||
sub selectrow_array {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
if ( wantarray ) {
|
||||
my @row = $self->SUPER::selectrow_array(@_);
|
||||
_fix_arrayref(\@row);
|
||||
return @row;
|
||||
} else {
|
||||
my $row = $self->SUPER::selectrow_array(@_);
|
||||
$row = _fix_empty($row) if defined $row;
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
sub selectrow_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectrow_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectrow_hashref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectrow_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
_fix_hashref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectall_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectall_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
foreach my $row (@$ref) {
|
||||
if (ref($row) eq 'ARRAY') {
|
||||
_fix_arrayref($row);
|
||||
}
|
||||
elsif (ref($row) eq 'HASH') {
|
||||
_fix_hashref($row);
|
||||
}
|
||||
}
|
||||
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectall_hashref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $rows = $self->SUPER::selectall_hashref(@_);
|
||||
return undef if !defined $rows;
|
||||
foreach my $row (values %$rows) {
|
||||
_fix_hashref($row);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
sub selectcol_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectcol_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub prepare {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
my $new_sql = adjust_statement($sql);
|
||||
unshift @_, $new_sql;
|
||||
return bless $self->SUPER::prepare(@_),
|
||||
'Bugzilla::DB::Oracle::st';
|
||||
}
|
||||
|
||||
sub prepare_cached {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
my $new_sql = adjust_statement($sql);
|
||||
unshift @_, $new_sql;
|
||||
return bless $self->SUPER::prepare_cached(@_),
|
||||
'Bugzilla::DB::Oracle::st';
|
||||
}
|
||||
|
||||
sub quote_identifier {
|
||||
my ($self,$id) = @_;
|
||||
return $id;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Protected "Real Database" Schema Information Methods
|
||||
#####################################################################
|
||||
|
||||
sub bz_table_columns_real {
|
||||
my ($self, $table) = @_;
|
||||
$table = uc($table);
|
||||
my $cols = $self->selectcol_arrayref(
|
||||
"SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE
|
||||
TABLE_NAME = ? ORDER BY COLUMN_NAME", undef, $table);
|
||||
return @$cols;
|
||||
}
|
||||
|
||||
sub bz_table_list_real {
|
||||
my ($self) = @_;
|
||||
my $tables = $self->selectcol_arrayref(
|
||||
"SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE
|
||||
TABLE_NAME NOT LIKE ? ORDER BY TABLE_NAME", undef, 'DR$%');
|
||||
return @$tables;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my $self = shift;
|
||||
|
||||
# Create a function that returns SYSDATE to emulate MySQL's "NOW()".
|
||||
# Function NOW() is used widely in Bugzilla SQLs, but Oracle does not
|
||||
# have that function, So we have to create one ourself.
|
||||
$self->do("CREATE OR REPLACE FUNCTION NOW "
|
||||
. " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
|
||||
$self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
|
||||
. " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
|
||||
# Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
|
||||
my $lexer = $self->selectcol_arrayref(
|
||||
"SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
|
||||
pre_owner = ?",
|
||||
undef,'BZ_LEX',uc(Bugzilla->localconfig->{db_user}));
|
||||
if(!@$lexer) {
|
||||
$self->do("BEGIN CTX_DDL.CREATE_PREFERENCE
|
||||
('BZ_LEX', 'WORLD_LEXER'); END;");
|
||||
}
|
||||
|
||||
$self->SUPER::bz_setup_database(@_);
|
||||
|
||||
my @tables = $self->bz_table_list_real();
|
||||
foreach my $table (@tables) {
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
foreach my $column (@columns) {
|
||||
my $def = $self->bz_column_info($table, $column);
|
||||
if ($def->{REFERENCES}) {
|
||||
my $references = $def->{REFERENCES};
|
||||
my $update = $references->{UPDATE} || 'CASCADE';
|
||||
my $to_table = $references->{TABLE};
|
||||
my $to_column = $references->{COLUMN};
|
||||
my $fk_name = $self->_bz_schema->_get_fk_name($table,
|
||||
$column,
|
||||
$references);
|
||||
if ( $update =~ /CASCADE/i ){
|
||||
my $trigger_name = uc($fk_name . "_UC");
|
||||
my $exist_trigger = $self->selectcol_arrayref(
|
||||
"SELECT OBJECT_NAME FROM USER_OBJECTS
|
||||
WHERE OBJECT_NAME = ?", undef, $trigger_name);
|
||||
if(@$exist_trigger) {
|
||||
$self->do("DROP TRIGGER $trigger_name");
|
||||
}
|
||||
|
||||
my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
|
||||
. " AFTER UPDATE ON ". $to_table
|
||||
. " REFERENCING "
|
||||
. " NEW AS NEW "
|
||||
. " OLD AS OLD "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " UPDATE $table"
|
||||
. " SET $column = :NEW.$to_column"
|
||||
. " WHERE $column = :OLD.$to_column;"
|
||||
. " END $trigger_name;";
|
||||
$self->do($tr_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package Bugzilla::DB::Oracle::st;
|
||||
use base qw(DBI::st);
|
||||
|
||||
sub fetchrow_arrayref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchrow_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchrow_array {
|
||||
my $self = shift;
|
||||
if ( wantarray ) {
|
||||
my @row = $self->SUPER::fetchrow_array(@_);
|
||||
Bugzilla::DB::Oracle::_fix_arrayref(\@row);
|
||||
return @row;
|
||||
} else {
|
||||
my $row = $self->SUPER::fetchrow_array(@_);
|
||||
$row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
sub fetchrow_hashref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchrow_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
Bugzilla::DB::Oracle::_fix_hashref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchall_arrayref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchall_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
foreach my $row (@$ref) {
|
||||
if (ref($row) eq 'ARRAY') {
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
||||
}
|
||||
elsif (ref($row) eq 'HASH') {
|
||||
Bugzilla::DB::Oracle::_fix_hashref($row);
|
||||
}
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchall_hashref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchall_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
foreach my $row (values %$ref) {
|
||||
Bugzilla::DB::Oracle::_fix_hashref($row);
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
my $row = $self->SUPER::fetch(@_);
|
||||
if ($row) {
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
1;
|
||||
@@ -1,261 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Dave Miller <davem00@aol.com>
|
||||
# Gayathri Swaminath <gayathrik00@aol.com>
|
||||
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with PostgreSQL
|
||||
specific implementation. It is instantiated by the Bugzilla::DB module
|
||||
and should never be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Pg;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use DBD::Pg;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port) = @_;
|
||||
|
||||
# The default database name for PostgreSQL. We have
|
||||
# to connect to SOME database, even if we have
|
||||
# no $dbname parameter.
|
||||
$dbname ||= 'template1';
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:Pg:dbname=$dbname";
|
||||
$dsn .= ";host=$host" if $host;
|
||||
$dsn .= ";port=$port" if $port;
|
||||
|
||||
# This stops Pg from printing out lots of "NOTICE" messages when
|
||||
# creating tables.
|
||||
$dsn .= ";options='-c client_min_messages=warning'";
|
||||
|
||||
my $attrs = { pg_enable_utf8 => Bugzilla->params->{'utf8'} };
|
||||
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
|
||||
# all class local variables stored in DBI derived class needs to have
|
||||
# a prefix 'private_'. See DBI documentation.
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# if last_insert_id is supported on PostgreSQL by lowest DBI/DBD version
|
||||
# supported by Bugzilla, this implementation can be removed.
|
||||
sub bz_last_key {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
my $seq = $table . "_" . $column . "_seq";
|
||||
my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr ~* $pattern";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern) = @_;
|
||||
|
||||
return "$expr !~* $pattern"
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if (defined($offset)) {
|
||||
return "LIMIT $limit OFFSET $offset";
|
||||
} else {
|
||||
return "LIMIT $limit";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_from_days {
|
||||
my ($self, $days) = @_;
|
||||
|
||||
return "TO_TIMESTAMP(${days}::int, 'J')::date";
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return "TO_CHAR(${date}::date, 'J')::int";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
$format =~ s/\%Y/YYYY/g;
|
||||
$format =~ s/\%y/YY/g;
|
||||
$format =~ s/\%m/MM/g;
|
||||
$format =~ s/\%d/DD/g;
|
||||
$format =~ s/\%a/Dy/g;
|
||||
$format =~ s/\%H/HH24/g;
|
||||
$format =~ s/\%i/MI/g;
|
||||
$format =~ s/\%s/SS/g;
|
||||
|
||||
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
|
||||
return "$interval * INTERVAL '1 $units'";
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
# Postgres 7.3 does not support concatenating of different types, so we
|
||||
# need to cast both parameters to text. Version 7.4 seems to handle this
|
||||
# properly, so when we stop support 7.3, this can be removed.
|
||||
return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
|
||||
}
|
||||
|
||||
# Tell us whether or not a particular sequence exists in the DB.
|
||||
sub bz_sequence_exists {
|
||||
my ($self, $seq_name) = @_;
|
||||
my $exists = $self->selectrow_array(
|
||||
'SELECT 1 FROM pg_statio_user_sequences WHERE relname = ?',
|
||||
undef, $seq_name);
|
||||
return $exists || 0;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my $self = shift;
|
||||
$self->SUPER::bz_setup_database(@_);
|
||||
|
||||
# PostgreSQL doesn't like having *any* index on the thetext
|
||||
# field, because it can't have index data longer than 2770
|
||||
# characters on that field.
|
||||
$self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
|
||||
# Same for all the comments fields in the fulltext table.
|
||||
$self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_idx');
|
||||
$self->bz_drop_index('bugs_fulltext',
|
||||
'bugs_fulltext_comments_noprivate_idx');
|
||||
|
||||
# PostgreSQL also wants an index for calling LOWER on
|
||||
# login_name, which we do with sql_istrcmp all over the place.
|
||||
$self->bz_add_index('profiles', 'profiles_login_name_lower_idx',
|
||||
{FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'});
|
||||
|
||||
# Now that Bugzilla::Object uses sql_istrcmp, other tables
|
||||
# also need a LOWER() index.
|
||||
_fix_case_differences('fielddefs', 'name');
|
||||
$self->bz_add_index('fielddefs', 'fielddefs_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
_fix_case_differences('keyworddefs', 'name');
|
||||
$self->bz_add_index('keyworddefs', 'keyworddefs_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
_fix_case_differences('products', 'name');
|
||||
$self->bz_add_index('products', 'products_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
|
||||
# bz_rename_column didn't correctly rename the sequence.
|
||||
if ($self->bz_column_info('fielddefs', 'id')
|
||||
&& $self->bz_sequence_exists('fielddefs_fieldid_seq'))
|
||||
{
|
||||
print "Fixing fielddefs_fieldid_seq sequence...\n";
|
||||
$self->do("ALTER TABLE fielddefs_fieldid_seq RENAME TO fielddefs_id_seq");
|
||||
$self->do("ALTER TABLE fielddefs ALTER COLUMN id
|
||||
SET DEFAULT NEXTVAL('fielddefs_id_seq')");
|
||||
}
|
||||
}
|
||||
|
||||
# Renames things that differ only in case.
|
||||
sub _fix_case_differences {
|
||||
my ($table, $field) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $duplicates = $dbh->selectcol_arrayref(
|
||||
"SELECT DISTINCT LOWER($field) FROM $table
|
||||
GROUP BY LOWER($field) HAVING COUNT(LOWER($field)) > 1");
|
||||
|
||||
foreach my $name (@$duplicates) {
|
||||
my $dups = $dbh->selectcol_arrayref(
|
||||
"SELECT $field FROM $table WHERE LOWER($field) = ?",
|
||||
undef, $name);
|
||||
my $primary = shift @$dups;
|
||||
foreach my $dup (@$dups) {
|
||||
my $new_name = "${dup}_";
|
||||
# Make sure the new name isn't *also* a duplicate.
|
||||
while (1) {
|
||||
last if (!$dbh->selectrow_array(
|
||||
"SELECT 1 FROM $table WHERE LOWER($field) = ?",
|
||||
undef, lc($new_name)));
|
||||
$new_name .= "_";
|
||||
}
|
||||
print "$table '$primary' and '$dup' have names that differ",
|
||||
" only in case.\nRenaming '$dup' to '$new_name'...\n";
|
||||
$dbh->do("UPDATE $table SET $field = ? WHERE $field = ?",
|
||||
undef, $new_name, $dup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Schema Information Functions
|
||||
#####################################################################
|
||||
|
||||
# Pg includes the PostgreSQL system tables in table_list_real, so
|
||||
# we need to remove those.
|
||||
sub bz_table_list_real {
|
||||
my $self = shift;
|
||||
|
||||
my @full_table_list = $self->SUPER::bz_table_list_real(@_);
|
||||
# All PostgreSQL system tables start with "pg_" or "sql_"
|
||||
my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
|
||||
return @table_list;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,369 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Mysql;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for MySQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
|
||||
# This is for column_info_to_column, to know when a tinyint is a
|
||||
# boolean and when it's really a tinyint. This only has to be accurate
|
||||
# up to and through 2.19.3, because that's the only time we need
|
||||
# column_info_to_column.
|
||||
#
|
||||
# This is basically a hash of tables/columns, with one entry for each column
|
||||
# that should be interpreted as a BOOLEAN instead of as an INT1 when
|
||||
# reading in the Schema from the disk. The values are discarded; I just
|
||||
# used "1" for simplicity.
|
||||
use constant BOOLEAN_MAP => {
|
||||
bugs => {everconfirmed => 1, reporter_accessible => 1,
|
||||
cclist_accessible => 1, qacontact_accessible => 1,
|
||||
assignee_accessible => 1},
|
||||
longdescs => {isprivate => 1, already_wrapped => 1},
|
||||
attachments => {ispatch => 1, isobsolete => 1, isprivate => 1},
|
||||
flags => {is_active => 1},
|
||||
flagtypes => {is_active => 1, is_requestable => 1,
|
||||
is_requesteeble => 1, is_multiplicable => 1},
|
||||
fielddefs => {mailhead => 1, obsolete => 1},
|
||||
bug_status => {isactive => 1},
|
||||
resolution => {isactive => 1},
|
||||
bug_severity => {isactive => 1},
|
||||
priority => {isactive => 1},
|
||||
rep_platform => {isactive => 1},
|
||||
op_sys => {isactive => 1},
|
||||
profiles => {mybugslink => 1, newemailtech => 1},
|
||||
namedqueries => {linkinfooter => 1, watchfordiffs => 1},
|
||||
groups => {isbuggroup => 1, isactive => 1},
|
||||
group_control_map => {entry => 1, membercontrol => 1, othercontrol => 1,
|
||||
canedit => 1},
|
||||
group_group_map => {isbless => 1},
|
||||
user_group_map => {isbless => 1, isderived => 1},
|
||||
products => {disallownew => 1},
|
||||
series => {public => 1},
|
||||
whine_queries => {onemailperbug => 1},
|
||||
quips => {approved => 1},
|
||||
setting => {is_enabled => 1}
|
||||
};
|
||||
|
||||
# Maps the db_specific hash backwards, for use in column_info_to_column.
|
||||
use constant REVERSE_MAPPING => {
|
||||
# Boolean and the SERIAL fields are handled in column_info_to_column,
|
||||
# and so don't have an entry here.
|
||||
TINYINT => 'INT1',
|
||||
SMALLINT => 'INT2',
|
||||
MEDIUMINT => 'INT3',
|
||||
INTEGER => 'INT4',
|
||||
|
||||
# All the other types have the same name in their abstract version
|
||||
# as in their db-specific version, so no reverse mapping is needed.
|
||||
};
|
||||
|
||||
use constant MYISAM_TABLES => qw(bugs_fulltext);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'tinyint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'tinyint',
|
||||
INT2 => 'smallint',
|
||||
INT3 => 'mediumint',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'smallint auto_increment',
|
||||
MEDIUMSERIAL => 'mediumint auto_increment',
|
||||
INTSERIAL => 'integer auto_increment',
|
||||
|
||||
TINYTEXT => 'tinytext',
|
||||
MEDIUMTEXT => 'mediumtext',
|
||||
LONGTEXT => 'mediumtext',
|
||||
|
||||
LONGBLOB => 'longblob',
|
||||
|
||||
DATETIME => 'datetime',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_table_ddl {
|
||||
# Extend superclass method to specify the MYISAM storage engine.
|
||||
# Returns a "create table" SQL statement.
|
||||
|
||||
my($self, $table) = @_;
|
||||
|
||||
my $charset = Bugzilla->dbh->bz_db_is_utf8 ? "CHARACTER SET utf8" : '';
|
||||
my $type = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
|
||||
return($self->SUPER::_get_create_table_ddl($table)
|
||||
. " ENGINE = $type $charset");
|
||||
|
||||
} #eosub--_get_create_table_ddl
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_index_ddl {
|
||||
# Extend superclass method to create FULLTEXT indexes on text fields.
|
||||
# Returns a "create index" SQL statement.
|
||||
|
||||
my($self, $table_name, $index_name, $index_fields, $index_type) = @_;
|
||||
|
||||
my $sql = "CREATE ";
|
||||
$sql .= "$index_type " if ($index_type eq 'UNIQUE'
|
||||
|| $index_type eq 'FULLTEXT');
|
||||
$sql .= "INDEX \`$index_name\` ON $table_name \(" .
|
||||
join(", ", @$index_fields) . "\)";
|
||||
|
||||
return($sql);
|
||||
|
||||
} #eosub--_get_create_index_ddl
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_create_database_sql {
|
||||
my ($self, $name) = @_;
|
||||
# We only create as utf8 if we have no params (meaning we're doing
|
||||
# a new installation) or if the utf8 param is on.
|
||||
my $create_utf8 = Bugzilla->params->{'utf8'}
|
||||
|| !defined Bugzilla->params->{'utf8'};
|
||||
my $charset = $create_utf8 ? "CHARACTER SET utf8" : '';
|
||||
return ("CREATE DATABASE $name $charset");
|
||||
}
|
||||
|
||||
# MySQL has a simpler ALTER TABLE syntax than ANSI.
|
||||
sub get_alter_column_ddl {
|
||||
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
|
||||
my $old_def = $self->get_column($table, $column);
|
||||
my %new_def_copy = %$new_def;
|
||||
if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
|
||||
# If a column stays a primary key do NOT specify PRIMARY KEY in the
|
||||
# ALTER TABLE statement. This avoids a MySQL error that two primary
|
||||
# keys are not allowed.
|
||||
delete $new_def_copy{PRIMARYKEY};
|
||||
}
|
||||
|
||||
my $new_ddl = $self->get_type_ddl(\%new_def_copy);
|
||||
my @statements;
|
||||
|
||||
push(@statements, "UPDATE $table SET $column = $set_nulls_to
|
||||
WHERE $column IS NULL") if defined $set_nulls_to;
|
||||
push(@statements, "ALTER TABLE $table CHANGE COLUMN
|
||||
$column $column $new_ddl");
|
||||
if ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
|
||||
# Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
|
||||
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_drop_fk_sql {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
my $fk_name = $self->_get_fk_name($table, $column, $references);
|
||||
my @sql = ("ALTER TABLE $table DROP FOREIGN KEY $fk_name");
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# MySQL requires, and will create, an index on any column with
|
||||
# an FK. It will name it after the fk, which we never do.
|
||||
# So if there's an index named after the fk, we also have to delete it.
|
||||
if ($dbh->bz_index_info_real($table, $fk_name)) {
|
||||
push(@sql, $self->get_drop_index_ddl($table, $fk_name));
|
||||
}
|
||||
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub get_drop_index_ddl {
|
||||
my ($self, $table, $name) = @_;
|
||||
return ("DROP INDEX \`$name\` ON $table");
|
||||
}
|
||||
|
||||
# A special function for MySQL, for renaming a lot of indexes.
|
||||
# Index renames is a hash, where the key is a string - the
|
||||
# old names of the index, and the value is a hash - the index
|
||||
# definition that we're renaming to, with an extra key of "NAME"
|
||||
# that contains the new index name.
|
||||
# The indexes in %indexes must be in hashref format.
|
||||
sub get_rename_indexes_ddl {
|
||||
my ($self, $table, %indexes) = @_;
|
||||
my @keys = keys %indexes or return ();
|
||||
|
||||
my $sql = "ALTER TABLE $table ";
|
||||
|
||||
foreach my $old_name (@keys) {
|
||||
my $name = $indexes{$old_name}->{NAME};
|
||||
my $type = $indexes{$old_name}->{TYPE};
|
||||
$type ||= 'INDEX';
|
||||
my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
|
||||
# $old_name needs to be escaped, sometimes, because it was
|
||||
# a reserved word.
|
||||
$old_name = '`' . $old_name . '`';
|
||||
$sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
|
||||
}
|
||||
# Remove the last comma.
|
||||
chop($sql);
|
||||
return ($sql);
|
||||
}
|
||||
|
||||
# Converts a DBI column_info output to an abstract column definition.
|
||||
# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk,
|
||||
# although there's a chance that it will also work properly if called
|
||||
# elsewhere.
|
||||
sub column_info_to_column {
|
||||
my ($self, $column_info) = @_;
|
||||
|
||||
# Unfortunately, we have to break Schema's normal "no database"
|
||||
# barrier a few times in this function.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $table = $column_info->{TABLE_NAME};
|
||||
my $col_name = $column_info->{COLUMN_NAME};
|
||||
|
||||
my $column = {};
|
||||
|
||||
($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
|
||||
|
||||
if ($column_info->{mysql_is_pri_key}) {
|
||||
# In MySQL, if a table has no PK, but it has a UNIQUE index,
|
||||
# that index will show up as the PK. So we have to eliminate
|
||||
# that possibility.
|
||||
# Unfortunately, the only way to definitely solve this is
|
||||
# to break Schema's standard of not touching the live database
|
||||
# and check if the index called PRIMARY is on that field.
|
||||
my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
|
||||
if ( $pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}}) ) {
|
||||
$column->{PRIMARYKEY} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# MySQL frequently defines a default for a field even when we
|
||||
# didn't explicitly set one. So we have to have some special
|
||||
# hacks to determine whether or not we should actually put
|
||||
# a default in the abstract schema for this field.
|
||||
if (defined $column_info->{COLUMN_DEF}) {
|
||||
# The defaults that MySQL inputs automatically are usually
|
||||
# something that would be considered "false" by perl, either
|
||||
# a 0 or an empty string. (Except for datetime and decimal
|
||||
# fields, which have their own special auto-defaults.)
|
||||
#
|
||||
# Here's how we handle this: If it exists in the schema
|
||||
# without a default, then we don't use the default. If it
|
||||
# doesn't exist in the schema, then we're either going to
|
||||
# be dropping it soon, or it's a custom end-user column, in which
|
||||
# case having a bogus default won't harm anything.
|
||||
my $schema_column = $self->get_column($table, $col_name);
|
||||
unless ( (!$column_info->{COLUMN_DEF}
|
||||
|| $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
|
||||
|| $column_info->{COLUMN_DEF} eq '0.00')
|
||||
&& $schema_column
|
||||
&& !exists $schema_column->{DEFAULT}) {
|
||||
|
||||
my $default = $column_info->{COLUMN_DEF};
|
||||
# Schema uses '0' for the defaults for decimal fields.
|
||||
$default = 0 if $default =~ /^0\.0+$/;
|
||||
# If we're not a number, we're a string and need to be
|
||||
# quoted.
|
||||
$default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
|
||||
$column->{DEFAULT} = $default;
|
||||
}
|
||||
}
|
||||
|
||||
my $type = $column_info->{TYPE_NAME};
|
||||
|
||||
# Certain types of columns need the size/precision appended.
|
||||
if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
|
||||
# This is nicely lowercase and has the size/precision appended.
|
||||
$type = $column_info->{mysql_type_name};
|
||||
}
|
||||
|
||||
# If we're a tinyint, we could be either a BOOLEAN or an INT1.
|
||||
# Only the BOOLEAN_MAP knows the difference.
|
||||
elsif ($type eq 'TINYINT' && exists BOOLEAN_MAP->{$table}
|
||||
&& exists BOOLEAN_MAP->{$table}->{$col_name}) {
|
||||
$type = 'BOOLEAN';
|
||||
if (exists $column->{DEFAULT}) {
|
||||
$column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
}
|
||||
|
||||
# We also need to check if we're an auto_increment field.
|
||||
elsif ($type =~ /INT/) {
|
||||
# Unfortunately, the only way to do this in DBI is to query the
|
||||
# database, so we have to break the rule here that Schema normally
|
||||
# doesn't touch the live DB.
|
||||
my $ref_sth = $dbh->prepare(
|
||||
"SELECT $col_name FROM $table LIMIT 1");
|
||||
$ref_sth->execute;
|
||||
if ($ref_sth->{mysql_is_auto_increment}->[0]) {
|
||||
if ($type eq 'MEDIUMINT') {
|
||||
$type = 'MEDIUMSERIAL';
|
||||
}
|
||||
elsif ($type eq 'SMALLINT') {
|
||||
$type = 'SMALLSERIAL';
|
||||
}
|
||||
else {
|
||||
$type = 'INTSERIAL';
|
||||
}
|
||||
}
|
||||
$ref_sth->finish;
|
||||
|
||||
}
|
||||
|
||||
# For all other db-specific types, check if they exist in
|
||||
# REVERSE_MAPPING and use the type found there.
|
||||
if (exists REVERSE_MAPPING->{$type}) {
|
||||
$type = REVERSE_MAPPING->{$type};
|
||||
}
|
||||
|
||||
$column->{TYPE} = $type;
|
||||
|
||||
#print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
my $def = $self->get_type_ddl($self->get_column($table, $old_name));
|
||||
# MySQL doesn't like having the PRIMARY KEY statement in a rename.
|
||||
$def =~ s/PRIMARY KEY//i;
|
||||
return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,403 +0,0 @@
|
||||
# -*- 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 Oracle Corporation.
|
||||
# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
|
||||
# Xiaoou Wu <xiaoou.wu@oracle.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Oracle;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for Oracle
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
use Carp qw(confess);
|
||||
use Bugzilla::Util;
|
||||
|
||||
use constant ADD_COLUMN => 'ADD';
|
||||
# Whether this is true or not, this is what it needs to be in order for
|
||||
# hash_identifier to maintain backwards compatibility with versions before
|
||||
# 3.2rc2.
|
||||
use constant MAX_IDENTIFIER_LEN => 27;
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'integer',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'integer',
|
||||
INT2 => 'integer',
|
||||
INT3 => 'integer',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'integer',
|
||||
MEDIUMSERIAL => 'integer',
|
||||
INTSERIAL => 'integer',
|
||||
|
||||
TINYTEXT => 'varchar(255)',
|
||||
MEDIUMTEXT => 'varchar(4000)',
|
||||
LONGTEXT => 'clob',
|
||||
|
||||
LONGBLOB => 'blob',
|
||||
|
||||
DATETIME => 'date',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_table_ddl {
|
||||
my $self = shift;
|
||||
my $table = shift;
|
||||
unshift @_, $table;
|
||||
my @ddl = $self->SUPER::get_table_ddl(@_);
|
||||
|
||||
my @fields = @{ $self->{abstract_schema}{$table}{FIELDS} || [] };
|
||||
while (@fields) {
|
||||
my $field_name = shift @fields;
|
||||
my $field_info = shift @fields;
|
||||
# Create triggers to deal with empty string.
|
||||
if ( $field_info->{TYPE} =~ /varchar|TEXT/i
|
||||
&& $field_info->{NOTNULL} ) {
|
||||
push (@ddl, _get_notnull_trigger_ddl($table, $field_name));
|
||||
}
|
||||
# Create sequences and triggers to emulate SERIAL datatypes.
|
||||
if ( $field_info->{TYPE} =~ /SERIAL/i ) {
|
||||
push (@ddl, $self->_get_create_seq_ddl($table, $field_name));
|
||||
}
|
||||
}
|
||||
return @ddl;
|
||||
|
||||
} #eosub--get_table_ddl
|
||||
|
||||
# Extend superclass method to create Oracle Text indexes if index type
|
||||
# is FULLTEXT from schema. Returns a "create index" SQL statement.
|
||||
sub _get_create_index_ddl {
|
||||
|
||||
my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
|
||||
$index_name = "idx_" . $self->_hash_identifier($index_name);
|
||||
if ($index_type eq 'FULLTEXT') {
|
||||
my $sql = "CREATE INDEX $index_name ON $table_name ("
|
||||
. join(',',@$index_fields)
|
||||
. ") INDEXTYPE IS CTXSYS.CONTEXT "
|
||||
. " PARAMETERS('LEXER BZ_LEX SYNC(ON COMMIT)')" ;
|
||||
return $sql;
|
||||
}
|
||||
|
||||
return($self->SUPER::_get_create_index_ddl($table_name, $index_name,
|
||||
$index_fields, $index_type));
|
||||
|
||||
}
|
||||
|
||||
sub get_drop_index_ddl {
|
||||
my $self = shift;
|
||||
my ($table, $name) = @_;
|
||||
|
||||
$name = 'idx_' . $self->_hash_identifier($name);
|
||||
return $self->SUPER::get_drop_index_ddl($table, $name);
|
||||
}
|
||||
|
||||
# Oracle supports the use of FOREIGN KEY integrity constraints
|
||||
# to define the referential integrity actions, including:
|
||||
# - Update and delete No Action (default)
|
||||
# - Delete CASCADE
|
||||
# - Delete SET NULL
|
||||
sub get_fk_ddl {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
return "" if !$references;
|
||||
|
||||
my $update = $references->{UPDATE} || 'CASCADE';
|
||||
my $delete = $references->{DELETE};
|
||||
my $to_table = $references->{TABLE} || confess "No table in reference";
|
||||
my $to_column = $references->{COLUMN} || confess "No column in reference";
|
||||
my $fk_name = $self->_get_fk_name($table, $column, $references);
|
||||
|
||||
my $fk_string = "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
|
||||
. " REFERENCES $to_table($to_column)\n";
|
||||
|
||||
$fk_string = $fk_string . " ON DELETE $delete" if $delete;
|
||||
|
||||
if ( $update =~ /CASCADE/i ){
|
||||
my $tr_str = "CREATE OR REPLACE TRIGGER ${fk_name}_UC"
|
||||
. " AFTER UPDATE ON ". $to_table
|
||||
. " REFERENCING "
|
||||
. " NEW AS NEW "
|
||||
. " OLD AS OLD "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " UPDATE $table"
|
||||
. " SET $column = :NEW.$to_column"
|
||||
. " WHERE $column = :OLD.$to_column;"
|
||||
. " END ${fk_name}_UC;";
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do($tr_str);
|
||||
}
|
||||
|
||||
return $fk_string;
|
||||
}
|
||||
|
||||
sub get_drop_fk_sql {
|
||||
my $self = shift;
|
||||
my ($table, $column, $references) = @_;
|
||||
my $fk_name = $self->_get_fk_name(@_);
|
||||
my @sql;
|
||||
if (!$references->{UPDATE} || $references->{UPDATE} =~ /CASCADE/i) {
|
||||
push(@sql, "DROP TRIGGER ${fk_name}_uc");
|
||||
}
|
||||
push(@sql, $self->SUPER::get_drop_fk_sql(@_));
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub _get_fk_name {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
my $to_table = $references->{TABLE};
|
||||
my $to_column = $references->{COLUMN};
|
||||
my $fk_name = "${table}_${column}_${to_table}_${to_column}";
|
||||
$fk_name = "fk_" . $self->_hash_identifier($fk_name);
|
||||
|
||||
return $fk_name;
|
||||
}
|
||||
|
||||
sub get_alter_column_ddl {
|
||||
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
|
||||
|
||||
my @statements;
|
||||
my $old_def = $self->get_column_abstract($table, $column);
|
||||
my $specific = $self->{db_specific};
|
||||
|
||||
# If the types have changed, we have to deal with that.
|
||||
if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
|
||||
push(@statements, $self->_get_alter_type_sql($table, $column,
|
||||
$new_def, $old_def));
|
||||
}
|
||||
|
||||
my $default = $new_def->{DEFAULT};
|
||||
my $default_old = $old_def->{DEFAULT};
|
||||
# This first condition prevents "uninitialized value" errors.
|
||||
if (!defined $default && !defined $default_old) {
|
||||
# Do Nothing
|
||||
}
|
||||
# If we went from having a default to not having one
|
||||
elsif (!defined $default && defined $default_old) {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " DEFAULT NULL");
|
||||
}
|
||||
# If we went from no default to a default, or we changed the default.
|
||||
elsif ( (defined $default && !defined $default_old) ||
|
||||
($default ne $default_old) )
|
||||
{
|
||||
$default = $specific->{$default} if exists $specific->{$default};
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column "
|
||||
. " DEFAULT $default");
|
||||
}
|
||||
|
||||
# If we went from NULL to NOT NULL.
|
||||
if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
|
||||
my $setdefault;
|
||||
# Handle any fields that were NULL before, if we have a default,
|
||||
$setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
|
||||
# But if we have a set_nulls_to, that overrides the DEFAULT
|
||||
# (although nobody would usually specify both a default and
|
||||
# a set_nulls_to.)
|
||||
$setdefault = $set_nulls_to if defined $set_nulls_to;
|
||||
if (defined $setdefault) {
|
||||
push(@statements, "UPDATE $table SET $column = $setdefault"
|
||||
. " WHERE $column IS NULL");
|
||||
}
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " NOT NULL");
|
||||
push (@statements, _get_notnull_trigger_ddl($table, $column))
|
||||
if $old_def->{TYPE} =~ /varchar|text/i
|
||||
&& $new_def->{TYPE} =~ /varchar|text/i;
|
||||
}
|
||||
# If we went from NOT NULL to NULL
|
||||
elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " NULL");
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}")
|
||||
if $new_def->{TYPE} =~ /varchar|text/i
|
||||
&& $old_def->{TYPE} =~ /varchar|text/i;
|
||||
}
|
||||
|
||||
# If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
|
||||
if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
|
||||
push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
|
||||
}
|
||||
# If we went from being a PK to not being a PK
|
||||
elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
|
||||
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub _get_alter_type_sql {
|
||||
my ($self, $table, $column, $new_def, $old_def) = @_;
|
||||
my @statements;
|
||||
|
||||
my $type = $new_def->{TYPE};
|
||||
$type = $self->{db_specific}->{$type}
|
||||
if exists $self->{db_specific}->{$type};
|
||||
|
||||
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
die("You cannot specify a DEFAULT on a SERIAL-type column.")
|
||||
if $new_def->{DEFAULT};
|
||||
}
|
||||
|
||||
if ( ($old_def->{TYPE} =~ /LONGTEXT/i && $new_def->{TYPE} !~ /LONGTEXT/i)
|
||||
|| ($old_def->{TYPE} !~ /LONGTEXT/i && $new_def->{TYPE} =~ /LONGTEXT/i)
|
||||
) {
|
||||
# LONG to VARCHAR or VARCHAR to LONG is not allowed in Oracle,
|
||||
# just a way to work around.
|
||||
# Determine whether column_temp is already exist.
|
||||
my $dbh=Bugzilla->dbh;
|
||||
my $column_exist = $dbh->selectcol_arrayref(
|
||||
"SELECT CNAME FROM COL WHERE TNAME = UPPER(?) AND
|
||||
CNAME = UPPER(?)", undef,$table,$column . "_temp");
|
||||
if(!@$column_exist) {
|
||||
push(@statements,
|
||||
"ALTER TABLE $table ADD ${column}_temp $type");
|
||||
}
|
||||
push(@statements, "UPDATE $table SET ${column}_temp = $column");
|
||||
push(@statements, "COMMIT");
|
||||
push(@statements, "ALTER TABLE $table DROP COLUMN $column");
|
||||
push(@statements,
|
||||
"ALTER TABLE $table RENAME COLUMN ${column}_temp TO $column");
|
||||
} else {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column $type");
|
||||
}
|
||||
|
||||
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, _get_create_seq_ddl($table, $column));
|
||||
}
|
||||
|
||||
# If this column is no longer SERIAL, we need to drop the sequence
|
||||
# that went along with it.
|
||||
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "DROP SEQUENCE ${table}_${column}_SEQ");
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}_TR");
|
||||
}
|
||||
|
||||
# If this column is changed to type TEXT/VARCHAR, we need to deal with
|
||||
# empty string.
|
||||
if ( $old_def->{TYPE} !~ /varchar|text/i
|
||||
&& $new_def->{TYPE} =~ /varchar|text/i
|
||||
&& $new_def->{NOTNULL} )
|
||||
{
|
||||
push (@statements, _get_notnull_trigger_ddl($table, $column));
|
||||
}
|
||||
# If this column is no longer TEXT/VARCHAR, we need to drop the trigger
|
||||
# that went along with it.
|
||||
if ( $old_def->{TYPE} =~ /varchar|text/i
|
||||
&& $old_def->{NOTNULL}
|
||||
&& $new_def->{TYPE} !~ /varchar|text/i )
|
||||
{
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}");
|
||||
}
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list.
|
||||
return ();
|
||||
}
|
||||
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
|
||||
my $def = $self->get_column_abstract($table, $old_name);
|
||||
if ($def->{TYPE} =~ /SERIAL/i) {
|
||||
# We have to rename the series also, and fix the default of the series.
|
||||
push(@sql, "RENAME ${table}_${old_name}_SEQ TO
|
||||
${table}_${new_name}_seq");
|
||||
my $serial_sql =
|
||||
"CREATE OR REPLACE TRIGGER ${table}_${new_name}_TR "
|
||||
. " BEFORE INSERT ON ${table} "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " SELECT ${table}_${new_name}_SEQ.NEXTVAL "
|
||||
. " INTO :NEW.${new_name} FROM DUAL; "
|
||||
. " END;";
|
||||
push(@sql, $serial_sql);
|
||||
push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
|
||||
}
|
||||
if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL} ) {
|
||||
push(@sql, _get_notnull_trigger_ddl($table,$new_name));
|
||||
push(@sql, "DROP TRIGGER ${table}_${old_name}");
|
||||
}
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub _get_notnull_trigger_ddl {
|
||||
my ($table, $column) = @_;
|
||||
|
||||
my $notnull_sql = "CREATE OR REPLACE TRIGGER "
|
||||
. " ${table}_${column}"
|
||||
. " BEFORE INSERT OR UPDATE ON ". $table
|
||||
. " FOR EACH ROW"
|
||||
. " BEGIN "
|
||||
. " IF :NEW.". $column ." IS NULL THEN "
|
||||
. " SELECT '" . Bugzilla::DB::Oracle->EMPTY_STRING
|
||||
. "' INTO :NEW.". $column ." FROM DUAL; "
|
||||
. " END IF; "
|
||||
. " END ".$table.";";
|
||||
return $notnull_sql;
|
||||
}
|
||||
|
||||
sub _get_create_seq_ddl {
|
||||
my ($self, $table, $column, $start_with) = @_;
|
||||
$start_with ||= 1;
|
||||
my @ddl;
|
||||
my $seq_name = "${table}_${column}_SEQ";
|
||||
my $seq_sql = "CREATE SEQUENCE $seq_name "
|
||||
. " INCREMENT BY 1 "
|
||||
. " START WITH $start_with "
|
||||
. " NOMAXVALUE "
|
||||
. " NOCYCLE "
|
||||
. " NOCACHE";
|
||||
my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
|
||||
. " BEFORE INSERT ON ${table} "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " SELECT ${seq_name}.NEXTVAL "
|
||||
. " INTO :NEW.${column} FROM DUAL; "
|
||||
. " END;";
|
||||
push (@ddl, $seq_sql);
|
||||
push (@ddl, $serial_sql);
|
||||
|
||||
return @ddl;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,166 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Pg;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for PostgreSQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
use Storable qw(dclone);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
# Remove FULLTEXT index types from the schemas.
|
||||
foreach my $table (keys %{ $self->{schema} }) {
|
||||
if ($self->{schema}{$table}{INDEXES}) {
|
||||
foreach my $index (@{ $self->{schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
foreach my $index (@{ $self->{abstract_schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'smallint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'integer',
|
||||
INT2 => 'integer',
|
||||
INT3 => 'integer',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'serial unique',
|
||||
MEDIUMSERIAL => 'serial unique',
|
||||
INTSERIAL => 'serial unique',
|
||||
|
||||
TINYTEXT => 'varchar(255)',
|
||||
MEDIUMTEXT => 'text',
|
||||
LONGTEXT => 'text',
|
||||
|
||||
LONGBLOB => 'bytea',
|
||||
|
||||
DATETIME => 'timestamp(0) without time zone',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list, since Pg
|
||||
# is case-insensitive and will return an error about a duplicate name
|
||||
return ();
|
||||
}
|
||||
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
|
||||
my $def = $self->get_column_abstract($table, $old_name);
|
||||
if ($def->{TYPE} =~ /SERIAL/i) {
|
||||
# We have to rename the series also, and fix the default of the series.
|
||||
push(@sql, "ALTER TABLE ${table}_${old_name}_seq
|
||||
RENAME TO ${table}_${new_name}_seq");
|
||||
push(@sql, "ALTER TABLE $table ALTER COLUMN $new_name
|
||||
SET DEFAULT NEXTVAL('${table}_${new_name}_seq')");
|
||||
}
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub get_rename_table_sql {
|
||||
my ($self, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list, since Pg
|
||||
# is case-insensitive and will return an error about a duplicate name
|
||||
return ();
|
||||
}
|
||||
return ("ALTER TABLE $old_name RENAME TO $new_name");
|
||||
}
|
||||
|
||||
sub _get_alter_type_sql {
|
||||
my ($self, $table, $column, $new_def, $old_def) = @_;
|
||||
my @statements;
|
||||
|
||||
my $type = $new_def->{TYPE};
|
||||
$type = $self->{db_specific}->{$type}
|
||||
if exists $self->{db_specific}->{$type};
|
||||
|
||||
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
die("You cannot specify a DEFAULT on a SERIAL-type column.")
|
||||
if $new_def->{DEFAULT};
|
||||
$type =~ s/serial/integer/i;
|
||||
}
|
||||
|
||||
# On Pg, you don't need UNIQUE if you're a PK--it creates
|
||||
# two identical indexes otherwise.
|
||||
$type =~ s/unique//i if $new_def->{PRIMARYKEY};
|
||||
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
TYPE $type");
|
||||
|
||||
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "CREATE SEQUENCE ${table}_${column}_seq");
|
||||
push(@statements, "SELECT setval('${table}_${column}_seq',
|
||||
MAX($table.$column))
|
||||
FROM $table");
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
SET DEFAULT nextval('${table}_${column}_seq')");
|
||||
}
|
||||
|
||||
# If this column is no longer SERIAL, we need to drop the sequence
|
||||
# that went along with it.
|
||||
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
DROP DEFAULT");
|
||||
# XXX Pg actually won't let us drop the sequence, even though it's
|
||||
# no longer in use. So we harmlessly leave behind a sequence
|
||||
# that does nothing.
|
||||
#push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,234 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Error;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Date::Format;
|
||||
|
||||
# We cannot use $^S to detect if we are in an eval(), because mod_perl
|
||||
# already eval'uates everything, so $^S = 1 in all cases under mod_perl!
|
||||
sub _in_eval {
|
||||
my $in_eval = 0;
|
||||
for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) {
|
||||
last if $sub =~ /^ModPerl/;
|
||||
$in_eval = 1 if $sub =~ /^\(eval\)/;
|
||||
}
|
||||
return $in_eval;
|
||||
}
|
||||
|
||||
sub _throw_error {
|
||||
my ($name, $error, $vars) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$vars ||= {};
|
||||
|
||||
$vars->{error} = $error;
|
||||
|
||||
# Make sure any transaction is rolled back (if supported).
|
||||
# If we are within an eval(), do not roll back transactions as we are
|
||||
# eval'uating some test on purpose.
|
||||
$dbh->bz_rollback_transaction() if ($dbh->bz_in_transaction() && !_in_eval());
|
||||
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
# If a writable $datadir/errorlog exists, log error details there.
|
||||
if (-w "$datadir/errorlog") {
|
||||
require Data::Dumper;
|
||||
my $mesg = "";
|
||||
for (1..75) { $mesg .= "-"; };
|
||||
$mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
|
||||
$mesg .= "$name $error ";
|
||||
$mesg .= "$ENV{REMOTE_ADDR} " if $ENV{REMOTE_ADDR};
|
||||
$mesg .= Bugzilla->user->login;
|
||||
$mesg .= (' actually ' . Bugzilla->sudoer->login) if Bugzilla->sudoer;
|
||||
$mesg .= "\n";
|
||||
my %params = Bugzilla->cgi->Vars;
|
||||
$Data::Dumper::Useqq = 1;
|
||||
for my $param (sort keys %params) {
|
||||
my $val = $params{$param};
|
||||
# obscure passwords
|
||||
$val = "*****" if $param =~ /password/i;
|
||||
# limit line length
|
||||
$val =~ s/^(.{512}).*$/$1\[CHOP\]/;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["param($param)"]);
|
||||
}
|
||||
for my $var (sort keys %ENV) {
|
||||
my $val = $ENV{$var};
|
||||
$val = "*****" if $val =~ /password|http_pass/i;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["env($var)"]);
|
||||
}
|
||||
open(ERRORLOGFID, ">>$datadir/errorlog");
|
||||
print ERRORLOGFID "$mesg\n";
|
||||
close ERRORLOGFID;
|
||||
}
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
|
||||
print Bugzilla->cgi->header();
|
||||
$template->process($name, $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
else {
|
||||
my $message;
|
||||
$template->process($name, $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
|
||||
die("$message\n");
|
||||
}
|
||||
elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
|
||||
# Clone the hash so we aren't modifying the constant.
|
||||
my %error_map = %{ WS_ERROR_CODE() };
|
||||
require Bugzilla::Hook;
|
||||
Bugzilla::Hook::process('webservice-error_codes',
|
||||
{ error_map => \%error_map });
|
||||
my $code = $error_map{$error};
|
||||
if (!$code) {
|
||||
$code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
|
||||
$code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
|
||||
}
|
||||
die SOAP::Fault->faultcode($code)->faultstring($message);
|
||||
}
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
sub ThrowUserError {
|
||||
_throw_error("global/user-error.html.tmpl", @_);
|
||||
}
|
||||
|
||||
sub ThrowCodeError {
|
||||
_throw_error("global/code-error.html.tmpl", @_);
|
||||
}
|
||||
|
||||
sub ThrowTemplateError {
|
||||
my ($template_err) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Make sure the transaction is rolled back (if supported).
|
||||
$dbh->bz_rollback_transaction() if $dbh->bz_in_transaction();
|
||||
|
||||
my $vars = {};
|
||||
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
|
||||
die("error: template error: $template_err");
|
||||
}
|
||||
|
||||
$vars->{'template_error_msg'} = $template_err;
|
||||
$vars->{'error'} = "template_error";
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
# Try a template first; but if this one fails too, fall back
|
||||
# on plain old print statements.
|
||||
if (!$template->process("global/code-error.html.tmpl", $vars)) {
|
||||
my $maintainer = Bugzilla->params->{'maintainer'};
|
||||
my $error = html_quote($vars->{'template_error_msg'});
|
||||
my $error2 = html_quote($template->error());
|
||||
print <<END;
|
||||
<tt>
|
||||
<p>
|
||||
Bugzilla has suffered an internal error. Please save this page and
|
||||
send it to $maintainer with details of what you were doing at the
|
||||
time this message appeared.
|
||||
</p>
|
||||
<script type="text/javascript"> <!--
|
||||
document.write("<p>URL: " +
|
||||
document.location.href.replace(/&/g,"&")
|
||||
.replace(/</g,"<")
|
||||
.replace(/>/g,">") + "</p>");
|
||||
// -->
|
||||
</script>
|
||||
<p>Template->process() failed twice.<br>
|
||||
First error: $error<br>
|
||||
Second error: $error2</p>
|
||||
</tt>
|
||||
END
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Error - Error handling utilities for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Error;
|
||||
|
||||
ThrowUserError("error_tag",
|
||||
{ foo => 'bar' });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Various places throughout the Bugzilla codebase need to report errors to the
|
||||
user. The C<Throw*Error> family of functions allow this to be done in a
|
||||
generic and localizable manner.
|
||||
|
||||
These functions automatically unlock the database tables, if there were any
|
||||
locked. They will also roll back the transaction, if it is supported by
|
||||
the underlying DB.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<ThrowUserError>
|
||||
|
||||
This function takes an error tag as the first argument, and an optional hashref
|
||||
of variables as a second argument. These are used by the
|
||||
I<global/user-error.html.tmpl> template to format the error, using the passed
|
||||
in variables as required.
|
||||
|
||||
=item C<ThrowCodeError>
|
||||
|
||||
This function is used when an internal check detects an error of some sort.
|
||||
This usually indicates a bug in Bugzilla, although it can occur if the user
|
||||
manually constructs urls without correct parameters.
|
||||
|
||||
This function's behaviour is similar to C<ThrowUserError>, except that the
|
||||
template used to display errors is I<global/code-error.html.tmpl>. In addition
|
||||
if the hashref used as the optional second argument contains a key I<variables>
|
||||
then the contents of the hashref (which is expected to be another hashref) will
|
||||
be displayed after the error message, as a debugging aid.
|
||||
|
||||
=item C<ThrowTemplateError>
|
||||
|
||||
This function should only be called if a C<template-<gt>process()> fails.
|
||||
It tries another template first, because often one template being
|
||||
broken or missing doesn't mean that they all are. But it falls back to
|
||||
a print statement as a last-ditch error.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla|Bugzilla>
|
||||
@@ -1,803 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Dan Mosedale <dmose@mozilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Field - a particular piece of information about bugs
|
||||
and useful routines for form field manipulation
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla;
|
||||
use Data::Dumper;
|
||||
|
||||
# Display information about all fields.
|
||||
print Dumper(Bugzilla->get_fields());
|
||||
|
||||
# Display information about non-obsolete custom fields.
|
||||
print Dumper(Bugzilla->active_custom_fields);
|
||||
|
||||
use Bugzilla::Field;
|
||||
|
||||
# Display information about non-obsolete custom fields.
|
||||
# Bugzilla->get_fields() is a wrapper around Bugzilla::Field->match(),
|
||||
# so both methods take the same arguments.
|
||||
print Dumper(Bugzilla::Field->match({ obsolete => 0, custom => 1 }));
|
||||
|
||||
# Create or update a custom field or field definition.
|
||||
my $field = Bugzilla::Field->create(
|
||||
{name => 'cf_silly', description => 'Silly', custom => 1});
|
||||
|
||||
# Instantiate a Field object for an existing field.
|
||||
my $field = new Bugzilla::Field({name => 'qacontact_accessible'});
|
||||
if ($field->obsolete) {
|
||||
print $field->description . " is obsolete\n";
|
||||
}
|
||||
|
||||
# Validation Routines
|
||||
check_field($name, $value, \@legal_values, $no_warn);
|
||||
$fieldid = get_field_id($fieldname);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Field.pm defines field objects, which represent the particular pieces
|
||||
of information that Bugzilla stores about bugs.
|
||||
|
||||
This package also provides functions for dealing with CGI form fields.
|
||||
|
||||
C<Bugzilla::Field> is an implementation of L<Bugzilla::Object>, and
|
||||
so provides all of the methods available in L<Bugzilla::Object>,
|
||||
in addition to what is documented here.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::Field;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter Bugzilla::Object);
|
||||
@Bugzilla::Field::EXPORT = qw(check_field get_field_id get_legal_field_values);
|
||||
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'fielddefs';
|
||||
use constant LIST_ORDER => 'sortkey, name';
|
||||
|
||||
use constant DB_COLUMNS => (
|
||||
'id',
|
||||
'name',
|
||||
'description',
|
||||
'type',
|
||||
'custom',
|
||||
'mailhead',
|
||||
'sortkey',
|
||||
'obsolete',
|
||||
'enter_bug',
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
custom => \&_check_custom,
|
||||
description => \&_check_description,
|
||||
enter_bug => \&_check_enter_bug,
|
||||
mailhead => \&_check_mailhead,
|
||||
obsolete => \&_check_obsolete,
|
||||
sortkey => \&_check_sortkey,
|
||||
type => \&_check_type,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
description
|
||||
mailhead
|
||||
sortkey
|
||||
obsolete
|
||||
enter_bug
|
||||
);
|
||||
|
||||
# How various field types translate into SQL data definitions.
|
||||
use constant SQL_DEFINITIONS => {
|
||||
# Using commas because these are constants and they shouldn't
|
||||
# be auto-quoted by the "=>" operator.
|
||||
FIELD_TYPE_FREETEXT, { TYPE => 'varchar(255)' },
|
||||
FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
|
||||
DEFAULT => "'---'" },
|
||||
FIELD_TYPE_TEXTAREA, { TYPE => 'MEDIUMTEXT' },
|
||||
FIELD_TYPE_DATETIME, { TYPE => 'DATETIME' },
|
||||
};
|
||||
|
||||
# Field definitions for the fields that ship with Bugzilla.
|
||||
# These are used by populate_field_definitions to populate
|
||||
# the fielddefs table.
|
||||
use constant DEFAULT_FIELDS => (
|
||||
{name => 'bug_id', desc => 'Bug #', in_new_bugmail => 1},
|
||||
{name => 'short_desc', desc => 'Summary', in_new_bugmail => 1},
|
||||
{name => 'classification', desc => 'Classification', in_new_bugmail => 1},
|
||||
{name => 'product', desc => 'Product', in_new_bugmail => 1},
|
||||
{name => 'version', desc => 'Version', in_new_bugmail => 1},
|
||||
{name => 'rep_platform', desc => 'Platform', in_new_bugmail => 1},
|
||||
{name => 'bug_file_loc', desc => 'URL', in_new_bugmail => 1},
|
||||
{name => 'op_sys', desc => 'OS/Version', in_new_bugmail => 1},
|
||||
{name => 'bug_status', desc => 'Status', in_new_bugmail => 1},
|
||||
{name => 'status_whiteboard', desc => 'Status Whiteboard',
|
||||
in_new_bugmail => 1},
|
||||
{name => 'keywords', desc => 'Keywords', in_new_bugmail => 1},
|
||||
{name => 'resolution', desc => 'Resolution'},
|
||||
{name => 'bug_severity', desc => 'Severity', in_new_bugmail => 1},
|
||||
{name => 'priority', desc => 'Priority', in_new_bugmail => 1},
|
||||
{name => 'component', desc => 'Component', in_new_bugmail => 1},
|
||||
{name => 'assigned_to', desc => 'AssignedTo', in_new_bugmail => 1},
|
||||
{name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1},
|
||||
{name => 'votes', desc => 'Votes'},
|
||||
{name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1},
|
||||
{name => 'cc', desc => 'CC', in_new_bugmail => 1},
|
||||
{name => 'dependson', desc => 'Depends on', in_new_bugmail => 1},
|
||||
{name => 'blocked', desc => 'Blocks', in_new_bugmail => 1},
|
||||
|
||||
{name => 'attachments.description', desc => 'Attachment description'},
|
||||
{name => 'attachments.filename', desc => 'Attachment filename'},
|
||||
{name => 'attachments.mimetype', desc => 'Attachment mime type'},
|
||||
{name => 'attachments.ispatch', desc => 'Attachment is patch'},
|
||||
{name => 'attachments.isobsolete', desc => 'Attachment is obsolete'},
|
||||
{name => 'attachments.isprivate', desc => 'Attachment is private'},
|
||||
{name => 'attachments.submitter', desc => 'Attachment creator'},
|
||||
|
||||
{name => 'target_milestone', desc => 'Target Milestone'},
|
||||
{name => 'creation_ts', desc => 'Creation date', in_new_bugmail => 1},
|
||||
{name => 'delta_ts', desc => 'Last changed date', in_new_bugmail => 1},
|
||||
{name => 'longdesc', desc => 'Comment'},
|
||||
{name => 'longdescs.isprivate', desc => 'Comment is private'},
|
||||
{name => 'alias', desc => 'Alias'},
|
||||
{name => 'everconfirmed', desc => 'Ever Confirmed'},
|
||||
{name => 'reporter_accessible', desc => 'Reporter Accessible'},
|
||||
{name => 'cclist_accessible', desc => 'CC Accessible'},
|
||||
{name => 'bug_group', desc => 'Group'},
|
||||
{name => 'estimated_time', desc => 'Estimated Hours', in_new_bugmail => 1},
|
||||
{name => 'remaining_time', desc => 'Remaining Hours'},
|
||||
{name => 'deadline', desc => 'Deadline', in_new_bugmail => 1},
|
||||
{name => 'commenter', desc => 'Commenter'},
|
||||
{name => 'flagtypes.name', desc => 'Flag'},
|
||||
{name => 'requestees.login_name', desc => 'Flag Requestee'},
|
||||
{name => 'setters.login_name', desc => 'Flag Setter'},
|
||||
{name => 'work_time', desc => 'Hours Worked'},
|
||||
{name => 'percentage_complete', desc => 'Percentage Complete'},
|
||||
{name => 'content', desc => 'Content'},
|
||||
{name => 'attach_data.thedata', desc => 'Attachment data'},
|
||||
{name => 'attachments.isurl', desc => 'Attachment is a URL'},
|
||||
{name => "owner_idle_time", desc => "Time Since Assignee Touched"},
|
||||
);
|
||||
|
||||
##############
|
||||
# Validators #
|
||||
##############
|
||||
|
||||
sub _check_custom { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $desc) = @_;
|
||||
$desc = clean_text($desc);
|
||||
$desc || ThrowUserError('field_missing_description');
|
||||
return $desc;
|
||||
}
|
||||
|
||||
sub _check_enter_bug { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_mailhead { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name, $is_custom) = @_;
|
||||
$name = lc(clean_text($name));
|
||||
$name || ThrowUserError('field_missing_name');
|
||||
|
||||
# Don't want to allow a name that might mess up SQL.
|
||||
my $name_regex = qr/^[\w\.]+$/;
|
||||
# Custom fields have more restrictive name requirements than
|
||||
# standard fields.
|
||||
$name_regex = qr/^\w+$/ if $is_custom;
|
||||
# Custom fields can't be named just "cf_", and there is no normal
|
||||
# field named just "cf_".
|
||||
($name =~ $name_regex && $name ne "cf_")
|
||||
|| ThrowUserError('field_invalid_name', { name => $name });
|
||||
|
||||
# If it's custom, prepend cf_ to the custom field name to distinguish
|
||||
# it from standard fields.
|
||||
if ($name !~ /^cf_/ && $is_custom) {
|
||||
$name = 'cf_' . $name;
|
||||
}
|
||||
|
||||
# Assure the name is unique. Names can't be changed, so we don't have
|
||||
# to worry about what to do on updates.
|
||||
my $field = new Bugzilla::Field({ name => $name });
|
||||
ThrowUserError('field_already_exists', {'field' => $field }) if $field;
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_obsolete { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_sortkey {
|
||||
my ($invocant, $sortkey) = @_;
|
||||
my $skey = $sortkey;
|
||||
if (!defined $skey || $skey eq '') {
|
||||
($sortkey) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT MAX(sortkey) + 100 FROM fielddefs') || 100;
|
||||
}
|
||||
detaint_natural($sortkey)
|
||||
|| ThrowUserError('field_invalid_sortkey', { sortkey => $skey });
|
||||
return $sortkey;
|
||||
}
|
||||
|
||||
sub _check_type {
|
||||
my ($invocant, $type) = @_;
|
||||
my $saved_type = $type;
|
||||
# The constant here should be updated every time a new,
|
||||
# higher field type is added.
|
||||
(detaint_natural($type) && $type <= FIELD_TYPE_DATETIME)
|
||||
|| ThrowCodeError('invalid_customfield_type', { type => $saved_type });
|
||||
return $type;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Properties
|
||||
|
||||
=over
|
||||
|
||||
=item C<name>
|
||||
|
||||
the name of the field in the database; begins with "cf_" if field
|
||||
is a custom field, but test the value of the boolean "custom" property
|
||||
to determine if a given field is a custom field;
|
||||
|
||||
=item C<description>
|
||||
|
||||
a short string describing the field; displayed to Bugzilla users
|
||||
in several places within Bugzilla's UI, f.e. as the form field label
|
||||
on the "show bug" page;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub description { return $_[0]->{description} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<type>
|
||||
|
||||
an integer specifying the kind of field this is; values correspond to
|
||||
the FIELD_TYPE_* constants in Constants.pm
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub type { return $_[0]->{type} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<custom>
|
||||
|
||||
a boolean specifying whether or not the field is a custom field;
|
||||
if true, field name should start "cf_", but use this property to determine
|
||||
which fields are custom fields;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub custom { return $_[0]->{custom} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<in_new_bugmail>
|
||||
|
||||
a boolean specifying whether or not the field is displayed in bugmail
|
||||
for newly-created bugs;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub in_new_bugmail { return $_[0]->{mailhead} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<sortkey>
|
||||
|
||||
an integer specifying the sortkey of the field.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub sortkey { return $_[0]->{sortkey} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<obsolete>
|
||||
|
||||
a boolean specifying whether or not the field is obsolete;
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub obsolete { return $_[0]->{obsolete} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<enter_bug>
|
||||
|
||||
A boolean specifying whether or not this field should appear on
|
||||
enter_bug.cgi
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub enter_bug { return $_[0]->{enter_bug} }
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values>
|
||||
|
||||
A reference to an array with valid active values for this field.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub legal_values {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'legal_values'}) {
|
||||
$self->{'legal_values'} = get_legal_field_values($self->name);
|
||||
}
|
||||
return $self->{'legal_values'};
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Mutators
|
||||
|
||||
These set the particular field that they are named after.
|
||||
|
||||
They take a single value--the new value for that field.
|
||||
|
||||
They will throw an error if you try to set the values to something invalid.
|
||||
|
||||
=over
|
||||
|
||||
=item C<set_description>
|
||||
|
||||
=item C<set_enter_bug>
|
||||
|
||||
=item C<set_obsolete>
|
||||
|
||||
=item C<set_sortkey>
|
||||
|
||||
=item C<set_in_new_bugmail>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
|
||||
sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
|
||||
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
||||
sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Method
|
||||
|
||||
=over
|
||||
|
||||
=item C<remove_from_db>
|
||||
|
||||
Attempts to remove the passed in field from the database.
|
||||
Deleting a field is only successful if the field is obsolete and
|
||||
there are no values specified (or EVER specified) for the field.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $name = $self->name;
|
||||
|
||||
if (!$self->custom) {
|
||||
ThrowCodeError('field_not_custom', {'name' => $name });
|
||||
}
|
||||
|
||||
if (!$self->obsolete) {
|
||||
ThrowUserError('customfield_not_obsolete', {'name' => $self->name });
|
||||
}
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
# Check to see if bug activity table has records (should be fast with index)
|
||||
my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity
|
||||
WHERE fieldid = ?", undef, $self->id);
|
||||
if ($has_activity) {
|
||||
ThrowUserError('customfield_has_activity', {'name' => $name });
|
||||
}
|
||||
|
||||
# Check to see if bugs table has records (slow)
|
||||
my $bugs_query = "";
|
||||
|
||||
if ($self->type == FIELD_TYPE_MULTI_SELECT) {
|
||||
$bugs_query = "SELECT COUNT(*) FROM bug_$name";
|
||||
}
|
||||
else {
|
||||
$bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL
|
||||
AND $name != ''";
|
||||
# Ignore the default single select value
|
||||
if ($self->type == FIELD_TYPE_SINGLE_SELECT) {
|
||||
$bugs_query .= " AND $name != '---'";
|
||||
}
|
||||
# Ignore blank dates.
|
||||
if ($self->type == FIELD_TYPE_DATETIME) {
|
||||
$bugs_query .= " AND $name != '00-00-00 00:00:00'";
|
||||
}
|
||||
}
|
||||
|
||||
my $has_bugs = $dbh->selectrow_array($bugs_query);
|
||||
if ($has_bugs) {
|
||||
ThrowUserError('customfield_has_contents', {'name' => $name });
|
||||
}
|
||||
|
||||
# Once we reach here, we should be OK to delete.
|
||||
$dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id);
|
||||
|
||||
my $type = $self->type;
|
||||
|
||||
# the values for multi-select are stored in a seperate table
|
||||
if ($type != FIELD_TYPE_MULTI_SELECT) {
|
||||
$dbh->bz_drop_column('bugs', $name);
|
||||
}
|
||||
|
||||
if ($type == FIELD_TYPE_SINGLE_SELECT
|
||||
|| $type == FIELD_TYPE_MULTI_SELECT)
|
||||
{
|
||||
# Delete the table that holds the legal values for this field.
|
||||
$dbh->bz_drop_field_tables($self);
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction()
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Class Methods
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Just like L<Bugzilla::Object/create>. Takes the following parameters:
|
||||
|
||||
=over
|
||||
|
||||
=item C<name> B<Required> - The name of the field.
|
||||
|
||||
=item C<description> B<Required> - The field label to display in the UI.
|
||||
|
||||
=item C<mailhead> - boolean - Whether this field appears at the
|
||||
top of the bugmail for a newly-filed bug. Defaults to 0.
|
||||
|
||||
=item C<custom> - boolean - True if this is a Custom Field. The field
|
||||
will be added to the C<bugs> table if it does not exist. Defaults to 0.
|
||||
|
||||
=item C<sortkey> - integer - The sortkey of the field. Defaults to 0.
|
||||
|
||||
=item C<enter_bug> - boolean - Whether this field is
|
||||
editable on the bug creation form. Defaults to 0.
|
||||
|
||||
C<obsolete> - boolean - Whether this field is obsolete. Defaults to 0.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my $field = $class->SUPER::create(@_);
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
if ($field->custom) {
|
||||
my $name = $field->name;
|
||||
my $type = $field->type;
|
||||
if (SQL_DEFINITIONS->{$type}) {
|
||||
# Create the database column that stores the data for this field.
|
||||
$dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
|
||||
}
|
||||
|
||||
if ($type == FIELD_TYPE_SINGLE_SELECT
|
||||
|| $type == FIELD_TYPE_MULTI_SELECT)
|
||||
{
|
||||
# Create the table that holds the legal values for this field.
|
||||
$dbh->bz_add_field_tables($field);
|
||||
}
|
||||
|
||||
if ($type == FIELD_TYPE_SINGLE_SELECT) {
|
||||
# Insert a default value of "---" into the legal values table.
|
||||
$dbh->do("INSERT INTO $name (value) VALUES ('---')");
|
||||
}
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
$params->{name} = $class->_check_name($params->{name}, $params->{custom});
|
||||
if (!exists $params->{sortkey}) {
|
||||
$params->{sortkey} = $dbh->selectrow_array(
|
||||
"SELECT MAX(sortkey) + 100 FROM fielddefs") || 100;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_legal_field_values($field)>
|
||||
|
||||
Description: returns all the legal values for a field that has a
|
||||
list of legal values, like rep_platform or resolution.
|
||||
The table where these values are stored must at least have
|
||||
the following columns: value, isactive, sortkey.
|
||||
|
||||
Params: C<$field> - Name of the table where valid values are.
|
||||
|
||||
Returns: a reference to a list of valid values.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_legal_field_values {
|
||||
my ($field) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $result_ref = $dbh->selectcol_arrayref(
|
||||
"SELECT value FROM $field
|
||||
WHERE isactive = ?
|
||||
ORDER BY sortkey, value", undef, (1));
|
||||
return $result_ref;
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<populate_field_definitions()>
|
||||
|
||||
Description: Populates the fielddefs table during an installation
|
||||
or upgrade.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub populate_field_definitions {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# ADD and UPDATE field definitions
|
||||
foreach my $def (DEFAULT_FIELDS) {
|
||||
my $field = new Bugzilla::Field({ name => $def->{name} });
|
||||
if ($field) {
|
||||
$field->set_description($def->{desc});
|
||||
$field->set_in_new_bugmail($def->{in_new_bugmail});
|
||||
$field->update();
|
||||
}
|
||||
else {
|
||||
if (exists $def->{in_new_bugmail}) {
|
||||
$def->{mailhead} = $def->{in_new_bugmail};
|
||||
delete $def->{in_new_bugmail};
|
||||
}
|
||||
$def->{description} = $def->{desc};
|
||||
delete $def->{desc};
|
||||
Bugzilla::Field->create($def);
|
||||
}
|
||||
}
|
||||
|
||||
# DELETE fields which were added only accidentally, or which
|
||||
# were never tracked in bugs_activity. Note that you can never
|
||||
# delete fields which are used by bugs_activity.
|
||||
|
||||
# Oops. Bug 163299
|
||||
$dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
|
||||
# Oops. Bug 215319
|
||||
$dbh->do("DELETE FROM fielddefs WHERE name='requesters.login_name'");
|
||||
# This field was never tracked in bugs_activity, so it's safe to delete.
|
||||
$dbh->do("DELETE FROM fielddefs WHERE name='attachments.thedata'");
|
||||
|
||||
# MODIFY old field definitions
|
||||
|
||||
# 2005-11-13 LpSolit@gmail.com - Bug 302599
|
||||
# One of the field names was a fragment of SQL code, which is DB dependent.
|
||||
# We have to rename it to a real name, which is DB independent.
|
||||
my $new_field_name = 'days_elapsed';
|
||||
my $field_description = 'Days since bug changed';
|
||||
|
||||
my ($old_field_id, $old_field_name) =
|
||||
$dbh->selectrow_array('SELECT id, name FROM fielddefs
|
||||
WHERE description = ?',
|
||||
undef, $field_description);
|
||||
|
||||
if ($old_field_id && ($old_field_name ne $new_field_name)) {
|
||||
print "SQL fragment found in the 'fielddefs' table...\n";
|
||||
print "Old field name: " . $old_field_name . "\n";
|
||||
# We have to fix saved searches first. Queries have been escaped
|
||||
# before being saved. We have to do the same here to find them.
|
||||
$old_field_name = url_quote($old_field_name);
|
||||
my $broken_named_queries =
|
||||
$dbh->selectall_arrayref('SELECT userid, name, query
|
||||
FROM namedqueries WHERE ' .
|
||||
$dbh->sql_istrcmp('query', '?', 'LIKE'),
|
||||
undef, "%=$old_field_name%");
|
||||
|
||||
my $sth_UpdateQueries = $dbh->prepare('UPDATE namedqueries SET query = ?
|
||||
WHERE userid = ? AND name = ?');
|
||||
|
||||
print "Fixing saved searches...\n" if scalar(@$broken_named_queries);
|
||||
foreach my $named_query (@$broken_named_queries) {
|
||||
my ($userid, $name, $query) = @$named_query;
|
||||
$query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
|
||||
$sth_UpdateQueries->execute($query, $userid, $name);
|
||||
}
|
||||
|
||||
# We now do the same with saved chart series.
|
||||
my $broken_series =
|
||||
$dbh->selectall_arrayref('SELECT series_id, query
|
||||
FROM series WHERE ' .
|
||||
$dbh->sql_istrcmp('query', '?', 'LIKE'),
|
||||
undef, "%=$old_field_name%");
|
||||
|
||||
my $sth_UpdateSeries = $dbh->prepare('UPDATE series SET query = ?
|
||||
WHERE series_id = ?');
|
||||
|
||||
print "Fixing saved chart series...\n" if scalar(@$broken_series);
|
||||
foreach my $series (@$broken_series) {
|
||||
my ($series_id, $query) = @$series;
|
||||
$query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
|
||||
$sth_UpdateSeries->execute($query, $series_id);
|
||||
}
|
||||
# Now that saved searches have been fixed, we can fix the field name.
|
||||
print "Fixing the 'fielddefs' table...\n";
|
||||
print "New field name: " . $new_field_name . "\n";
|
||||
$dbh->do('UPDATE fielddefs SET name = ? WHERE id = ?',
|
||||
undef, ($new_field_name, $old_field_id));
|
||||
}
|
||||
|
||||
# This field has to be created separately, or the above upgrade code
|
||||
# might not run properly.
|
||||
Bugzilla::Field->create({ name => $new_field_name,
|
||||
description => $field_description })
|
||||
unless new Bugzilla::Field({ name => $new_field_name });
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
=head2 Data Validation
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_field($name, $value, \@legal_values, $no_warn)>
|
||||
|
||||
Description: Makes sure the field $name is defined and its $value
|
||||
is non empty. If @legal_values is defined, this routine
|
||||
checks whether its value is one of the legal values
|
||||
associated with this field, else it checks against
|
||||
the default valid values for this field obtained by
|
||||
C<get_legal_field_values($name)>. If the test is successful,
|
||||
the function returns 1. If the test fails, an error
|
||||
is thrown (by default), unless $no_warn is true, in which
|
||||
case the function returns 0.
|
||||
|
||||
Params: $name - the field name
|
||||
$value - the field value
|
||||
@legal_values - (optional) list of legal values
|
||||
$no_warn - (optional) do not throw an error if true
|
||||
|
||||
Returns: 1 on success; 0 on failure if $no_warn is true (else an
|
||||
error is thrown).
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub check_field {
|
||||
my ($name, $value, $legalsRef, $no_warn) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# If $legalsRef is undefined, we use the default valid values.
|
||||
unless (defined $legalsRef) {
|
||||
$legalsRef = get_legal_field_values($name);
|
||||
}
|
||||
|
||||
if (!defined($value)
|
||||
|| trim($value) eq ""
|
||||
|| lsearch($legalsRef, $value) < 0)
|
||||
{
|
||||
return 0 if $no_warn; # We don't want an error to be thrown; return.
|
||||
trick_taint($name);
|
||||
|
||||
my $field = new Bugzilla::Field({ name => $name });
|
||||
my $field_desc = $field ? $field->description : $name;
|
||||
ThrowCodeError('illegal_field', { field => $field_desc });
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_field_id($fieldname)>
|
||||
|
||||
Description: Returns the ID of the specified field name and throws
|
||||
an error if this field does not exist.
|
||||
|
||||
Params: $name - a field name
|
||||
|
||||
Returns: the corresponding field ID or an error if the field name
|
||||
does not exist.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_field_id {
|
||||
my ($name) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
trick_taint($name);
|
||||
my $id = $dbh->selectrow_array('SELECT id FROM fielddefs
|
||||
WHERE name = ?', undef, $name);
|
||||
|
||||
ThrowCodeError('invalid_field_name', {field => $name}) unless $id;
|
||||
return $id
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,482 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Myk Melez <myk@mozilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::FlagType;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::FlagType - A module to deal with Bugzilla flag types.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
FlagType.pm provides an interface to flag types as stored in Bugzilla.
|
||||
See below for more information.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
=over
|
||||
|
||||
=item *
|
||||
|
||||
Use of private functions/variables outside this module may lead to
|
||||
unexpected results after an upgrade. Please avoid using private
|
||||
functions in other files/modules. Private functions are functions
|
||||
whose names start with _ or are specifically noted as being private.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Group;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE VARIABLES/CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_COLUMNS>
|
||||
|
||||
basic sets of columns and tables for getting flag types from the
|
||||
database.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
flagtypes.id
|
||||
flagtypes.name
|
||||
flagtypes.description
|
||||
flagtypes.cc_list
|
||||
flagtypes.target_type
|
||||
flagtypes.sortkey
|
||||
flagtypes.is_active
|
||||
flagtypes.is_requestable
|
||||
flagtypes.is_requesteeble
|
||||
flagtypes.is_multiplicable
|
||||
flagtypes.grant_group_id
|
||||
flagtypes.request_group_id
|
||||
);
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_TABLE>
|
||||
|
||||
Which database(s) is the data coming from?
|
||||
|
||||
Note: when adding tables to DB_TABLE, make sure to include the separator
|
||||
(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take
|
||||
multiple separators based on the join type, and therefore it is not possible
|
||||
to join them later using a single known separator.
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=cut
|
||||
|
||||
use constant DB_TABLE => 'flagtypes';
|
||||
use constant LIST_ORDER => 'flagtypes.sortkey, flagtypes.name';
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
=head2 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<id>
|
||||
|
||||
Returns the ID of the flagtype.
|
||||
|
||||
=item C<name>
|
||||
|
||||
Returns the name of the flagtype.
|
||||
|
||||
=item C<description>
|
||||
|
||||
Returns the description of the flagtype.
|
||||
|
||||
=item C<cc_list>
|
||||
|
||||
Returns the concatenated CC list for the flagtype, as a single string.
|
||||
|
||||
=item C<target_type>
|
||||
|
||||
Returns whether the flagtype applies to bugs or attachments.
|
||||
|
||||
=item C<is_active>
|
||||
|
||||
Returns whether the flagtype is active or disabled. Flags being
|
||||
in a disabled flagtype are not deleted. It only prevents you from
|
||||
adding new flags to it.
|
||||
|
||||
=item C<is_requestable>
|
||||
|
||||
Returns whether you can request for the given flagtype
|
||||
(i.e. whether the '?' flag is available or not).
|
||||
|
||||
=item C<is_requesteeble>
|
||||
|
||||
Returns whether you can ask someone specifically or not.
|
||||
|
||||
=item C<is_multiplicable>
|
||||
|
||||
Returns whether you can have more than one flag for the given
|
||||
flagtype in a given bug/attachment.
|
||||
|
||||
=item C<sortkey>
|
||||
|
||||
Returns the sortkey of the flagtype.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub cc_list { return $_[0]->{'cc_list'}; }
|
||||
sub target_type { return $_[0]->{'target_type'} eq 'b' ? 'bug' : 'attachment'; }
|
||||
sub is_active { return $_[0]->{'is_active'}; }
|
||||
sub is_requestable { return $_[0]->{'is_requestable'}; }
|
||||
sub is_requesteeble { return $_[0]->{'is_requesteeble'}; }
|
||||
sub is_multiplicable { return $_[0]->{'is_multiplicable'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
sub request_group_id { return $_[0]->{'request_group_id'}; }
|
||||
sub grant_group_id { return $_[0]->{'grant_group_id'}; }
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<grant_list>
|
||||
|
||||
Returns a reference to an array of users who have permission to grant this flag type.
|
||||
The arrays are populated with hashrefs containing the login, identity and visibility of users.
|
||||
|
||||
=item C<grant_group>
|
||||
|
||||
Returns the group (as a Bugzilla::Group object) in which a user
|
||||
must be in order to grant or deny a request.
|
||||
|
||||
=item C<request_group>
|
||||
|
||||
Returns the group (as a Bugzilla::Group object) in which a user
|
||||
must be in order to request or clear a flag.
|
||||
|
||||
=item C<flag_count>
|
||||
|
||||
Returns the number of flags belonging to the flagtype.
|
||||
|
||||
=item C<inclusions>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
explicitly associated with the flagtype.
|
||||
|
||||
=item C<exclusions>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
explicitly excluded from the flagtype.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub grant_list {
|
||||
my $self = shift;
|
||||
my @custusers;
|
||||
my @allusers = @{Bugzilla->user->get_userlist};
|
||||
foreach my $user (@allusers) {
|
||||
my $user_obj = new Bugzilla::User({name => $user->{login}});
|
||||
push(@custusers, $user) if $user_obj->can_set_flag($self);
|
||||
}
|
||||
return \@custusers;
|
||||
}
|
||||
|
||||
sub grant_group {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'grant_group'} && $self->{'grant_group_id'}) {
|
||||
$self->{'grant_group'} = new Bugzilla::Group($self->{'grant_group_id'});
|
||||
}
|
||||
return $self->{'grant_group'};
|
||||
}
|
||||
|
||||
sub request_group {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'request_group'} && $self->{'request_group_id'}) {
|
||||
$self->{'request_group'} = new Bugzilla::Group($self->{'request_group_id'});
|
||||
}
|
||||
return $self->{'request_group'};
|
||||
}
|
||||
|
||||
sub flag_count {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_count'}) {
|
||||
$self->{'flag_count'} =
|
||||
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM flags
|
||||
WHERE type_id = ?', undef, $self->{'id'});
|
||||
}
|
||||
return $self->{'flag_count'};
|
||||
}
|
||||
|
||||
sub inclusions {
|
||||
my $self = shift;
|
||||
|
||||
$self->{'inclusions'} ||= get_clusions($self->id, 'in');
|
||||
return $self->{'inclusions'};
|
||||
}
|
||||
|
||||
sub exclusions {
|
||||
my $self = shift;
|
||||
|
||||
$self->{'exclusions'} ||= get_clusions($self->id, 'ex');
|
||||
return $self->{'exclusions'};
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Public Functions
|
||||
######################################################################
|
||||
|
||||
=pod
|
||||
|
||||
=head1 PUBLIC FUNCTIONS/METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_clusions($id, $type)>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
associated with the flagtype:
|
||||
$clusions{'product_name:component_name'} = "product_ID:component_ID"
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_clusions {
|
||||
my ($id, $type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $list =
|
||||
$dbh->selectall_arrayref("SELECT products.id, products.name, " .
|
||||
" components.id, components.name " .
|
||||
"FROM flagtypes, flag${type}clusions " .
|
||||
"LEFT OUTER JOIN products " .
|
||||
" ON flag${type}clusions.product_id = products.id " .
|
||||
"LEFT OUTER JOIN components " .
|
||||
" ON flag${type}clusions.component_id = components.id " .
|
||||
"WHERE flagtypes.id = ? " .
|
||||
" AND flag${type}clusions.type_id = flagtypes.id",
|
||||
undef, $id);
|
||||
my %clusions;
|
||||
foreach my $data (@$list) {
|
||||
my ($product_id, $product_name, $component_id, $component_name) = @$data;
|
||||
$product_id ||= 0;
|
||||
$product_name ||= "__Any__";
|
||||
$component_id ||= 0;
|
||||
$component_name ||= "__Any__";
|
||||
$clusions{"$product_name:$component_name"} = "$product_id:$component_id";
|
||||
}
|
||||
return \%clusions;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<match($criteria)>
|
||||
|
||||
Queries the database for flag types matching the given criteria
|
||||
and returns a list of matching flagtype objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub match {
|
||||
my ($criteria) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Depending on the criteria, we may have to append additional tables.
|
||||
my $tables = [DB_TABLE];
|
||||
my @criteria = sqlify_criteria($criteria, $tables);
|
||||
$tables = join(' ', @$tables);
|
||||
$criteria = join(' AND ', @criteria);
|
||||
|
||||
my $flagtype_ids = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria");
|
||||
|
||||
return Bugzilla::FlagType->new_from_list($flagtype_ids);
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<count($criteria)>
|
||||
|
||||
Returns the total number of flag types matching the given criteria.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub count {
|
||||
my ($criteria) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Depending on the criteria, we may have to append additional tables.
|
||||
my $tables = [DB_TABLE];
|
||||
my @criteria = sqlify_criteria($criteria, $tables);
|
||||
$tables = join(' ', @$tables);
|
||||
$criteria = join(' AND ', @criteria);
|
||||
|
||||
my $count = $dbh->selectrow_array("SELECT COUNT(flagtypes.id)
|
||||
FROM $tables WHERE $criteria");
|
||||
return $count;
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Private Functions
|
||||
######################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<sqlify_criteria($criteria, $tables)>
|
||||
|
||||
Converts a hash of criteria into a list of SQL criteria.
|
||||
$criteria is a reference to the criteria (field => value),
|
||||
$tables is a reference to an array of tables being accessed
|
||||
by the query.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub sqlify_criteria {
|
||||
my ($criteria, $tables) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# the generated list of SQL criteria; "1=1" is a clever way of making sure
|
||||
# there's something in the list so calling code doesn't have to check list
|
||||
# size before building a WHERE clause out of it
|
||||
my @criteria = ("1=1");
|
||||
|
||||
if ($criteria->{name}) {
|
||||
my $name = $dbh->quote($criteria->{name});
|
||||
trick_taint($name); # Detaint data as we have quoted it.
|
||||
push(@criteria, "flagtypes.name = $name");
|
||||
}
|
||||
if ($criteria->{target_type}) {
|
||||
# The target type is stored in the database as a one-character string
|
||||
# ("a" for attachment and "b" for bug), but this function takes complete
|
||||
# names ("attachment" and "bug") for clarity, so we must convert them.
|
||||
my $target_type = $criteria->{target_type} eq 'bug'? 'b' : 'a';
|
||||
push(@criteria, "flagtypes.target_type = '$target_type'");
|
||||
}
|
||||
if (exists($criteria->{is_active})) {
|
||||
my $is_active = $criteria->{is_active} ? "1" : "0";
|
||||
push(@criteria, "flagtypes.is_active = $is_active");
|
||||
}
|
||||
if ($criteria->{product_id} && $criteria->{'component_id'}) {
|
||||
my $product_id = $criteria->{product_id};
|
||||
my $component_id = $criteria->{component_id};
|
||||
|
||||
# Add inclusions to the query, which simply involves joining the table
|
||||
# by flag type ID and target product/component.
|
||||
push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
|
||||
push(@criteria, "(i.product_id = $product_id OR i.product_id IS NULL)");
|
||||
push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
|
||||
|
||||
# Add exclusions to the query, which is more complicated. First of all,
|
||||
# we do a LEFT JOIN so we don't miss flag types with no exclusions.
|
||||
# Then, as with inclusions, we join on flag type ID and target product/
|
||||
# component. However, since we want flag types that *aren't* on the
|
||||
# exclusions list, we add a WHERE criteria to use only records with
|
||||
# NULL exclusion type, i.e. without any exclusions.
|
||||
my $join_clause = "flagtypes.id = e.type_id " .
|
||||
"AND (e.product_id = $product_id OR e.product_id IS NULL) " .
|
||||
"AND (e.component_id = $component_id OR e.component_id IS NULL)";
|
||||
push(@$tables, "LEFT JOIN flagexclusions AS e ON ($join_clause)");
|
||||
push(@criteria, "e.type_id IS NULL");
|
||||
}
|
||||
if ($criteria->{group}) {
|
||||
my $gid = $criteria->{group};
|
||||
detaint_natural($gid);
|
||||
push(@criteria, "(flagtypes.grant_group_id = $gid " .
|
||||
" OR flagtypes.request_group_id = $gid)");
|
||||
}
|
||||
|
||||
return @criteria;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end private
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over
|
||||
|
||||
=item B<Bugzilla::Flags>
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONTRIBUTORS
|
||||
|
||||
=over
|
||||
|
||||
=item Myk Melez <myk@mozilla.org>
|
||||
|
||||
=item Kevin Benton <kevin.benton@amd.com>
|
||||
|
||||
=item Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,403 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Joel Peshkin <bugreport@peshkin.net>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Group;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Config qw(:admin);
|
||||
|
||||
###############################
|
||||
##### Module Initialization ###
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
groups.id
|
||||
groups.name
|
||||
groups.description
|
||||
groups.isbuggroup
|
||||
groups.userregexp
|
||||
groups.isactive
|
||||
groups.icon_url
|
||||
);
|
||||
|
||||
use constant DB_TABLE => 'groups';
|
||||
|
||||
use constant LIST_ORDER => 'isbuggroup, name';
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
description => \&_check_description,
|
||||
userregexp => \&_check_user_regexp,
|
||||
isactive => \&_check_is_active,
|
||||
isbuggroup => \&_check_is_bug_group,
|
||||
icon_url => \&_check_icon_url,
|
||||
};
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
description
|
||||
userregexp
|
||||
isactive
|
||||
icon_url
|
||||
);
|
||||
|
||||
# Parameters that are lists of groups.
|
||||
use constant GROUP_PARAMS => qw(chartgroup insidergroup timetrackinggroup
|
||||
querysharegroup);
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub is_bug_group { return $_[0]->{'isbuggroup'}; }
|
||||
sub user_regexp { return $_[0]->{'userregexp'}; }
|
||||
sub is_active { return $_[0]->{'isactive'}; }
|
||||
sub icon_url { return $_[0]->{'icon_url'}; }
|
||||
|
||||
sub members_direct {
|
||||
my ($self) = @_;
|
||||
return $self->{members_direct} if defined $self->{members_direct};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $user_ids = $dbh->selectcol_arrayref(
|
||||
"SELECT user_group_map.user_id
|
||||
FROM user_group_map
|
||||
WHERE user_group_map.group_id = ?
|
||||
AND grant_type = " . GRANT_DIRECT . "
|
||||
AND isbless = 0", undef, $self->id);
|
||||
require Bugzilla::User;
|
||||
$self->{members_direct} = Bugzilla::User->new_from_list($user_ids);
|
||||
return $self->{members_direct};
|
||||
}
|
||||
|
||||
sub grant_direct {
|
||||
my ($self, $type) = @_;
|
||||
$self->{grant_direct} ||= {};
|
||||
return $self->{grant_direct}->{$type}
|
||||
if defined $self->{members_direct}->{$type};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(
|
||||
"SELECT member_id FROM group_group_map
|
||||
WHERE grantor_id = ? AND grant_type = $type",
|
||||
undef, $self->id) || [];
|
||||
|
||||
$self->{grant_direct}->{$type} = $self->new_from_list($ids);
|
||||
return $self->{grant_direct}->{$type};
|
||||
}
|
||||
|
||||
sub granted_by_direct {
|
||||
my ($self, $type) = @_;
|
||||
$self->{granted_by_direct} ||= {};
|
||||
return $self->{granted_by_direct}->{$type}
|
||||
if defined $self->{granted_by_direct}->{$type};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(
|
||||
"SELECT grantor_id FROM group_group_map
|
||||
WHERE member_id = ? AND grant_type = $type",
|
||||
undef, $self->id) || [];
|
||||
|
||||
$self->{granted_by_direct}->{$type} = $self->new_from_list($ids);
|
||||
return $self->{granted_by_direct}->{$type};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_is_active { $_[0]->set('isactive', $_[1]); }
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_user_regexp { $_[0]->set('userregexp', $_[1]); }
|
||||
sub set_icon_url { $_[0]->set('icon_url', $_[1]); }
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
if (exists $changes->{name}) {
|
||||
my ($old_name, $new_name) = @{$changes->{name}};
|
||||
my $update_params;
|
||||
foreach my $group (GROUP_PARAMS) {
|
||||
if ($old_name eq Bugzilla->params->{$group}) {
|
||||
SetParam($group, $new_name);
|
||||
$update_params = 1;
|
||||
}
|
||||
}
|
||||
write_params() if $update_params;
|
||||
}
|
||||
|
||||
# If we've changed this group to be active, fix any Mandatory groups.
|
||||
$self->_enforce_mandatory if (exists $changes->{isactive}
|
||||
&& $changes->{isactive}->[1]);
|
||||
|
||||
$self->_rederive_regexp() if exists $changes->{userregexp};
|
||||
return $changes;
|
||||
}
|
||||
|
||||
# Add missing entries in bug_group_map for bugs created while
|
||||
# a mandatory group was disabled and which is now enabled again.
|
||||
sub _enforce_mandatory {
|
||||
my ($self) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $gid = $self->id;
|
||||
|
||||
my $bug_ids =
|
||||
$dbh->selectcol_arrayref('SELECT bugs.bug_id
|
||||
FROM bugs
|
||||
INNER JOIN group_control_map
|
||||
ON group_control_map.product_id = bugs.product_id
|
||||
LEFT JOIN bug_group_map
|
||||
ON bug_group_map.bug_id = bugs.bug_id
|
||||
AND bug_group_map.group_id = group_control_map.group_id
|
||||
WHERE group_control_map.group_id = ?
|
||||
AND group_control_map.membercontrol = ?
|
||||
AND bug_group_map.group_id IS NULL',
|
||||
undef, ($gid, CONTROLMAPMANDATORY));
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
|
||||
foreach my $bug_id (@$bug_ids) {
|
||||
$sth->execute($bug_id, $gid);
|
||||
}
|
||||
}
|
||||
|
||||
sub is_active_bug_group {
|
||||
my $self = shift;
|
||||
return $self->is_active && $self->is_bug_group;
|
||||
}
|
||||
|
||||
sub _rederive_regexp {
|
||||
my ($self) = @_;
|
||||
RederiveRegexp($self->user_regexp, $self->id);
|
||||
}
|
||||
|
||||
sub members_non_inherited {
|
||||
my ($self) = @_;
|
||||
return $self->{members_non_inherited}
|
||||
if exists $self->{members_non_inherited};
|
||||
|
||||
my $member_ids = Bugzilla->dbh->selectcol_arrayref(
|
||||
'SELECT DISTINCT user_id FROM user_group_map
|
||||
WHERE isbless = 0 AND group_id = ?',
|
||||
undef, $self->id) || [];
|
||||
require Bugzilla::User;
|
||||
$self->{members_non_inherited} = Bugzilla::User->new_from_list($member_ids);
|
||||
return $self->{members_non_inherited};
|
||||
}
|
||||
|
||||
################################
|
||||
##### Module Subroutines ###
|
||||
################################
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my ($params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
print get_text('install_group_create', { name => $params->{name} }) . "\n"
|
||||
if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
|
||||
|
||||
my $group = $class->SUPER::create(@_);
|
||||
|
||||
# Since we created a new group, give the "admin" group all privileges
|
||||
# initially.
|
||||
my $admin = new Bugzilla::Group({name => 'admin'});
|
||||
# This function is also used to create the "admin" group itself,
|
||||
# so there's a chance it won't exist yet.
|
||||
if ($admin) {
|
||||
my $sth = $dbh->prepare('INSERT INTO group_group_map
|
||||
(member_id, grantor_id, grant_type)
|
||||
VALUES (?, ?, ?)');
|
||||
$sth->execute($admin->id, $group->id, GROUP_MEMBERSHIP);
|
||||
$sth->execute($admin->id, $group->id, GROUP_BLESS);
|
||||
$sth->execute($admin->id, $group->id, GROUP_VISIBLE);
|
||||
}
|
||||
|
||||
$group->_rederive_regexp() if $group->user_regexp;
|
||||
return $group;
|
||||
}
|
||||
|
||||
sub ValidateGroupName {
|
||||
my ($name, @users) = (@_);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $query = "SELECT id FROM groups " .
|
||||
"WHERE name = ?";
|
||||
if (Bugzilla->params->{'usevisibilitygroups'}) {
|
||||
my @visible = (-1);
|
||||
foreach my $user (@users) {
|
||||
$user && push @visible, @{$user->visible_groups_direct};
|
||||
}
|
||||
my $visible = join(', ', @visible);
|
||||
$query .= " AND id IN($visible)";
|
||||
}
|
||||
my $sth = $dbh->prepare($query);
|
||||
$sth->execute($name);
|
||||
my ($ret) = $sth->fetchrow_array();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
# This sub is not perldoc'ed because we expect it to go away and
|
||||
# just become the _rederive_regexp private method.
|
||||
sub RederiveRegexp {
|
||||
my ($regexp, $gid) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare("SELECT userid, login_name, group_id
|
||||
FROM profiles
|
||||
LEFT JOIN user_group_map
|
||||
ON user_group_map.user_id = profiles.userid
|
||||
AND group_id = ?
|
||||
AND grant_type = ?
|
||||
AND isbless = 0");
|
||||
my $sthadd = $dbh->prepare("INSERT INTO user_group_map
|
||||
(user_id, group_id, grant_type, isbless)
|
||||
VALUES (?, ?, ?, 0)");
|
||||
my $sthdel = $dbh->prepare("DELETE FROM user_group_map
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
AND grant_type = ? and isbless = 0");
|
||||
$sth->execute($gid, GRANT_REGEXP);
|
||||
while (my ($uid, $login, $present) = $sth->fetchrow_array()) {
|
||||
if (($regexp =~ /\S+/) && ($login =~ m/$regexp/i))
|
||||
{
|
||||
$sthadd->execute($uid, $gid, GRANT_REGEXP) unless $present;
|
||||
} else {
|
||||
$sthdel->execute($uid, $gid, GRANT_REGEXP) if $present;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
###############################
|
||||
### Validators ###
|
||||
###############################
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name) = @_;
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError("empty_group_name");
|
||||
# If we're creating a Group or changing the name...
|
||||
if (!ref($invocant) || $invocant->name ne $name) {
|
||||
my $exists = new Bugzilla::Group({name => $name });
|
||||
ThrowUserError("group_exists", { name => $name }) if $exists;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $desc) = @_;
|
||||
$desc = trim($desc);
|
||||
$desc || ThrowUserError("empty_group_description");
|
||||
return $desc;
|
||||
}
|
||||
|
||||
sub _check_user_regexp {
|
||||
my ($invocant, $regex) = @_;
|
||||
$regex = trim($regex) || '';
|
||||
ThrowUserError("invalid_regexp") unless (eval {qr/$regex/});
|
||||
return $regex;
|
||||
}
|
||||
|
||||
sub _check_is_active { return $_[1] ? 1 : 0; }
|
||||
sub _check_is_bug_group {
|
||||
return $_[1] ? 1 : 0;
|
||||
}
|
||||
|
||||
sub _check_icon_url { return $_[1] ? clean_text($_[1]) : undef; }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Group - Bugzilla group class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Group;
|
||||
|
||||
my $group = new Bugzilla::Group(1);
|
||||
my $group = new Bugzilla::Group({name => 'AcmeGroup'});
|
||||
|
||||
my $id = $group->id;
|
||||
my $name = $group->name;
|
||||
my $description = $group->description;
|
||||
my $user_reg_exp = $group->user_reg_exp;
|
||||
my $is_active = $group->is_active;
|
||||
my $icon_url = $group->icon_url;
|
||||
my $is_active_bug_group = $group->is_active_bug_group;
|
||||
|
||||
my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
|
||||
my @groups = Bugzilla::Group->get_all;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Group.pm represents a Bugzilla Group object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus has all the methods that L<Bugzilla::Object>
|
||||
provides, in addition to any methods documented below.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Note that in addition to what L<Bugzilla::Object/create($params)>
|
||||
normally does, this function also makes the new group be inherited
|
||||
by the C<admin> group. That is, the C<admin> group will automatically
|
||||
be a member of this group.
|
||||
|
||||
=item C<ValidateGroupName($name, @users)>
|
||||
|
||||
Description: ValidateGroupName checks to see if ANY of the users
|
||||
in the provided list of user objects can see the
|
||||
named group.
|
||||
|
||||
Params: $name - String with the group name.
|
||||
@users - An array with Bugzilla::User objects.
|
||||
|
||||
Returns: It returns the group id if successful
|
||||
and undef otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<members_non_inherited>
|
||||
|
||||
Returns an arrayref of L<Bugzilla::User> objects representing people who are
|
||||
"directly" in this group, meaning that they're in it because they match
|
||||
the group regular expression, or they have been actually added to the
|
||||
group manually.
|
||||
|
||||
=back
|
||||
@@ -1,389 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Zach Lipton <zach@zachlipton.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Hook;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use strict;
|
||||
|
||||
sub process {
|
||||
my ($name, $args) = @_;
|
||||
|
||||
# get a list of all extensions
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
|
||||
# check each extension to see if it uses the hook
|
||||
# if so, invoke the extension source file:
|
||||
foreach my $extension (@extensions) {
|
||||
# all of these variables come directly from code or directory names.
|
||||
# If there's malicious data here, we have much bigger issues to
|
||||
# worry about, so we can safely detaint them:
|
||||
trick_taint($extension);
|
||||
# Skip CVS directories and any hidden files/dirs.
|
||||
next if $extension =~ m{/CVS$} || $extension =~ m{/\.[^/]+$};
|
||||
next if -e "$extension/disabled";
|
||||
if (-e $extension.'/code/'.$name.'.pl') {
|
||||
Bugzilla->hook_args($args);
|
||||
# Allow extensions to load their own libraries.
|
||||
local @INC = ("$extension/lib", @INC);
|
||||
do($extension.'/code/'.$name.'.pl');
|
||||
ThrowCodeError('extension_invalid',
|
||||
{ errstr => $@, name => $name, extension => $extension }) if $@;
|
||||
# Flush stored data.
|
||||
Bugzilla->hook_args({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub enabled_plugins {
|
||||
my $extdir = bz_locations()->{'extensionsdir'};
|
||||
my @extensions = glob("$extdir/*");
|
||||
my %enabled;
|
||||
foreach my $extension (@extensions) {
|
||||
trick_taint($extension);
|
||||
my $extname = $extension;
|
||||
$extname =~ s{^\Q$extdir\E/}{};
|
||||
next if $extname eq 'CVS' || $extname =~ /^\./;
|
||||
next if -e "$extension/disabled";
|
||||
# Allow extensions to load their own libraries.
|
||||
local @INC = ("$extension/lib", @INC);
|
||||
$enabled{$extname} = do("$extension/info.pl");
|
||||
ThrowCodeError('extension_invalid',
|
||||
{ errstr => $@, name => 'version',
|
||||
extension => $extension }) if $@;
|
||||
|
||||
}
|
||||
|
||||
return \%enabled;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Hook - Extendable extension hooks for Bugzilla code
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Hook;
|
||||
|
||||
Bugzilla::Hook::process("hookname", { arg => $value, arg2 => $value2 });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla allows extension modules to drop in and add routines at
|
||||
arbitrary points in Bugzilla code. These points are referred to as
|
||||
hooks. When a piece of standard Bugzilla code wants to allow an extension
|
||||
to perform additional functions, it uses Bugzilla::Hook's L</process>
|
||||
subroutine to invoke any extension code if installed.
|
||||
|
||||
There is a sample extension in F<extensions/example/> that demonstrates
|
||||
most of the things described in this document, as well as many of the
|
||||
hooks available.
|
||||
|
||||
=head2 How Hooks Work
|
||||
|
||||
When a hook named C<HOOK_NAME> is run, Bugzilla will attempt to invoke any
|
||||
source files named F<extensions/*/code/HOOK_NAME.pl>.
|
||||
|
||||
So, for example, if your extension is called "testopia", and you
|
||||
want to have code run during the L</install-update_db> hook, you
|
||||
would have a file called F<extensions/testopia/code/install-update_db.pl>
|
||||
that contained perl code to run during that hook.
|
||||
|
||||
=head2 Arguments Passed to Hooks
|
||||
|
||||
Some L<hooks|/HOOKS> have params that are passed to them.
|
||||
|
||||
These params are accessible through L<Bugzilla/hook_args>.
|
||||
That returns a hashref. Very frequently, if you want your
|
||||
hook to do anything, you have to modify these variables.
|
||||
|
||||
=head2 Versioning Extensions
|
||||
|
||||
Every extension must have a file in its root called F<info.pl>.
|
||||
This file must return a hash when called with C<do>.
|
||||
The hash must contain a 'version' key with the current version of the
|
||||
extension. Extension authors can also add any extra infomration to this hash if
|
||||
required, by adding a new key beginning with x_ which will not be used the
|
||||
core Bugzilla code.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<process>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Invoke any code hooks with a matching name from any installed extensions.
|
||||
|
||||
See C<customization.xml> in the Bugzilla Guide for more information on
|
||||
Bugzilla's extension mechanism.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$name> - The name of the hook to invoke.
|
||||
|
||||
=item C<$args> - A hashref. The named args to pass to the hook.
|
||||
They will be accessible to the hook via L<Bugzilla/hook_args>.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head1 HOOKS
|
||||
|
||||
This describes what hooks exist in Bugzilla currently. They are mostly
|
||||
in alphabetical order, but some related hooks are near each other instead
|
||||
of being alphabetical.
|
||||
|
||||
=head2 bug-end_of_update
|
||||
|
||||
This happens at the end of L<Bugzilla::Bug/update>, after all other changes are
|
||||
made to the database. This generally occurs inside a database transaction.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug> - The changed bug object, with all fields set to their updated
|
||||
values.
|
||||
|
||||
=item C<timestamp> - The timestamp used for all updates in this transaction.
|
||||
|
||||
=item C<changes> - The hash of changed fields.
|
||||
C<$changes-E<gt>{field} = [old, new]>
|
||||
|
||||
=back
|
||||
|
||||
=head2 buglist-columns
|
||||
|
||||
This happens in buglist.cgi after the standard columns have been defined and
|
||||
right before the display column determination. It gives you the opportunity
|
||||
to add additional display columns.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - A hashref, where the keys are unique string identifiers
|
||||
for the column being defined and the values are hashrefs with the
|
||||
following fields:
|
||||
|
||||
=over
|
||||
|
||||
=item C<name> - The name of the column in the database.
|
||||
|
||||
=item C<title> - The title of the column as displayed to users.
|
||||
|
||||
=back
|
||||
|
||||
The definition is structured as:
|
||||
|
||||
$columns->{$id} = { name => $name, title => $title };
|
||||
|
||||
=back
|
||||
|
||||
=head2 colchange-columns
|
||||
|
||||
This happens in F<colchange.cgi> right after the list of possible display
|
||||
columns have been defined and gives you the opportunity to add additional
|
||||
display columns to the list of selectable columns.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - An arrayref containing an array of column IDs. Any IDs
|
||||
added by this hook must have been defined in the the buglist-columns hook.
|
||||
See L</buglist-columns>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 enter_bug-entrydefaultvars
|
||||
|
||||
This happens right before the template is loaded on enter_bug.cgi.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<vars> - A hashref. The variables that will be passed into the template.
|
||||
|
||||
=back
|
||||
|
||||
=head2 flag-end_of_update
|
||||
|
||||
This happens at the end of L<Bugzilla::Flag/process>, after all other changes
|
||||
are made to the database and after emails are sent. It gives you a before/after
|
||||
snapshot of flags so you can react to specific flag changes.
|
||||
This generally occurs inside a database transaction.
|
||||
|
||||
Note that the interface to this hook is B<UNSTABLE> and it may change in the
|
||||
future.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug> - The changed bug object.
|
||||
|
||||
=item C<timestamp> - The timestamp used for all updates in this transaction.
|
||||
|
||||
=item C<old_flags> - The snapshot of flag summaries from before the change.
|
||||
|
||||
=item C<new_flags> - The snapshot of flag summaries after the change. Call
|
||||
C<my ($removed, $added) = diff_arrays(old_flags, new_flags)> to get the list of
|
||||
changed flags, and search for a specific condition like C<added eq 'review-'>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 install-before_final_checks
|
||||
|
||||
Allows execution of custom code before the final checks are done in
|
||||
checksetup.pl.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<silent>
|
||||
|
||||
A flag that indicates whether or not checksetup is running in silent mode.
|
||||
|
||||
=back
|
||||
|
||||
=head2 install-requirements
|
||||
|
||||
Because of the way Bugzilla installation works, there can't be a normal
|
||||
hook during the time that F<checksetup.pl> checks what modules are
|
||||
installed. (C<Bugzilla::Hook> needs to have those modules installed--it's
|
||||
a chicken-and-egg problem.)
|
||||
|
||||
So instead of the way hooks normally work, this hook just looks for two
|
||||
subroutines (or constants, since all constants are just subroutines) in
|
||||
your file, called C<OPTIONAL_MODULES> and C<REQUIRED_MODULES>,
|
||||
which should return arrayrefs in the same format as C<OPTIONAL_MODULES> and
|
||||
C<REQUIRED_MODULES> in L<Bugzilla::Install::Requirements>.
|
||||
|
||||
These subroutines will be passed an arrayref that contains the current
|
||||
Bugzilla requirements of the same type, in case you want to modify
|
||||
Bugzilla's requirements somehow. (Probably the most common would be to
|
||||
alter a version number or the "feature" element of C<OPTIONAL_MODULES>.)
|
||||
|
||||
F<checksetup.pl> will add these requirements to its own.
|
||||
|
||||
Please remember--if you put something in C<REQUIRED_MODULES>, then
|
||||
F<checksetup.pl> B<cannot complete> unless the user has that module
|
||||
installed! So use C<OPTIONAL_MODULES> whenever you can.
|
||||
|
||||
=head2 install-update_db
|
||||
|
||||
This happens at the very end of all the tables being updated
|
||||
during an installation or upgrade. If you need to modify your custom
|
||||
schema, do it here. No params are passed.
|
||||
|
||||
=head2 db_schema-abstract_schema
|
||||
|
||||
This allows you to add tables to Bugzilla. Note that we recommend that you
|
||||
prefix the names of your tables with some word, so that they don't conflict
|
||||
with any future Bugzilla tables.
|
||||
|
||||
If you wish to add new I<columns> to existing Bugzilla tables, do that
|
||||
in L</install-update_db>.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<schema> - A hashref, in the format of
|
||||
L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>. Add new hash keys to make new table
|
||||
definitions. F<checksetup.pl> will automatically add these tables to the
|
||||
database when run.
|
||||
|
||||
=back
|
||||
|
||||
=head2 webservice
|
||||
|
||||
This hook allows you to add your own modules to the WebService. (See
|
||||
L<Bugzilla::WebService>.)
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<dispatch>
|
||||
|
||||
A hashref that you can specify the names of your modules and what Perl
|
||||
module handles the functions for that module. (This is actually sent to
|
||||
L<SOAP::Lite/dispatch_with>. You can see how that's used in F<xmlrpc.cgi>.)
|
||||
|
||||
The Perl module name must start with C<extensions::yourextension::lib::>
|
||||
(replace C<yourextension> with the name of your extension). The C<package>
|
||||
declaration inside that module must also start with
|
||||
C<extensions::yourextension::lib::> in that module's code.
|
||||
|
||||
Example:
|
||||
|
||||
$dispatch->{Example} = "extensions::example::lib::Example";
|
||||
|
||||
And then you'd have a module F<extensions/example/lib/Example.pm>
|
||||
|
||||
It's recommended that all the keys you put in C<dispatch> start with the
|
||||
name of your extension, so that you don't conflict with the standard Bugzilla
|
||||
WebService functions (and so that you also don't conflict with other
|
||||
plugins).
|
||||
|
||||
=back
|
||||
|
||||
=head2 webservice-error_codes
|
||||
|
||||
If your webservice extension throws custom errors, you can set numeric
|
||||
codes for those errors here.
|
||||
|
||||
Extensions should use error codes above 10000, unless they are re-using
|
||||
an already-existing error code.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<error_map>
|
||||
|
||||
A hash that maps the names of errors (like C<invalid_param>) to numbers.
|
||||
See L<Bugzilla::WebService::Constants/WS_ERROR_CODE> for an example.
|
||||
|
||||
=back
|
||||
@@ -1,452 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install;
|
||||
|
||||
# Functions in this this package can assume that the database
|
||||
# has been set up, params are available, localconfig is
|
||||
# available, and any module can be used.
|
||||
#
|
||||
# If you want to write an installation function that can't
|
||||
# make those assumptions, then it should go into one of the
|
||||
# packages under the Bugzilla::Install namespace.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::User::Setting;
|
||||
use Bugzilla::Util qw(get_text);
|
||||
use Bugzilla::Version;
|
||||
|
||||
sub SETTINGS {
|
||||
return {
|
||||
# 2005-03-03 travis@sedsystems.ca -- Bug 41972
|
||||
display_quips => { options => ["on", "off"], default => "on" },
|
||||
# 2005-03-10 travis@sedsystems.ca -- Bug 199048
|
||||
comment_sort_order => { options => ["oldest_to_newest", "newest_to_oldest",
|
||||
"newest_to_oldest_desc_first"],
|
||||
default => "oldest_to_newest" },
|
||||
# 2005-05-12 bugzilla@glob.com.au -- Bug 63536
|
||||
post_bug_submit_action => { options => ["next_bug", "same_bug", "nothing"],
|
||||
default => "next_bug" },
|
||||
# 2005-06-29 wurblzap@gmail.com -- Bug 257767
|
||||
csv_colsepchar => { options => [',',';'], default => ',' },
|
||||
# 2005-10-26 wurblzap@gmail.com -- Bug 291459
|
||||
zoom_textareas => { options => ["on", "off"], default => "on" },
|
||||
# 2005-10-21 LpSolit@gmail.com -- Bug 313020
|
||||
per_bug_queries => { options => ['on', 'off'], default => 'off' },
|
||||
# 2006-05-01 olav@bkor.dhs.org -- Bug 7710
|
||||
state_addselfcc => { options => ['always', 'never', 'cc_unless_role'],
|
||||
default => 'cc_unless_role' },
|
||||
# 2006-08-04 wurblzap@gmail.com -- Bug 322693
|
||||
skin => { subclass => 'Skin', default => 'Dusk' },
|
||||
# 2006-12-10 LpSolit@gmail.com -- Bug 297186
|
||||
lang => { subclass => 'Lang',
|
||||
default => ${Bugzilla->languages}[0] },
|
||||
# 2007-07-02 altlist@gmail.com -- Bug 225731
|
||||
quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'],
|
||||
default => "quoted_reply" }
|
||||
}
|
||||
};
|
||||
|
||||
use constant SYSTEM_GROUPS => (
|
||||
{
|
||||
name => 'admin',
|
||||
description => 'Administrators'
|
||||
},
|
||||
{
|
||||
name => 'tweakparams',
|
||||
description => 'Can change Parameters'
|
||||
},
|
||||
{
|
||||
name => 'editusers',
|
||||
description => 'Can edit or disable users'
|
||||
},
|
||||
{
|
||||
name => 'creategroups',
|
||||
description => 'Can create and destroy groups'
|
||||
},
|
||||
{
|
||||
name => 'editclassifications',
|
||||
description => 'Can create, destroy, and edit classifications'
|
||||
},
|
||||
{
|
||||
name => 'editcomponents',
|
||||
description => 'Can create, destroy, and edit components'
|
||||
},
|
||||
{
|
||||
name => 'editkeywords',
|
||||
description => 'Can create, destroy, and edit keywords'
|
||||
},
|
||||
{
|
||||
name => 'editbugs',
|
||||
description => 'Can edit all bug fields',
|
||||
userregexp => '.*'
|
||||
},
|
||||
{
|
||||
name => 'canconfirm',
|
||||
description => 'Can confirm a bug or mark it a duplicate'
|
||||
},
|
||||
{
|
||||
name => 'bz_canusewhines',
|
||||
description => 'User can configure whine reports for self'
|
||||
},
|
||||
{
|
||||
name => 'bz_sudoers',
|
||||
description => 'Can perform actions as other users'
|
||||
},
|
||||
# There are also other groups created in update_system_groups.
|
||||
);
|
||||
|
||||
use constant DEFAULT_CLASSIFICATION => {
|
||||
name => 'Unclassified',
|
||||
description => 'Not assigned to any classification'
|
||||
};
|
||||
|
||||
use constant DEFAULT_PRODUCT => {
|
||||
name => 'TestProduct',
|
||||
description => 'This is a test product.'
|
||||
. ' This ought to be blown away and replaced with real stuff in a'
|
||||
. ' finished installation of bugzilla.'
|
||||
};
|
||||
|
||||
use constant DEFAULT_COMPONENT => {
|
||||
name => 'TestComponent',
|
||||
description => 'This is a test component in the test product database.'
|
||||
. ' This ought to be blown away and replaced with real stuff in'
|
||||
. ' a finished installation of Bugzilla.'
|
||||
};
|
||||
|
||||
sub update_settings {
|
||||
my %settings = %{SETTINGS()};
|
||||
foreach my $setting (keys %settings) {
|
||||
add_setting($setting,
|
||||
$settings{$setting}->{options},
|
||||
$settings{$setting}->{default},
|
||||
$settings{$setting}->{subclass});
|
||||
}
|
||||
}
|
||||
|
||||
sub update_system_groups {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Create most of the system groups
|
||||
foreach my $definition (SYSTEM_GROUPS) {
|
||||
my $exists = new Bugzilla::Group({ name => $definition->{name} });
|
||||
$definition->{isbuggroup} = 0;
|
||||
Bugzilla::Group->create($definition) unless $exists;
|
||||
}
|
||||
|
||||
# Certain groups need something done after they are created. We do
|
||||
# that here.
|
||||
|
||||
# Make sure people who can whine at others can also whine.
|
||||
if (!new Bugzilla::Group({name => 'bz_canusewhineatothers'})) {
|
||||
my $whineatothers = Bugzilla::Group->create({
|
||||
name => 'bz_canusewhineatothers',
|
||||
description => 'Can configure whine reports for other users',
|
||||
isbuggroup => 0 });
|
||||
my $whine = new Bugzilla::Group({ name => 'bz_canusewhines' });
|
||||
|
||||
$dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
|
||||
VALUES (?,?)', undef, $whine->id, $whineatothers->id);
|
||||
}
|
||||
|
||||
# Make sure sudoers are automatically protected from being sudoed.
|
||||
if (!new Bugzilla::Group({name => 'bz_sudo_protect'})) {
|
||||
my $sudo_protect = Bugzilla::Group->create({
|
||||
name => 'bz_sudo_protect',
|
||||
description => 'Can not be impersonated by other users',
|
||||
isbuggroup => 0 });
|
||||
my $sudo = new Bugzilla::Group({ name => 'bz_sudoers' });
|
||||
$dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
|
||||
VALUES (?,?)', undef, $sudo_protect->id, $sudo->id);
|
||||
}
|
||||
|
||||
# Re-evaluate all regexps, to keep them up-to-date.
|
||||
my $sth = $dbh->prepare(
|
||||
"SELECT profiles.userid, profiles.login_name, groups.id,
|
||||
groups.userregexp, user_group_map.group_id
|
||||
FROM (profiles CROSS JOIN groups)
|
||||
LEFT JOIN user_group_map
|
||||
ON user_group_map.user_id = profiles.userid
|
||||
AND user_group_map.group_id = groups.id
|
||||
AND user_group_map.grant_type = ?
|
||||
WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL");
|
||||
|
||||
my $sth_add = $dbh->prepare(
|
||||
"INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
|
||||
VALUES (?, ?, 0, " . GRANT_REGEXP . ")");
|
||||
|
||||
my $sth_del = $dbh->prepare(
|
||||
"DELETE FROM user_group_map
|
||||
WHERE user_id = ? AND group_id = ? AND isbless = 0
|
||||
AND grant_type = " . GRANT_REGEXP);
|
||||
|
||||
$sth->execute(GRANT_REGEXP);
|
||||
while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) {
|
||||
if ($login =~ m/$rexp/i) {
|
||||
$sth_add->execute($uid, $gid) unless $present;
|
||||
} else {
|
||||
$sth_del->execute($uid, $gid) if $present;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# This function should be called only after creating the admin user.
|
||||
sub create_default_product {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Make the default Classification if it doesn't already exist.
|
||||
if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
|
||||
my $class = DEFAULT_CLASSIFICATION;
|
||||
print get_text('install_default_classification',
|
||||
{ name => $class->{name} }) . "\n";
|
||||
$dbh->do('INSERT INTO classifications (name, description)
|
||||
VALUES (?, ?)',
|
||||
undef, $class->{name}, $class->{description});
|
||||
}
|
||||
|
||||
# And same for the default product/component.
|
||||
if (!$dbh->selectrow_array('SELECT 1 FROM products')) {
|
||||
my $default_prod = DEFAULT_PRODUCT;
|
||||
print get_text('install_default_product',
|
||||
{ name => $default_prod->{name} }) . "\n";
|
||||
|
||||
$dbh->do(q{INSERT INTO products (name, description)
|
||||
VALUES (?,?)},
|
||||
undef, $default_prod->{name}, $default_prod->{description});
|
||||
|
||||
my $product = new Bugzilla::Product({name => $default_prod->{name}});
|
||||
|
||||
# The default version.
|
||||
Bugzilla::Version::create(Bugzilla::Version::DEFAULT_VERSION, $product);
|
||||
|
||||
# And we automatically insert the default milestone.
|
||||
$dbh->do(q{INSERT INTO milestones (product_id, value, sortkey)
|
||||
SELECT id, defaultmilestone, 0
|
||||
FROM products});
|
||||
|
||||
# Get the user who will be the owner of the Product.
|
||||
# We pick the admin with the lowest id, or we insert
|
||||
# an invalid "0" into the database, just so that we can
|
||||
# create the component.
|
||||
my $admin_group = new Bugzilla::Group({name => 'admin'});
|
||||
my ($admin_id) = $dbh->selectrow_array(
|
||||
'SELECT user_id FROM user_group_map WHERE group_id = ?
|
||||
ORDER BY user_id ' . $dbh->sql_limit(1),
|
||||
undef, $admin_group->id) || 0;
|
||||
|
||||
my $default_comp = DEFAULT_COMPONENT;
|
||||
|
||||
$dbh->do("INSERT INTO components (name, product_id, description,
|
||||
initialowner)
|
||||
VALUES (?, ?, ?, ?)", undef, $default_comp->{name},
|
||||
$product->id, $default_comp->{description}, $admin_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub create_admin {
|
||||
my ($params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
my $admin_group = new Bugzilla::Group({ name => 'admin' });
|
||||
my $admin_inheritors =
|
||||
Bugzilla::User->flatten_group_membership($admin_group->id);
|
||||
my $admin_group_ids = join(',', @$admin_inheritors);
|
||||
|
||||
my ($admin_count) = $dbh->selectrow_array(
|
||||
"SELECT COUNT(*) FROM user_group_map
|
||||
WHERE group_id IN ($admin_group_ids)");
|
||||
|
||||
return if $admin_count;
|
||||
|
||||
my %answer = %{Bugzilla->installation_answers};
|
||||
my $login = $answer{'ADMIN_EMAIL'};
|
||||
my $password = $answer{'ADMIN_PASSWORD'};
|
||||
my $full_name = $answer{'ADMIN_REALNAME'};
|
||||
|
||||
if (!$login || !$password || !$full_name) {
|
||||
print "\n" . get_text('install_admin_setup') . "\n\n";
|
||||
}
|
||||
|
||||
while (!$login) {
|
||||
print get_text('install_admin_get_email') . ' ';
|
||||
$login = <STDIN>;
|
||||
chomp $login;
|
||||
eval { Bugzilla::User->check_login_name_for_creation($login); };
|
||||
if ($@) {
|
||||
print $@ . "\n";
|
||||
undef $login;
|
||||
}
|
||||
}
|
||||
|
||||
while (!defined $full_name) {
|
||||
print get_text('install_admin_get_name') . ' ';
|
||||
$full_name = <STDIN>;
|
||||
chomp($full_name);
|
||||
}
|
||||
|
||||
if (!$password) {
|
||||
$password = _prompt_for_password(
|
||||
get_text('install_admin_get_password'));
|
||||
}
|
||||
|
||||
my $admin = Bugzilla::User->create({ login_name => $login,
|
||||
realname => $full_name,
|
||||
cryptpassword => $password });
|
||||
make_admin($admin);
|
||||
}
|
||||
|
||||
sub make_admin {
|
||||
my ($user) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$user = ref($user) ? $user
|
||||
: new Bugzilla::User(login_to_id($user, THROW_ERROR));
|
||||
|
||||
my $admin_group = new Bugzilla::Group({ name => 'admin' });
|
||||
|
||||
# Admins get explicit membership and bless capability for the admin group
|
||||
$dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
|
||||
|
||||
my $group_insert = $dbh->prepare(
|
||||
'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
|
||||
VALUES (?, ?, ?, ?)');
|
||||
# These are run in an eval so that we can ignore the error of somebody
|
||||
# already being granted these things.
|
||||
eval {
|
||||
$group_insert->execute($user->id, $admin_group->id, 0, GRANT_DIRECT);
|
||||
};
|
||||
eval {
|
||||
$group_insert->execute($user->id, $admin_group->id, 1, GRANT_DIRECT);
|
||||
};
|
||||
|
||||
# Admins should also have editusers directly, even though they'll usually
|
||||
# inherit it. People could have changed their inheritance structure.
|
||||
my $editusers = new Bugzilla::Group({ name => 'editusers' });
|
||||
eval {
|
||||
$group_insert->execute($user->id, $editusers->id, 0, GRANT_DIRECT);
|
||||
};
|
||||
|
||||
print "\n", get_text('install_admin_created', { user => $user }), "\n";
|
||||
}
|
||||
|
||||
sub _prompt_for_password {
|
||||
my $prompt = shift;
|
||||
|
||||
my $password;
|
||||
while (!$password) {
|
||||
# trap a few interrupts so we can fix the echo if we get aborted.
|
||||
local $SIG{HUP} = \&_password_prompt_exit;
|
||||
local $SIG{INT} = \&_password_prompt_exit;
|
||||
local $SIG{QUIT} = \&_password_prompt_exit;
|
||||
local $SIG{TERM} = \&_password_prompt_exit;
|
||||
|
||||
system("stty","-echo") unless ON_WINDOWS; # disable input echoing
|
||||
|
||||
print $prompt, ' ';
|
||||
$password = <STDIN>;
|
||||
chomp $password;
|
||||
print "\n", get_text('install_confirm_password'), ' ';
|
||||
my $pass2 = <STDIN>;
|
||||
chomp $pass2;
|
||||
eval { validate_password($password, $pass2); };
|
||||
if ($@) {
|
||||
print "\n$@\n";
|
||||
undef $password;
|
||||
}
|
||||
system("stty","echo") unless ON_WINDOWS;
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
# This is just in case we get interrupted while getting a password.
|
||||
sub _password_prompt_exit {
|
||||
# re-enable input echoing
|
||||
system("stty","echo") unless ON_WINDOWS;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub reset_password {
|
||||
my $login = shift;
|
||||
my $user = Bugzilla::User->check($login);
|
||||
my $prompt = "\n" . get_text('install_reset_password', { user => $user });
|
||||
my $password = _prompt_for_password($prompt);
|
||||
$user->set_password($password);
|
||||
$user->update();
|
||||
print "\n", get_text('install_reset_password_done'), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install - Functions and variables having to do with
|
||||
installation.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install;
|
||||
Bugzilla::Install::update_settings();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> during installation.
|
||||
This module contains functions that deal with general installation
|
||||
issues after the database is completely set up and configured.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<SETTINGS>
|
||||
|
||||
Contains information about Settings, used by L</update_settings()>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<update_settings()>
|
||||
|
||||
Description: Adds and updates Settings for users.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing.
|
||||
|
||||
=item C<create_default_product()>
|
||||
|
||||
Description: Creates the default product and classification if
|
||||
they don't exist.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,251 +0,0 @@
|
||||
# -*- 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, Inc.
|
||||
# Portions created by Everything Solved are Copyright (C) 2007
|
||||
# Everything Solved, Inc. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::CPAN;
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(set_cpan_config install_module BZ_LIB);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(bin_loc install_string);
|
||||
|
||||
use CPAN;
|
||||
use Cwd qw(abs_path);
|
||||
use File::Path qw(rmtree);
|
||||
use List::Util qw(shuffle);
|
||||
|
||||
# We need the absolute path of ext_libpath, because CPAN chdirs around
|
||||
# and so we can't use a relative directory.
|
||||
#
|
||||
# We need it often enough (and at compile time, in install-module.pl) so
|
||||
# we make it a constant.
|
||||
use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath});
|
||||
|
||||
# CPAN requires nearly all of its parameters to be set, or it will start
|
||||
# asking questions to the user. We want to avoid that, so we have
|
||||
# defaults here for most of the required parameters we know about, in case
|
||||
# any of them aren't set. The rest are handled by set_cpan_defaults().
|
||||
use constant CPAN_DEFAULTS => {
|
||||
auto_commit => 0,
|
||||
# We always force builds, so there's no reason to cache them.
|
||||
build_cache => 0,
|
||||
cache_metadata => 1,
|
||||
index_expire => 1,
|
||||
scan_cache => 'atstart',
|
||||
|
||||
inhibit_startup_message => 1,
|
||||
mbuild_install_build_command => './Build',
|
||||
|
||||
curl => bin_loc('curl'),
|
||||
gzip => bin_loc('gzip'),
|
||||
links => bin_loc('links'),
|
||||
lynx => bin_loc('lynx'),
|
||||
make => bin_loc('make'),
|
||||
pager => bin_loc('less'),
|
||||
tar => bin_loc('tar'),
|
||||
unzip => bin_loc('unzip'),
|
||||
wget => bin_loc('wget'),
|
||||
|
||||
urllist => [shuffle qw(
|
||||
http://cpan.pair.com/
|
||||
http://mirror.hiwaay.net/CPAN/
|
||||
ftp://ftp.dc.aleron.net/pub/CPAN/
|
||||
http://perl.secsup.org/
|
||||
http://mirrors.kernel.org/cpan/)],
|
||||
};
|
||||
|
||||
sub install_module {
|
||||
my ($name, $notest) = @_;
|
||||
my $bzlib = BZ_LIB;
|
||||
|
||||
# Certain modules require special stuff in order to not prompt us.
|
||||
my $original_makepl = $CPAN::Config->{makepl_arg};
|
||||
# This one's a regex in case we're doing Template::Plugin::GD and it
|
||||
# pulls in Template-Toolkit as a dependency.
|
||||
if ($name =~ /^Template/) {
|
||||
$CPAN::Config->{makepl_arg} .= " TT_ACCEPT=y TT_EXTRAS=n";
|
||||
}
|
||||
elsif ($name eq 'XML::Twig') {
|
||||
$CPAN::Config->{makepl_arg} = "-n $original_makepl";
|
||||
}
|
||||
elsif ($name eq 'Net::LDAP') {
|
||||
$CPAN::Config->{makepl_arg} .= " --skipdeps";
|
||||
}
|
||||
elsif ($name eq 'SOAP::Lite') {
|
||||
$CPAN::Config->{makepl_arg} .= " --noprompt";
|
||||
}
|
||||
|
||||
my $module = CPAN::Shell->expand('Module', $name);
|
||||
print install_string('install_module',
|
||||
{ module => $name, version => $module->cpan_version }) . "\n";
|
||||
if ($notest) {
|
||||
CPAN::Shell->notest('install', $name);
|
||||
}
|
||||
else {
|
||||
CPAN::Shell->force('install', $name);
|
||||
}
|
||||
|
||||
# If it installed any binaries in the Bugzilla directory, delete them.
|
||||
if (-d "$bzlib/bin") {
|
||||
File::Path::rmtree("$bzlib/bin");
|
||||
}
|
||||
|
||||
$CPAN::Config->{makepl_arg} = $original_makepl;
|
||||
}
|
||||
|
||||
sub set_cpan_config {
|
||||
my $do_global = shift;
|
||||
my $bzlib = BZ_LIB;
|
||||
|
||||
# We set defaults before we do anything, otherwise CPAN will
|
||||
# start asking us questions as soon as we load its configuration.
|
||||
eval { require CPAN::Config; };
|
||||
_set_cpan_defaults();
|
||||
|
||||
# Calling a senseless autoload that does nothing makes us
|
||||
# automatically load any existing configuration.
|
||||
# We want to avoid the "invalid command" message.
|
||||
open(my $saveout, ">&STDOUT");
|
||||
open(STDOUT, '>/dev/null');
|
||||
eval { CPAN->ignore_this_error_message_from_bugzilla; };
|
||||
undef $@;
|
||||
close(STDOUT);
|
||||
open(STDOUT, '>&', $saveout);
|
||||
|
||||
my $dir = $CPAN::Config->{cpan_home};
|
||||
if (!defined $dir || !-w $dir) {
|
||||
# If we can't use the standard CPAN build dir, we try to make one.
|
||||
$dir = "$ENV{HOME}/.cpan";
|
||||
mkdir $dir;
|
||||
|
||||
# If we can't make one, we finally try to use the Bugzilla directory.
|
||||
if (!-w $dir) {
|
||||
print "WARNING: Using the Bugzilla directory as the CPAN home.\n";
|
||||
$dir = "$bzlib/.cpan";
|
||||
}
|
||||
}
|
||||
$CPAN::Config->{cpan_home} = $dir;
|
||||
$CPAN::Config->{build_dir} = "$dir/build";
|
||||
# We always force builds, so there's no reason to cache them.
|
||||
$CPAN::Config->{keep_source_where} = "$dir/source";
|
||||
# This is set both here and in defaults so that it's always true.
|
||||
$CPAN::Config->{inhibit_startup_message} = 1;
|
||||
# Automatically install dependencies.
|
||||
$CPAN::Config->{prerequisites_policy} = 'follow';
|
||||
|
||||
# Unless specified, we install the modules into the Bugzilla directory.
|
||||
if (!$do_global) {
|
||||
$CPAN::Config->{makepl_arg} .= " LIB=\"$bzlib\""
|
||||
. " INSTALLMAN1DIR=\"$bzlib/man/man1\""
|
||||
. " INSTALLMAN3DIR=\"$bzlib/man/man3\""
|
||||
# The bindirs are here because otherwise we'll try to write to
|
||||
# the system binary dirs, and that will cause CPAN to die.
|
||||
. " INSTALLBIN=\"$bzlib/bin\""
|
||||
. " INSTALLSCRIPT=\"$bzlib/bin\""
|
||||
# INSTALLDIRS=perl is set because that makes sure that MakeMaker
|
||||
# always uses the directories we've specified here.
|
||||
. " INSTALLDIRS=perl";
|
||||
$CPAN::Config->{mbuild_arg} = "--install_base \"$bzlib\"";
|
||||
|
||||
# When we're not root, sometimes newer versions of CPAN will
|
||||
# try to read/modify things that belong to root, unless we set
|
||||
# certain config variables.
|
||||
$CPAN::Config->{histfile} = "$dir/histfile";
|
||||
$CPAN::Config->{use_sqlite} = 0;
|
||||
$CPAN::Config->{prefs_dir} = "$dir/prefs";
|
||||
|
||||
# Unless we actually set PERL5LIB, some modules can't install
|
||||
# themselves, like DBD::mysql, DBD::Pg, and XML::Twig.
|
||||
my $current_lib = $ENV{PERL5LIB} ? $ENV{PERL5LIB} . ':' : '';
|
||||
$ENV{PERL5LIB} = $current_lib . $bzlib;
|
||||
}
|
||||
}
|
||||
|
||||
sub _set_cpan_defaults {
|
||||
# If CPAN hasn't been configured, we try to use some reasonable defaults.
|
||||
foreach my $key (keys %{CPAN_DEFAULTS()}) {
|
||||
$CPAN::Config->{$key} = CPAN_DEFAULTS->{$key}
|
||||
if !defined $CPAN::Config->{$key};
|
||||
}
|
||||
|
||||
my @missing;
|
||||
# In newer CPANs, this is in HandleConfig. In older CPANs, it's in
|
||||
# Config.
|
||||
if (eval { require CPAN::HandleConfig }) {
|
||||
@missing = CPAN::HandleConfig->missing_config_data;
|
||||
}
|
||||
else {
|
||||
@missing = CPAN::Config->missing_config_data;
|
||||
}
|
||||
|
||||
foreach my $key (@missing) {
|
||||
$CPAN::Config->{$key} = '';
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::CPAN - Routines to install Perl modules from CPAN.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install::CPAN;
|
||||
|
||||
set_cpan_config();
|
||||
install_module('Module::Name', 1);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is primarily used by L<install-module> to do the "hard work" of
|
||||
installing CPAN modules.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<set_cpan_config>
|
||||
|
||||
Sets up the configuration of CPAN for this session. Must be called
|
||||
before L</install_module>. Takes one boolean parameter. If true,
|
||||
L</install_module> will install modules globally instead of to the
|
||||
local F<lib/> directory. On most systems, you have to be root to do that.
|
||||
|
||||
=item C<install_module>
|
||||
|
||||
Installs a module from CPAN. Takes two arguments:
|
||||
|
||||
=over
|
||||
|
||||
=item C<$name> - The name of the module, just like you'd pass to the
|
||||
C<install> command in the CPAN shell.
|
||||
|
||||
=item C<$notest> - If true, we skip running tests on this module. This
|
||||
can greatly speed up the installation time.
|
||||
|
||||
=back
|
||||
|
||||
Note that calling this function prints a B<lot> of information to
|
||||
STDOUT and STDERR.
|
||||
|
||||
=back
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,690 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Bill Barry <after.fallout@gmail.com>
|
||||
|
||||
package Bugzilla::Install::Filesystem;
|
||||
|
||||
# NOTE: This package may "use" any modules that it likes,
|
||||
# and localconfig is available. However, all functions in this
|
||||
# package should assume that:
|
||||
#
|
||||
# * Templates are not available.
|
||||
# * Files do not have the correct permissions.
|
||||
# * The database does not exist.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Install::Localconfig;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use File::Find;
|
||||
use File::Path;
|
||||
use File::Basename;
|
||||
use IO::File;
|
||||
use POSIX ();
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(
|
||||
update_filesystem
|
||||
create_htaccess
|
||||
fix_all_file_permissions
|
||||
);
|
||||
|
||||
# This looks like a constant because it effectively is, but
|
||||
# it has to call other subroutines and read the current filesystem,
|
||||
# so it's defined as a sub. This is not exported, so it doesn't have
|
||||
# a perldoc. However, look at the various hashes defined inside this
|
||||
# function to understand what it returns. (There are comments throughout.)
|
||||
#
|
||||
# The rationale for the file permissions is that the web server generally
|
||||
# runs as apache, so the cgi scripts should not be writable for apache,
|
||||
# otherwise someone may find it possible to change the cgis when exploiting
|
||||
# some security flaw somewhere (not necessarily in Bugzilla!)
|
||||
sub FILESYSTEM {
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
my $attachdir = bz_locations()->{'attachdir'};
|
||||
my $extensionsdir = bz_locations()->{'extensionsdir'};
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
my $libdir = bz_locations()->{'libpath'};
|
||||
my $extlib = bz_locations()->{'ext_libpath'};
|
||||
my $skinsdir = bz_locations()->{'skinsdir'};
|
||||
|
||||
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
|
||||
|
||||
# The set of permissions that we use:
|
||||
|
||||
# FILES
|
||||
# Executable by the web server
|
||||
my $ws_executable = $ws_group ? 0750 : 0755;
|
||||
# Executable by the owner only.
|
||||
my $owner_executable = 0700;
|
||||
# Readable by the web server.
|
||||
my $ws_readable = $ws_group ? 0640 : 0644;
|
||||
# Readable by the owner only.
|
||||
my $owner_readable = 0600;
|
||||
# Writeable by the web server.
|
||||
my $ws_writeable = $ws_group ? 0660 : 0666;
|
||||
|
||||
# DIRECTORIES
|
||||
# Readable by the web server.
|
||||
my $ws_dir_readable = $ws_group ? 0750 : 0755;
|
||||
# Readable only by the owner.
|
||||
my $owner_dir_readable = 0700;
|
||||
# Writeable by the web server.
|
||||
my $ws_dir_writeable = $ws_group ? 0770 : 01777;
|
||||
# The web server can overwrite files owned by other users,
|
||||
# in this directory.
|
||||
my $ws_dir_full_control = $ws_group ? 0770 : 0777;
|
||||
|
||||
# Note: When being processed by checksetup, these have their permissions
|
||||
# set in this order: %all_dirs, %recurse_dirs, %all_files.
|
||||
#
|
||||
# Each is processed in alphabetical order of keys, so shorter keys
|
||||
# will have their permissions set before longer keys (thus setting
|
||||
# the permissions on parent directories before setting permissions
|
||||
# on their children).
|
||||
|
||||
# --- FILE PERMISSIONS (Non-created files) --- #
|
||||
my %files = (
|
||||
'*' => { perms => $ws_readable },
|
||||
'*.cgi' => { perms => $ws_executable },
|
||||
'whineatnews.pl' => { perms => $ws_executable },
|
||||
'collectstats.pl' => { perms => $ws_executable },
|
||||
'checksetup.pl' => { perms => $owner_executable },
|
||||
'importxml.pl' => { perms => $ws_executable },
|
||||
'runtests.pl' => { perms => $owner_executable },
|
||||
'testserver.pl' => { perms => $ws_executable },
|
||||
'whine.pl' => { perms => $ws_executable },
|
||||
'customfield.pl' => { perms => $owner_executable },
|
||||
'email_in.pl' => { perms => $ws_executable },
|
||||
'sanitycheck.pl' => { perms => $ws_executable },
|
||||
'install-module.pl' => { perms => $owner_executable },
|
||||
|
||||
'docs/makedocs.pl' => { perms => $owner_executable },
|
||||
'docs/style.css' => { perms => $ws_readable },
|
||||
'docs/*/rel_notes.txt' => { perms => $ws_readable },
|
||||
'docs/*/README.docs' => { perms => $owner_readable },
|
||||
"$datadir/bugzilla-update.xml" => { perms => $ws_writeable },
|
||||
"$datadir/params" => { perms => $ws_writeable },
|
||||
"$datadir/mailer.testfile" => { perms => $ws_writeable },
|
||||
);
|
||||
|
||||
# Directories that we want to set the perms on, but not
|
||||
# recurse through. These are directories we didn't create
|
||||
# in checkesetup.pl.
|
||||
my %non_recurse_dirs = (
|
||||
'.' => $ws_dir_readable,
|
||||
docs => $ws_dir_readable,
|
||||
);
|
||||
|
||||
# This sets the permissions for each item inside each of these
|
||||
# directories, including the directory itself.
|
||||
# 'CVS' directories are special, though, and are never readable by
|
||||
# the webserver.
|
||||
my %recurse_dirs = (
|
||||
# Writeable directories
|
||||
"$datadir/template" => { files => $ws_readable,
|
||||
dirs => $ws_dir_full_control },
|
||||
$attachdir => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
$webdotdir => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
graphs => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
|
||||
# Readable directories
|
||||
"$datadir/mining" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
"$datadir/duplicates" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
"$libdir/Bugzilla" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$extlib => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$templatedir => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$extensionsdir => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
images => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
css => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
js => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
skins => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
t => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
'docs/*/html' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/pdf' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/txt' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/images' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/lib' => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
'docs/*/xml' => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
);
|
||||
|
||||
# --- FILES TO CREATE --- #
|
||||
|
||||
# The name of each directory that we should actually *create*,
|
||||
# pointing at its default permissions.
|
||||
my %create_dirs = (
|
||||
$datadir => $ws_dir_full_control,
|
||||
"$datadir/mining" => $ws_dir_readable,
|
||||
"$datadir/duplicates" => $ws_dir_readable,
|
||||
$attachdir => $ws_dir_writeable,
|
||||
$extensionsdir => $ws_dir_readable,
|
||||
graphs => $ws_dir_writeable,
|
||||
$webdotdir => $ws_dir_writeable,
|
||||
'skins/custom' => $ws_dir_readable,
|
||||
'skins/contrib' => $ws_dir_readable,
|
||||
);
|
||||
|
||||
# The name of each file, pointing at its default permissions and
|
||||
# default contents.
|
||||
my %create_files = ();
|
||||
|
||||
# Each standard stylesheet has an associated custom stylesheet that
|
||||
# we create. Also, we create placeholders for standard stylesheets
|
||||
# for contrib skins which don't provide them themselves.
|
||||
foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) {
|
||||
next unless -d $skin_dir;
|
||||
next if basename($skin_dir) =~ /^cvs$/i;
|
||||
$create_dirs{"$skin_dir/yui"} = $ws_dir_readable;
|
||||
foreach my $base_css (<$skinsdir/standard/*.css>) {
|
||||
_add_custom_css($skin_dir, basename($base_css), \%create_files, $ws_readable);
|
||||
}
|
||||
foreach my $dir_css (<$skinsdir/standard/*/*.css>) {
|
||||
$dir_css =~ s{.+?([^/]+/[^/]+)$}{$1};
|
||||
_add_custom_css($skin_dir, $dir_css, \%create_files, $ws_readable);
|
||||
}
|
||||
}
|
||||
|
||||
# Because checksetup controls the creation of index.html separately
|
||||
# from all other files, it gets its very own hash.
|
||||
my %index_html = (
|
||||
'index.html' => { perms => $ws_readable, contents => <<EOT
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Refresh" content="0; URL=index.cgi">
|
||||
</head>
|
||||
<body>
|
||||
<h1>I think you are looking for <a href="index.cgi">index.cgi</a></h1>
|
||||
</body>
|
||||
</html>
|
||||
EOT
|
||||
}
|
||||
);
|
||||
|
||||
# Because checksetup controls the .htaccess creation separately
|
||||
# by a localconfig variable, these go in a separate variable from
|
||||
# %create_files.
|
||||
my $ht_default_deny = <<EOT;
|
||||
# nothing in this directory is retrievable unless overridden by an .htaccess
|
||||
# in a subdirectory
|
||||
deny from all
|
||||
EOT
|
||||
|
||||
my %htaccess = (
|
||||
"$attachdir/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$libdir/Bugzilla/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$extlib/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$templatedir/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
|
||||
'.htaccess' => { perms => $ws_readable, contents => <<EOT
|
||||
# Don't allow people to retrieve non-cgi executable files or our private data
|
||||
<FilesMatch ^(.*\\.pm|.*\\.pl|.*localconfig.*)\$>
|
||||
deny from all
|
||||
</FilesMatch>
|
||||
EOT
|
||||
},
|
||||
|
||||
"$webdotdir/.htaccess" => { perms => $ws_readable, contents => <<EOT
|
||||
# Restrict access to .dot files to the public webdot server at research.att.com
|
||||
# if research.att.com ever changes their IP, or if you use a different
|
||||
# webdot server, you'll need to edit this
|
||||
<FilesMatch \\.dot\$>
|
||||
Allow from 192.20.225.0/24
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Allow access to .png files created by a local copy of 'dot'
|
||||
<FilesMatch \\.png\$>
|
||||
Allow from all
|
||||
</FilesMatch>
|
||||
|
||||
# And no directory listings, either.
|
||||
Deny from all
|
||||
EOT
|
||||
},
|
||||
|
||||
# Even though $datadir may not (and should not) be accessible from the
|
||||
# web server, we can't know for sure, so create the .htaccess anyway.
|
||||
# It's harmless if it isn't accessible...
|
||||
"$datadir/.htaccess" => { perms => $ws_readable, contents => <<EOT
|
||||
# Nothing in this directory is retrievable unless overridden by an .htaccess
|
||||
# in a subdirectory; the only exception is duplicates.rdf, which is used by
|
||||
# duplicates.xul and must be accessible from the web server
|
||||
deny from all
|
||||
<Files duplicates.rdf>
|
||||
allow from all
|
||||
</Files>
|
||||
EOT
|
||||
|
||||
|
||||
},
|
||||
);
|
||||
|
||||
my %all_files = (%create_files, %htaccess, %index_html, %files);
|
||||
my %all_dirs = (%create_dirs, %non_recurse_dirs);
|
||||
|
||||
return {
|
||||
create_dirs => \%create_dirs,
|
||||
recurse_dirs => \%recurse_dirs,
|
||||
all_dirs => \%all_dirs,
|
||||
|
||||
create_files => \%create_files,
|
||||
htaccess => \%htaccess,
|
||||
index_html => \%index_html,
|
||||
all_files => \%all_files,
|
||||
};
|
||||
}
|
||||
|
||||
sub update_filesystem {
|
||||
my ($params) = @_;
|
||||
my $fs = FILESYSTEM();
|
||||
my %dirs = %{$fs->{create_dirs}};
|
||||
my %files = %{$fs->{create_files}};
|
||||
|
||||
my $datadir = bz_locations->{'datadir'};
|
||||
# If the graphs/ directory doesn't exist, we're upgrading from
|
||||
# a version old enough that we need to update the $datadir/mining
|
||||
# format.
|
||||
if (-d "$datadir/mining" && !-d 'graphs') {
|
||||
_update_old_charts($datadir);
|
||||
}
|
||||
|
||||
# By sorting the dirs, we assure that shorter-named directories
|
||||
# (meaning parent directories) are always created before their
|
||||
# child directories.
|
||||
foreach my $dir (sort keys %dirs) {
|
||||
unless (-d $dir) {
|
||||
print "Creating $dir directory...\n";
|
||||
mkdir $dir || die $!;
|
||||
# For some reason, passing in the permissions to "mkdir"
|
||||
# doesn't work right, but doing a "chmod" does.
|
||||
chmod $dirs{$dir}, $dir || die $!;
|
||||
}
|
||||
}
|
||||
|
||||
_create_files(%files);
|
||||
if ($params->{index_html}) {
|
||||
_create_files(%{$fs->{index_html}});
|
||||
}
|
||||
elsif (-e 'index.html') {
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
print <<EOT;
|
||||
|
||||
*** It appears that you still have an old index.html hanging around.
|
||||
Either the contents of this file should be moved into a template and
|
||||
placed in the '$templatedir/en/custom' directory, or you should delete
|
||||
the file.
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
# Delete old files that no longer need to exist
|
||||
|
||||
# 2001-04-29 jake@bugzilla.org - Remove oldemailtech
|
||||
# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
|
||||
if (-d 'shadow') {
|
||||
print "Removing shadow directory...\n";
|
||||
rmtree("shadow");
|
||||
}
|
||||
|
||||
if (-e "$datadir/versioncache") {
|
||||
print "Removing versioncache...\n";
|
||||
unlink "$datadir/versioncache";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# A simple helper for creating "empty" CSS files.
|
||||
sub _add_custom_css {
|
||||
my ($skin_dir, $path, $create_files, $perms) = @_;
|
||||
$create_files->{"$skin_dir/$path"} = { perms => $perms, contents => <<EOT
|
||||
/*
|
||||
* Custom rules for $path.
|
||||
* The rules you put here override rules in that stylesheet.
|
||||
*/
|
||||
EOT
|
||||
};
|
||||
}
|
||||
|
||||
sub create_htaccess {
|
||||
_create_files(%{FILESYSTEM()->{htaccess}});
|
||||
|
||||
# Repair old .htaccess files
|
||||
my $htaccess = new IO::File('.htaccess', 'r') || die ".htaccess: $!";
|
||||
my $old_data;
|
||||
{ local $/; $old_data = <$htaccess>; }
|
||||
$htaccess->close;
|
||||
|
||||
my $repaired = 0;
|
||||
if ($old_data =~ s/\|localconfig\|/\|.*localconfig.*\|/) {
|
||||
$repaired = 1;
|
||||
}
|
||||
if ($old_data !~ /\(\.\*\\\.pm\|/) {
|
||||
$old_data =~ s/\(/(.*\\.pm\|/;
|
||||
$repaired = 1;
|
||||
}
|
||||
if ($repaired) {
|
||||
print "Repairing .htaccess...\n";
|
||||
$htaccess = new IO::File('.htaccess', 'w') || die $!;
|
||||
print $htaccess $old_data;
|
||||
$htaccess->close;
|
||||
}
|
||||
|
||||
|
||||
my $webdot_dir = bz_locations()->{'webdotdir'};
|
||||
# The public webdot IP address changed.
|
||||
my $webdot = new IO::File("$webdot_dir/.htaccess", 'r')
|
||||
|| die "$webdot_dir/.htaccess: $!";
|
||||
my $webdot_data;
|
||||
{ local $/; $webdot_data = <$webdot>; }
|
||||
$webdot->close;
|
||||
if ($webdot_data =~ /192\.20\.225\.10/) {
|
||||
print "Repairing $webdot_dir/.htaccess...\n";
|
||||
$webdot_data =~ s/192\.20\.225\.10/192.20.225.0\/24/g;
|
||||
$webdot = new IO::File("$webdot_dir/.htaccess", 'w') || die $!;
|
||||
print $webdot $webdot_data;
|
||||
$webdot->close;
|
||||
}
|
||||
}
|
||||
|
||||
# A helper for the above functions.
|
||||
sub _create_files {
|
||||
my (%files) = @_;
|
||||
|
||||
# It's not necessary to sort these, but it does make the
|
||||
# output of checksetup.pl look a bit nicer.
|
||||
foreach my $file (sort keys %files) {
|
||||
unless (-e $file) {
|
||||
print "Creating $file...\n";
|
||||
my $info = $files{$file};
|
||||
my $fh = new IO::File($file, O_WRONLY | O_CREAT, $info->{perms})
|
||||
|| die $!;
|
||||
print $fh $info->{contents} if $info->{contents};
|
||||
$fh->close;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If you ran a REALLY old version of Bugzilla, your chart files are in the
|
||||
# wrong format. This code is a little messy, because it's very old, and
|
||||
# when moving it into this module, I couldn't test it so I left it almost
|
||||
# completely alone.
|
||||
sub _update_old_charts {
|
||||
my ($datadir) = @_;
|
||||
print "Updating old chart storage format...\n";
|
||||
foreach my $in_file (glob("$datadir/mining/*")) {
|
||||
# Don't try and upgrade image or db files!
|
||||
next if (($in_file =~ /\.gif$/i) ||
|
||||
($in_file =~ /\.png$/i) ||
|
||||
($in_file =~ /\.db$/i) ||
|
||||
($in_file =~ /\.orig$/i));
|
||||
|
||||
rename("$in_file", "$in_file.orig") or next;
|
||||
open(IN, "$in_file.orig") or next;
|
||||
open(OUT, '>', $in_file) or next;
|
||||
|
||||
# Fields in the header
|
||||
my @declared_fields;
|
||||
|
||||
# Fields we changed to half way through by mistake
|
||||
# This list comes from an old version of collectstats.pl
|
||||
# This part is only for people who ran later versions of 2.11 (devel)
|
||||
my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED
|
||||
RESOLVED VERIFIED CLOSED);
|
||||
|
||||
# Fields we actually want (matches the current collectstats.pl)
|
||||
my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
|
||||
VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND
|
||||
DUPLICATE WORKSFORME MOVED);
|
||||
|
||||
while (<IN>) {
|
||||
if (/^# fields?: (.*)\s$/) {
|
||||
@declared_fields = map uc, (split /\||\r/, $1);
|
||||
print OUT "# fields: ", join('|', @out_fields), "\n";
|
||||
}
|
||||
elsif (/^(\d+\|.*)/) {
|
||||
my @data = split(/\||\r/, $1);
|
||||
my %data;
|
||||
if (@data == @declared_fields) {
|
||||
# old format
|
||||
for my $i (0 .. $#declared_fields) {
|
||||
$data{$declared_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
elsif (@data == @intermediate_fields) {
|
||||
# Must have changed over at this point
|
||||
for my $i (0 .. $#intermediate_fields) {
|
||||
$data{$intermediate_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
elsif (@data == @out_fields) {
|
||||
# This line's fine - it has the right number of entries
|
||||
for my $i (0 .. $#out_fields) {
|
||||
$data{$out_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
print "Oh dear, input line $. of $in_file had " .
|
||||
scalar(@data) . " fields\nThis was unexpected.",
|
||||
" You may want to check your data files.\n";
|
||||
}
|
||||
|
||||
print OUT join('|',
|
||||
map { defined ($data{$_}) ? ($data{$_}) : "" } @out_fields),
|
||||
"\n";
|
||||
}
|
||||
else {
|
||||
print OUT;
|
||||
}
|
||||
}
|
||||
|
||||
close(IN);
|
||||
close(OUT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub fix_all_file_permissions {
|
||||
my ($output) = @_;
|
||||
|
||||
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
|
||||
my $group_id = _check_web_server_group($ws_group, $output);
|
||||
|
||||
return if ON_WINDOWS;
|
||||
|
||||
my $fs = FILESYSTEM();
|
||||
my %files = %{$fs->{all_files}};
|
||||
my %dirs = %{$fs->{all_dirs}};
|
||||
my %recurse_dirs = %{$fs->{recurse_dirs}};
|
||||
|
||||
print get_text('install_file_perms_fix') . "\n" if $output;
|
||||
|
||||
my $owner_id = POSIX::getuid();
|
||||
$group_id = POSIX::getgid() unless defined $group_id;
|
||||
|
||||
foreach my $dir (sort keys %dirs) {
|
||||
next unless -d $dir;
|
||||
_fix_perms($dir, $owner_id, $group_id, $dirs{$dir});
|
||||
}
|
||||
|
||||
foreach my $dir (sort keys %recurse_dirs) {
|
||||
next unless -d $dir;
|
||||
# Set permissions on the directory itself.
|
||||
my $perms = $recurse_dirs{$dir};
|
||||
_fix_perms($dir, $owner_id, $group_id, $perms->{dirs});
|
||||
# Now recurse through the directory and set the correct permissions
|
||||
# on subdirectories and files.
|
||||
find({ no_chdir => 1, wanted => sub {
|
||||
my $name = $File::Find::name;
|
||||
if (-d $name) {
|
||||
_fix_perms($name, $owner_id, $group_id, $perms->{dirs});
|
||||
}
|
||||
else {
|
||||
_fix_perms($name, $owner_id, $group_id, $perms->{files});
|
||||
}
|
||||
}}, $dir);
|
||||
}
|
||||
|
||||
foreach my $file (sort keys %files) {
|
||||
# %files supports globs
|
||||
foreach my $filename (glob $file) {
|
||||
# Don't touch directories.
|
||||
next if -d $filename || !-e $filename;
|
||||
_fix_perms($filename, $owner_id, $group_id,
|
||||
$files{$file}->{perms});
|
||||
}
|
||||
}
|
||||
|
||||
_fix_cvs_dirs($owner_id, '.');
|
||||
}
|
||||
|
||||
# A helper for fix_all_file_permissions
|
||||
sub _fix_cvs_dirs {
|
||||
my ($owner_id, $dir) = @_;
|
||||
my $owner_gid = POSIX::getgid();
|
||||
find({ no_chdir => 1, wanted => sub {
|
||||
my $name = $File::Find::name;
|
||||
if ($File::Find::dir =~ /\/CVS/ || $_ eq '.cvsignore'
|
||||
|| (-d $name && $_ eq 'CVS')) {
|
||||
_fix_perms($name, $owner_id, $owner_gid, 0700);
|
||||
}
|
||||
}}, $dir);
|
||||
}
|
||||
|
||||
sub _fix_perms {
|
||||
my ($name, $owner, $group, $perms) = @_;
|
||||
#printf ("Changing $name to %o\n", $perms);
|
||||
chown $owner, $group, $name
|
||||
|| warn "Failed to change ownership of $name: $!";
|
||||
chmod $perms, $name
|
||||
|| warn "Failed to change permissions of $name: $!";
|
||||
}
|
||||
|
||||
sub _check_web_server_group {
|
||||
my ($group, $output) = @_;
|
||||
|
||||
my $filename = bz_locations()->{'localconfig'};
|
||||
my $group_id;
|
||||
|
||||
# If we are on Windows, webservergroup does nothing
|
||||
if (ON_WINDOWS && $group && $output) {
|
||||
print "\n\n" . get_text('install_webservergroup_windows') . "\n\n";
|
||||
}
|
||||
|
||||
# If we're not on Windows, make sure that webservergroup isn't
|
||||
# empty.
|
||||
elsif (!ON_WINDOWS && !$group && $output) {
|
||||
print "\n\n" . get_text('install_webservergroup_empty') . "\n\n";
|
||||
}
|
||||
|
||||
# If we're not on Windows, make sure we are actually a member of
|
||||
# the webservergroup.
|
||||
elsif (!ON_WINDOWS && $group) {
|
||||
$group_id = getgrnam($group);
|
||||
ThrowCodeError('invalid_webservergroup', { group => $group })
|
||||
unless defined $group_id;
|
||||
|
||||
# If on unix, see if we need to print a warning about a webservergroup
|
||||
# that we can't chgrp to
|
||||
if ($output && $< != 0 && !grep($_ eq $group_id, split(" ", $)))) {
|
||||
print "\n\n" . get_text('install_webservergroup_not_in') . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $group_id;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Filesystem - Fix up the filesystem during
|
||||
installation.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> to modify the
|
||||
filesystem during installation, including creating the data/ directory.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<update_filesystem({ index_html => 0 })>
|
||||
|
||||
Description: Creates all the directories and files that Bugzilla
|
||||
needs to function but doesn't ship with. Also does
|
||||
any updates to these files as necessary during an
|
||||
upgrade.
|
||||
|
||||
Params: C<index_html> - Whether or not we should create
|
||||
the F<index.html> file.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<create_htaccess()>
|
||||
|
||||
Description: Creates all of the .htaccess files for Apache,
|
||||
in the various Bugzilla directories. Also updates
|
||||
the .htaccess files if they need updating.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<fix_all_file_permissions($output)>
|
||||
|
||||
Description: Sets all the file permissions on all of Bugzilla's files
|
||||
to what they should be. Note that permissions are different
|
||||
depending on whether or not C<$webservergroup> is set
|
||||
in F<localconfig>.
|
||||
|
||||
Params: C<$output> - C<true> if you want this function to print
|
||||
out information about what it's doing.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,440 +0,0 @@
|
||||
# -*- 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 Initial Developer of the Original Code is Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::Localconfig;
|
||||
|
||||
# NOTE: This package may "use" any modules that it likes. However,
|
||||
# all functions in this package should assume that:
|
||||
#
|
||||
# * The data/ directory does not exist.
|
||||
# * Templates are not available.
|
||||
# * Files do not have the correct permissions
|
||||
# * The database is not up to date
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(bin_loc);
|
||||
|
||||
use Data::Dumper;
|
||||
use File::Basename qw(dirname);
|
||||
use IO::File;
|
||||
use Safe;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(
|
||||
read_localconfig
|
||||
update_localconfig
|
||||
);
|
||||
|
||||
use constant LOCALCONFIG_VARS => (
|
||||
{
|
||||
name => 'create_htaccess',
|
||||
default => 1,
|
||||
desc => <<EOT
|
||||
# If you are using Apache as your web server, Bugzilla can create .htaccess
|
||||
# files for you that will instruct Apache not to serve files that shouldn't
|
||||
# be accessed from the web browser (like your local configuration data and non-cgi
|
||||
# executable files). For this to work, the directory your Bugzilla
|
||||
# installation is in must be within the jurisdiction of a <Directory> block
|
||||
# in the httpd.conf file that has 'AllowOverride Limit' in it. If it has
|
||||
# 'AllowOverride All' or other options with Limit, that's fine.
|
||||
# (Older Apache installations may use an access.conf file to store these
|
||||
# <Directory> blocks.)
|
||||
# If this is set to 1, Bugzilla will create these files if they don't exist.
|
||||
# If this is set to 0, Bugzilla will not create these files.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'webservergroup',
|
||||
default => ON_WINDOWS ? '' : 'apache',
|
||||
desc => q{# This is the group your web server runs as.
|
||||
# If you have a Windows box, ignore this setting.
|
||||
# If you do not have access to the group your web server runs under,
|
||||
# set this to "". If you do set this to "", then your Bugzilla installation
|
||||
# will be _VERY_ insecure, because some files will be world readable/writable,
|
||||
# and so anyone who can get local access to your machine can do whatever they
|
||||
# want. You should only have this set to "" if this is a testing installation
|
||||
# and you cannot set this up any other way. YOU HAVE BEEN WARNED!
|
||||
# If you set this to anything other than "", you will need to run checksetup.pl
|
||||
# as} . ROOT_USER . qq{, or as a user who is a member of the specified group.\n}
|
||||
},
|
||||
{
|
||||
name => 'db_driver',
|
||||
default => 'mysql',
|
||||
desc => <<EOT
|
||||
# What SQL database to use. Default is mysql. List of supported databases
|
||||
# can be obtained by listing Bugzilla/DB directory - every module corresponds
|
||||
# to one supported database and the name corresponds to a driver name.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_host',
|
||||
default => 'localhost',
|
||||
desc =>
|
||||
"# The DNS name of the host that the database server runs on.\n"
|
||||
},
|
||||
{
|
||||
name => 'db_name',
|
||||
default => 'bugs',
|
||||
desc => "# The name of the database\n"
|
||||
},
|
||||
{
|
||||
name => 'db_user',
|
||||
default => 'bugs',
|
||||
desc => "# Who we connect to the database as.\n"
|
||||
},
|
||||
{
|
||||
name => 'db_pass',
|
||||
default => '',
|
||||
desc => <<EOT
|
||||
# Enter your database password here. It's normally advisable to specify
|
||||
# a password for your bugzilla database user.
|
||||
# If you use apostrophe (') or a backslash (\\) in your password, you'll
|
||||
# need to escape it by preceding it with a '\\' character. (\\') or (\\)
|
||||
# (Far simpler just not to use those characters.)
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_port',
|
||||
default => 0,
|
||||
desc => <<EOT
|
||||
# Sometimes the database server is running on a non-standard port. If that's
|
||||
# the case for your database server, set this to the port number that your
|
||||
# database server is running on. Setting this to 0 means "use the default
|
||||
# port for my database server."
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_sock',
|
||||
default => '',
|
||||
desc => <<EOT
|
||||
# MySQL Only: Enter a path to the unix socket for MySQL. If this is
|
||||
# blank, then MySQL's compiled-in default will be used. You probably
|
||||
# want that.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_check',
|
||||
default => 1,
|
||||
desc => <<EOT
|
||||
# Should checksetup.pl try to verify that your database setup is correct?
|
||||
# (with some combinations of database servers/Perl modules/moonphase this
|
||||
# doesn't work)
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'index_html',
|
||||
default => 0,
|
||||
desc => <<EOT
|
||||
# With the introduction of a configurable index page using the
|
||||
# template toolkit, Bugzilla's main index page is now index.cgi.
|
||||
# Most web servers will allow you to use index.cgi as a directory
|
||||
# index, and many come preconfigured that way, but if yours doesn't
|
||||
# then you'll need an index.html file that provides redirection
|
||||
# to index.cgi. Setting \$index_html to 1 below will allow
|
||||
# checksetup.pl to create one for you if it doesn't exist.
|
||||
# NOTE: checksetup.pl will not replace an existing file, so if you
|
||||
# wish to have checksetup.pl create one for you, you must
|
||||
# make sure that index.html doesn't already exist
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'cvsbin',
|
||||
default => \&_get_default_cvsbin,
|
||||
desc => <<EOT
|
||||
# For some optional functions of Bugzilla (such as the pretty-print patch
|
||||
# viewer), we need the cvs binary to access files and revisions.
|
||||
# Because it's possible that this program is not in your path, you can specify
|
||||
# its location here. Please specify the full path to the executable.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'interdiffbin',
|
||||
default => \&_get_default_interdiffbin,
|
||||
desc => <<EOT
|
||||
# For some optional functions of Bugzilla (such as the pretty-print patch
|
||||
# viewer), we need the interdiff binary to make diffs between two patches.
|
||||
# Because it's possible that this program is not in your path, you can specify
|
||||
# its location here. Please specify the full path to the executable.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'diffpath',
|
||||
default => \&_get_default_diffpath,
|
||||
desc => <<EOT
|
||||
# The interdiff feature needs diff, so we have to have that path.
|
||||
# Please specify the directory name only; do not use trailing slash.
|
||||
EOT
|
||||
},
|
||||
);
|
||||
|
||||
use constant OLD_LOCALCONFIG_VARS => qw(
|
||||
mysqlpath
|
||||
contenttypes
|
||||
pages
|
||||
severities platforms opsys priorities
|
||||
);
|
||||
|
||||
sub read_localconfig {
|
||||
my ($include_deprecated) = @_;
|
||||
my $filename = bz_locations()->{'localconfig'};
|
||||
|
||||
my %localconfig;
|
||||
if (-e $filename) {
|
||||
my $s = new Safe;
|
||||
# Some people like to store their database password in another file.
|
||||
$s->permit('dofile');
|
||||
|
||||
$s->rdo($filename);
|
||||
if ($@ || $!) {
|
||||
my $err_msg = $@ ? $@ : $!;
|
||||
die <<EOT;
|
||||
An error has occurred while reading your 'localconfig' file. The text of
|
||||
the error message is:
|
||||
|
||||
$err_msg
|
||||
|
||||
Please fix the error in your 'localconfig' file. Alternately, rename your
|
||||
'localconfig' file, rerun checksetup.pl, and re-enter your answers.
|
||||
|
||||
\$ mv -f localconfig localconfig.old
|
||||
\$ ./checksetup.pl
|
||||
EOT
|
||||
}
|
||||
|
||||
my @vars = map($_->{name}, LOCALCONFIG_VARS);
|
||||
push(@vars, OLD_LOCALCONFIG_VARS) if $include_deprecated;
|
||||
foreach my $var (@vars) {
|
||||
my $glob = $s->varglob($var);
|
||||
# We can't get the type of a variable out of a Safe automatically.
|
||||
# We can only get the glob itself. So we figure out its type this
|
||||
# way, by trying first a scalar, then an array, then a hash.
|
||||
#
|
||||
# The interesting thing is that this converts all deprecated
|
||||
# array or hash vars into hashrefs or arrayrefs, but that's
|
||||
# fine since as I write this all modern localconfig vars are
|
||||
# actually scalars.
|
||||
if (defined $$glob) {
|
||||
$localconfig{$var} = $$glob;
|
||||
}
|
||||
elsif (defined @$glob) {
|
||||
$localconfig{$var} = \@$glob;
|
||||
}
|
||||
elsif (defined %$glob) {
|
||||
$localconfig{$var} = \%$glob;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return \%localconfig;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# This is quite tricky. But fun!
|
||||
#
|
||||
# First we read the file 'localconfig'. Then we check if the variables we
|
||||
# need are defined. If not, we will append the new settings to
|
||||
# localconfig, instruct the user to check them, and stop.
|
||||
#
|
||||
# Why do it this way?
|
||||
#
|
||||
# Assume we will enhance Bugzilla and eventually more local configuration
|
||||
# stuff arises on the horizon.
|
||||
#
|
||||
# But the file 'localconfig' is not in the Bugzilla CVS or tarfile. You
|
||||
# know, we never want to overwrite your own version of 'localconfig', so
|
||||
# we can't put it into the CVS/tarfile, can we?
|
||||
#
|
||||
# Now, when we need a new variable, we simply add the necessary stuff to
|
||||
# LOCALCONFIG_VARS. When the user gets the new version of Bugzilla from CVS and
|
||||
# runs checksetup, it finds out "Oh, there is something new". Then it adds
|
||||
# some default value to the user's local setup and informs the user to
|
||||
# check that to see if it is what the user wants.
|
||||
#
|
||||
# Cute, ey?
|
||||
#
|
||||
sub update_localconfig {
|
||||
my ($params) = @_;
|
||||
|
||||
my $output = $params->{output} || 0;
|
||||
my $answer = Bugzilla->installation_answers;
|
||||
my $localconfig = read_localconfig('include deprecated');
|
||||
|
||||
my @new_vars;
|
||||
foreach my $var (LOCALCONFIG_VARS) {
|
||||
my $name = $var->{name};
|
||||
if (!defined $localconfig->{$name}) {
|
||||
push(@new_vars, $name);
|
||||
$var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
|
||||
$localconfig->{$name} = $answer->{$name} || $var->{default};
|
||||
}
|
||||
}
|
||||
|
||||
my @old_vars;
|
||||
foreach my $name (OLD_LOCALCONFIG_VARS) {
|
||||
push(@old_vars, $name) if defined $localconfig->{$name};
|
||||
}
|
||||
|
||||
if (!$localconfig->{'interdiffbin'} && $output) {
|
||||
print <<EOT
|
||||
|
||||
OPTIONAL NOTE: If you want to be able to use the 'difference between two
|
||||
patches' feature of Bugzilla (which requires the PatchReader Perl module
|
||||
as well), you should install patchutils from:
|
||||
|
||||
http://cyberelk.net/tim/patchutils/
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
my $filename = bz_locations->{'localconfig'};
|
||||
|
||||
if (scalar @old_vars) {
|
||||
my $oldstuff = join(', ', @old_vars);
|
||||
print <<EOT
|
||||
|
||||
The following variables are no longer used in $filename, and
|
||||
should be removed: $oldstuff
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
if (scalar @new_vars) {
|
||||
my $filename = bz_locations->{'localconfig'};
|
||||
my $fh = new IO::File($filename, '>>') || die "$filename: $!";
|
||||
$fh->seek(0, SEEK_END);
|
||||
foreach my $var (LOCALCONFIG_VARS) {
|
||||
if (grep($_ eq $var->{name}, @new_vars)) {
|
||||
print $fh "\n", $var->{desc},
|
||||
Data::Dumper->Dump([$localconfig->{$var->{name}}],
|
||||
["*$var->{name}"]);
|
||||
}
|
||||
}
|
||||
|
||||
my $newstuff = join(', ', @new_vars);
|
||||
print <<EOT;
|
||||
|
||||
This version of Bugzilla contains some variables that you may want to
|
||||
change and adapt to your local settings. Please edit the file
|
||||
$filename and rerun checksetup.pl.
|
||||
|
||||
The following variables are new to $filename since you last ran
|
||||
checksetup.pl: $newstuff
|
||||
|
||||
EOT
|
||||
exit;
|
||||
}
|
||||
|
||||
# Reset the cache for Bugzilla->localconfig so that it will be re-read
|
||||
delete Bugzilla->request_cache->{localconfig};
|
||||
|
||||
return { old_vars => \@old_vars, new_vars => \@new_vars };
|
||||
}
|
||||
|
||||
sub _get_default_cvsbin { return bin_loc('cvs') }
|
||||
sub _get_default_interdiffbin { return bin_loc('interdiff') }
|
||||
sub _get_default_diffpath {
|
||||
my $diff_bin = bin_loc('diff');
|
||||
return dirname($diff_bin);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Localconfig - Functions and variables dealing
|
||||
with the manipulation and creation of the F<localconfig> file.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install::Requirements qw(update_localconfig);
|
||||
update_localconfig({ output => 1 });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> to create and
|
||||
modify the localconfig file. Most scripts should use L<Bugzilla/localconfig>
|
||||
to access localconfig variables.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<LOCALCONFIG_VARS>
|
||||
|
||||
An array of hashrefs. These hashrefs contain three keys:
|
||||
|
||||
name - The name of the variable.
|
||||
default - The default value for the variable. Should always be
|
||||
something that can fit in a scalar.
|
||||
desc - Additional text to put in localconfig before the variable
|
||||
definition. Must end in a newline. Each line should start
|
||||
with "#" unless you have some REALLY good reason not
|
||||
to do that.
|
||||
|
||||
=item C<OLD_LOCALCONFIG_VARS>
|
||||
|
||||
An array of names of variables. If C<update_localconfig> finds these
|
||||
variables defined in localconfig, it will print out a warning.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<read_localconfig($include_deprecated)>
|
||||
|
||||
Description: Reads the localconfig file and returns all valid
|
||||
values in a hashref.
|
||||
|
||||
Params: C<$include_deprecated> - C<true> if you want the returned
|
||||
hashref to also include variables listed in
|
||||
C<OLD_LOCALCONFIG_VARS>, if they exist. Generally
|
||||
this is only for use by C<update_localconfig>.
|
||||
|
||||
Returns: A hashref of the localconfig variables. If an array
|
||||
is defined, it will be an arrayref in the returned hash. If a
|
||||
hash is defined, it will be a hashref in the returned hash.
|
||||
Only includes variables specified in C<LOCALCONFIG_VARS>
|
||||
(and C<OLD_LOCALCONFIG_VARS> if C<$include_deprecated> is
|
||||
specified).
|
||||
|
||||
=item C<update_localconfig({ output =E<gt> 1 })>
|
||||
|
||||
Description: Adds any new variables to localconfig that aren't
|
||||
currently defined there. Also optionally prints out
|
||||
a message about vars that *should* be there and aren't.
|
||||
Exits the program if it adds any new vars.
|
||||
|
||||
Params: C<output> - C<true> if the function should display informational
|
||||
output and warnings. It will always display errors or
|
||||
any message which would cause program execution to halt.
|
||||
|
||||
Returns: A hashref, with C<old_vals> being an array of names of variables
|
||||
that were removed, and C<new_vals> being an array of names
|
||||
of variables that were added to localconfig.
|
||||
|
||||
=back
|
||||
@@ -1,695 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Install::Requirements;
|
||||
|
||||
# NOTE: This package MUST NOT "use" any Bugzilla modules other than
|
||||
# Bugzilla::Constants, anywhere. We may "use" standard perl modules.
|
||||
#
|
||||
# Subroutines may "require" and "import" from modules, but they
|
||||
# MUST NOT "use."
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Install::Util qw(vers_cmp install_string);
|
||||
use List::Util qw(max);
|
||||
use Safe;
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(
|
||||
REQUIRED_MODULES
|
||||
OPTIONAL_MODULES
|
||||
|
||||
check_requirements
|
||||
check_graphviz
|
||||
have_vers
|
||||
install_command
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
# The below two constants are subroutines so that they can implement
|
||||
# a hook. Other than that they are actually constants.
|
||||
|
||||
# "package" is the perl package we're checking for. "module" is the name
|
||||
# of the actual module we load with "require" to see if the package is
|
||||
# installed or not. "version" is the version we need, or 0 if we'll accept
|
||||
# any version.
|
||||
#
|
||||
# "blacklist" is an arrayref of regular expressions that describe versions that
|
||||
# are 'blacklisted'--that is, even if the version is high enough, Bugzilla
|
||||
# will refuse to say that it's OK to run with that version.
|
||||
sub REQUIRED_MODULES {
|
||||
my $perl_ver = sprintf('%vd', $^V);
|
||||
my @modules = (
|
||||
{
|
||||
package => 'CGI.pm',
|
||||
module => 'CGI',
|
||||
# Perl 5.10 requires CGI 3.33 due to a taint issue when
|
||||
# uploading attachments, see bug 416382.
|
||||
# Require CGI 3.21 for -httponly support, see bug 368502.
|
||||
version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21'
|
||||
},
|
||||
{
|
||||
package => 'TimeDate',
|
||||
module => 'Date::Format',
|
||||
version => '2.21'
|
||||
},
|
||||
{
|
||||
package => 'PathTools',
|
||||
module => 'File::Spec',
|
||||
version => '0.84'
|
||||
},
|
||||
{
|
||||
package => 'DBI',
|
||||
module => 'DBI',
|
||||
version => '1.41'
|
||||
},
|
||||
{
|
||||
package => 'Template-Toolkit',
|
||||
module => 'Template',
|
||||
version => '2.15'
|
||||
},
|
||||
{
|
||||
package => 'Email-Send',
|
||||
module => 'Email::Send',
|
||||
version => ON_WINDOWS ? '2.16' : '2.00'
|
||||
},
|
||||
{
|
||||
package => 'Email-MIME',
|
||||
module => 'Email::MIME',
|
||||
version => '1.861'
|
||||
},
|
||||
{
|
||||
package => 'Email-MIME-Modifier',
|
||||
module => 'Email::MIME::Modifier',
|
||||
version => '1.442'
|
||||
},
|
||||
);
|
||||
|
||||
my $all_modules = _get_extension_requirements(
|
||||
'REQUIRED_MODULES', \@modules);
|
||||
return $all_modules;
|
||||
};
|
||||
|
||||
sub OPTIONAL_MODULES {
|
||||
my @modules = (
|
||||
{
|
||||
package => 'GD',
|
||||
module => 'GD',
|
||||
version => '1.20',
|
||||
feature => 'Graphical Reports, New Charts, Old Charts'
|
||||
},
|
||||
{
|
||||
package => 'Chart',
|
||||
module => 'Chart::Base',
|
||||
version => '1.0',
|
||||
feature => 'New Charts, Old Charts'
|
||||
},
|
||||
{
|
||||
package => 'Template-GD',
|
||||
# This module tells us whether or not Template-GD is installed
|
||||
# on Template-Toolkits after 2.14, and still works with 2.14 and lower.
|
||||
module => 'Template::Plugin::GD::Image',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'GDTextUtil',
|
||||
module => 'GD::Text',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'GDGraph',
|
||||
module => 'GD::Graph',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'XML-Twig',
|
||||
module => 'XML::Twig',
|
||||
version => 0,
|
||||
feature => 'Move Bugs Between Installations'
|
||||
},
|
||||
{
|
||||
package => 'MIME-tools',
|
||||
# MIME::Parser is packaged as MIME::Tools on ActiveState Perl
|
||||
module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
|
||||
version => '5.406',
|
||||
feature => 'Move Bugs Between Installations'
|
||||
},
|
||||
{
|
||||
package => 'libwww-perl',
|
||||
module => 'LWP::UserAgent',
|
||||
version => 0,
|
||||
feature => 'Automatic Update Notifications'
|
||||
},
|
||||
{
|
||||
package => 'PatchReader',
|
||||
module => 'PatchReader',
|
||||
version => '0.9.4',
|
||||
feature => 'Patch Viewer'
|
||||
},
|
||||
{
|
||||
package => 'PerlMagick',
|
||||
module => 'Image::Magick',
|
||||
version => 0,
|
||||
feature => 'Optionally Convert BMP Attachments to PNGs'
|
||||
},
|
||||
{
|
||||
package => 'perl-ldap',
|
||||
module => 'Net::LDAP',
|
||||
version => 0,
|
||||
feature => 'LDAP Authentication'
|
||||
},
|
||||
{
|
||||
package => 'Authen-SASL',
|
||||
module => 'Authen::SASL',
|
||||
version => 0,
|
||||
feature => 'SMTP Authentication'
|
||||
},
|
||||
{
|
||||
package => 'RadiusPerl',
|
||||
module => 'Authen::Radius',
|
||||
version => 0,
|
||||
feature => 'RADIUS Authentication'
|
||||
},
|
||||
{
|
||||
package => 'SOAP-Lite',
|
||||
module => 'SOAP::Lite',
|
||||
version => 0,
|
||||
feature => 'XML-RPC Interface'
|
||||
},
|
||||
{
|
||||
# We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
|
||||
package => 'HTML-Parser',
|
||||
module => 'HTML::Parser',
|
||||
version => '3.40',
|
||||
feature => 'More HTML in Product/Group Descriptions'
|
||||
},
|
||||
{
|
||||
package => 'HTML-Scrubber',
|
||||
module => 'HTML::Scrubber',
|
||||
version => 0,
|
||||
feature => 'More HTML in Product/Group Descriptions'
|
||||
},
|
||||
|
||||
# Inbound Email
|
||||
{
|
||||
package => 'Email-MIME-Attachment-Stripper',
|
||||
module => 'Email::MIME::Attachment::Stripper',
|
||||
version => 0,
|
||||
feature => 'Inbound Email'
|
||||
},
|
||||
{
|
||||
package => 'Email-Reply',
|
||||
module => 'Email::Reply',
|
||||
version => 0,
|
||||
feature => 'Inbound Email'
|
||||
},
|
||||
|
||||
# mod_perl
|
||||
{
|
||||
package => 'mod_perl',
|
||||
module => 'mod_perl2',
|
||||
version => '1.999022',
|
||||
feature => 'mod_perl'
|
||||
},
|
||||
);
|
||||
|
||||
# Even very new releases of perl (5.8.5) don't come with this version,
|
||||
# so I didn't want to make it a general requirement just for
|
||||
# running under mod_cgi.
|
||||
# If Perl 5.10 is installed, then CGI 3.33 is already required. So this
|
||||
# check is only relevant with Perl 5.8.x.
|
||||
my $perl_ver = sprintf('%vd', $^V);
|
||||
if (vers_cmp($perl_ver, '5.10') < 0) {
|
||||
push(@modules, { package => 'CGI.pm',
|
||||
module => 'CGI',
|
||||
version => '3.11',
|
||||
feature => 'mod_perl' });
|
||||
}
|
||||
|
||||
my $all_modules = _get_extension_requirements(
|
||||
'OPTIONAL_MODULES', \@modules);
|
||||
return $all_modules;
|
||||
};
|
||||
|
||||
# This implements the install-requirements hook described in Bugzilla::Hook.
|
||||
sub _get_extension_requirements {
|
||||
my ($function, $base_modules) = @_;
|
||||
my @all_modules;
|
||||
# get a list of all extensions
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
foreach my $extension (@extensions) {
|
||||
my $file = "$extension/code/install-requirements.pl";
|
||||
if (-e $file) {
|
||||
my $safe = new Safe;
|
||||
# This is a very liberal Safe.
|
||||
$safe->permit(qw(:browse require entereval caller));
|
||||
$safe->rdo($file);
|
||||
if ($@) {
|
||||
warn $@;
|
||||
next;
|
||||
}
|
||||
my $modules = eval { &{$safe->varglob($function)}($base_modules) };
|
||||
next unless $modules;
|
||||
push(@all_modules, @$modules);
|
||||
}
|
||||
}
|
||||
|
||||
unshift(@all_modules, @$base_modules);
|
||||
return \@all_modules;
|
||||
};
|
||||
|
||||
sub check_requirements {
|
||||
my ($output) = @_;
|
||||
|
||||
print "\n", install_string('checking_modules'), "\n" if $output;
|
||||
my $root = ROOT_USER;
|
||||
my $missing = _check_missing(REQUIRED_MODULES, $output);
|
||||
|
||||
print "\n", install_string('checking_dbd'), "\n" if $output;
|
||||
my $have_one_dbd = 0;
|
||||
my $db_modules = DB_MODULE;
|
||||
foreach my $db (keys %$db_modules) {
|
||||
my $dbd = $db_modules->{$db}->{dbd};
|
||||
$have_one_dbd = 1 if have_vers($dbd, $output);
|
||||
}
|
||||
|
||||
print "\n", install_string('checking_optional'), "\n" if $output;
|
||||
my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
|
||||
|
||||
# If we're running on Windows, reset the input line terminator so that
|
||||
# console input works properly - loading CGI tends to mess it up
|
||||
$/ = "\015\012" if ON_WINDOWS;
|
||||
|
||||
my $pass = !scalar(@$missing) && $have_one_dbd;
|
||||
return {
|
||||
pass => $pass,
|
||||
one_dbd => $have_one_dbd,
|
||||
missing => $missing,
|
||||
optional => $missing_optional,
|
||||
any_missing => !$pass || scalar(@$missing_optional),
|
||||
};
|
||||
}
|
||||
|
||||
# A helper for check_requirements
|
||||
sub _check_missing {
|
||||
my ($modules, $output) = @_;
|
||||
|
||||
my @missing;
|
||||
foreach my $module (@$modules) {
|
||||
unless (have_vers($module, $output)) {
|
||||
push(@missing, $module);
|
||||
}
|
||||
}
|
||||
|
||||
return \@missing;
|
||||
}
|
||||
|
||||
# Returns the build ID of ActivePerl. If several versions of
|
||||
# ActivePerl are installed, it won't be able to know which one
|
||||
# you are currently running. But that's our best guess.
|
||||
sub _get_activestate_build_id {
|
||||
eval 'use Win32::TieRegistry';
|
||||
return 0 if $@;
|
||||
my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
|
||||
or return 0;
|
||||
return $key->GetValue("CurrentVersion");
|
||||
}
|
||||
|
||||
sub print_module_instructions {
|
||||
my ($check_results, $output) = @_;
|
||||
|
||||
# We only print these notes if we have to.
|
||||
if ((!$output && @{$check_results->{missing}})
|
||||
|| ($output && $check_results->{any_missing}))
|
||||
{
|
||||
|
||||
if (ON_WINDOWS) {
|
||||
|
||||
print "\n* NOTE: You must run any commands listed below as "
|
||||
. ROOT_USER . ".\n\n";
|
||||
|
||||
my $perl_ver = sprintf('%vd', $^V);
|
||||
|
||||
# URL when running Perl 5.8.x.
|
||||
my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
|
||||
my $repo_up_cmd =
|
||||
'* *';
|
||||
# Packages for Perl 5.10 are not compatible with Perl 5.8.
|
||||
if (vers_cmp($perl_ver, '5.10') > -1) {
|
||||
$url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
|
||||
}
|
||||
# ActivePerl older than revision 819 require an additional command.
|
||||
if (_get_activestate_build_id() < 819) {
|
||||
$repo_up_cmd = <<EOT;
|
||||
* *
|
||||
* Then you have to do (also as an Administrator): *
|
||||
* *
|
||||
* ppm repo up theory58S *
|
||||
* *
|
||||
* Do that last command over and over until you see "theory58S" at the *
|
||||
* top of the displayed list. *
|
||||
EOT
|
||||
}
|
||||
print <<EOT;
|
||||
***********************************************************************
|
||||
* Note For Windows Users *
|
||||
***********************************************************************
|
||||
* In order to install the modules listed below, you first have to run *
|
||||
* the following command as an Administrator: *
|
||||
* *
|
||||
* ppm repo add theory58S $url_to_theory58S
|
||||
$repo_up_cmd
|
||||
***********************************************************************
|
||||
EOT
|
||||
}
|
||||
}
|
||||
|
||||
# Required Modules
|
||||
if (my @missing = @{$check_results->{missing}}) {
|
||||
print <<EOT;
|
||||
***********************************************************************
|
||||
* REQUIRED MODULES *
|
||||
***********************************************************************
|
||||
* Bugzilla requires you to install some Perl modules which are either *
|
||||
* missing from your system, or the version on your system is too old. *
|
||||
* *
|
||||
* The latest versions of each module can be installed by running the *
|
||||
* commands below. *
|
||||
***********************************************************************
|
||||
EOT
|
||||
|
||||
print "COMMANDS:\n\n";
|
||||
foreach my $package (@missing) {
|
||||
my $command = install_command($package);
|
||||
print " $command\n";
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
if (!$check_results->{one_dbd}) {
|
||||
print <<EOT;
|
||||
***********************************************************************
|
||||
* DATABASE ACCESS *
|
||||
***********************************************************************
|
||||
* In order to access your database, Bugzilla requires that the *
|
||||
* correct "DBD" module be installed for the database that you are *
|
||||
* running. *
|
||||
* *
|
||||
* Pick and run the correct command below for the database that you *
|
||||
* plan to use with Bugzilla. *
|
||||
***********************************************************************
|
||||
COMMANDS:
|
||||
|
||||
EOT
|
||||
|
||||
my %db_modules = %{DB_MODULE()};
|
||||
foreach my $db (keys %db_modules) {
|
||||
my $command = install_command($db_modules{$db}->{dbd});
|
||||
printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
|
||||
print ' ' x 12 . "Minimum version required: "
|
||||
. $db_modules{$db}->{dbd}->{version} . "\n";
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
return unless $output;
|
||||
|
||||
if (my @missing = @{$check_results->{optional}}) {
|
||||
print <<EOT;
|
||||
**********************************************************************
|
||||
* OPTIONAL MODULES *
|
||||
**********************************************************************
|
||||
* Certain Perl modules are not required by Bugzilla, but by *
|
||||
* installing the latest version you gain access to additional *
|
||||
* features. *
|
||||
* *
|
||||
* The optional modules you do not have installed are listed below, *
|
||||
* with the name of the feature they enable. If you want to install *
|
||||
* one of these modules, just run the appropriate command in the *
|
||||
* "COMMANDS TO INSTALL" section. *
|
||||
**********************************************************************
|
||||
|
||||
EOT
|
||||
# Now we have to determine how large the table cols will be.
|
||||
my $longest_name = max(map(length($_->{package}), @missing));
|
||||
|
||||
# The first column header is at least 11 characters long.
|
||||
$longest_name = 11 if $longest_name < 11;
|
||||
|
||||
# The table is 71 characters long. There are seven mandatory
|
||||
# characters (* and space) in the string. So, we have a total
|
||||
# of 64 characters to work with.
|
||||
my $remaining_space = 64 - $longest_name;
|
||||
print '*' x 71 . "\n";
|
||||
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
|
||||
'MODULE NAME', 'ENABLES FEATURE(S)';
|
||||
print '*' x 71 . "\n";
|
||||
foreach my $package (@missing) {
|
||||
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
|
||||
$package->{package}, $package->{feature};
|
||||
}
|
||||
print '*' x 71 . "\n";
|
||||
|
||||
print "COMMANDS TO INSTALL:\n\n";
|
||||
foreach my $module (@missing) {
|
||||
my $command = install_command($module);
|
||||
printf "%15s: $command\n", $module->{package};
|
||||
}
|
||||
}
|
||||
|
||||
if ($output && $check_results->{any_missing} && !ON_WINDOWS) {
|
||||
print install_string('install_all', { perl => $^X });
|
||||
}
|
||||
}
|
||||
|
||||
sub check_graphviz {
|
||||
my ($output) = @_;
|
||||
|
||||
return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);
|
||||
|
||||
printf("Checking for %15s %-9s ", "GraphViz", "(any)") if $output;
|
||||
|
||||
my $return = 0;
|
||||
if(-x Bugzilla->params->{'webdotbase'}) {
|
||||
print "ok: found\n" if $output;
|
||||
$return = 1;
|
||||
} else {
|
||||
print "not a valid executable: " . Bugzilla->params->{'webdotbase'} . "\n";
|
||||
}
|
||||
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
# Check .htaccess allows access to generated images
|
||||
if (-e "$webdotdir/.htaccess") {
|
||||
my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
|
||||
|| die "$webdotdir/.htaccess: " . $!;
|
||||
if (!grep(/png/, $htaccess->getlines)) {
|
||||
print "Dependency graph images are not accessible.\n";
|
||||
print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
|
||||
}
|
||||
$htaccess->close;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
# This was originally clipped from the libnet Makefile.PL, adapted here to
|
||||
# use the below vers_cmp routine for accurate version checking.
|
||||
sub have_vers {
|
||||
my ($params, $output) = @_;
|
||||
my $module = $params->{module};
|
||||
my $package = $params->{package};
|
||||
if (!$package) {
|
||||
$package = $module;
|
||||
$package =~ s/::/-/g;
|
||||
}
|
||||
my $wanted = $params->{version};
|
||||
|
||||
eval "require $module;";
|
||||
|
||||
# VERSION is provided by UNIVERSAL::
|
||||
my $vnum = eval { $module->VERSION } || -1;
|
||||
|
||||
# CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
|
||||
# That breaks the standard version tests, so we need to manually correct
|
||||
# the version
|
||||
if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
|
||||
$vnum = $1 . "." . $2;
|
||||
}
|
||||
|
||||
my $vstr;
|
||||
if ($vnum eq "-1") { # string compare just in case it's non-numeric
|
||||
$vstr = install_string('module_not_found');
|
||||
}
|
||||
elsif (vers_cmp($vnum,"0") > -1) {
|
||||
$vstr = install_string('module_found', { ver => $vnum });
|
||||
}
|
||||
else {
|
||||
$vstr = install_string('module_unknown_version');
|
||||
}
|
||||
|
||||
my $vok = (vers_cmp($vnum,$wanted) > -1);
|
||||
my $blacklisted;
|
||||
if ($vok && $params->{blacklist}) {
|
||||
$blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
|
||||
$vok = 0 if $blacklisted;
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
my $ok = $vok ? install_string('module_ok') : '';
|
||||
my $black_string = $blacklisted ? install_string('blacklisted') : '';
|
||||
my $want_string = $wanted ? "v$wanted" : install_string('any');
|
||||
|
||||
$ok = "$ok:" if $ok;
|
||||
printf "%s %19s %-9s $ok $vstr $black_string\n",
|
||||
install_string('checking_for'), $package, "($want_string)";
|
||||
}
|
||||
|
||||
return $vok ? 1 : 0;
|
||||
}
|
||||
|
||||
sub install_command {
|
||||
my $module = shift;
|
||||
my ($command, $package);
|
||||
|
||||
if (ON_WINDOWS) {
|
||||
$command = 'ppm install %s';
|
||||
$package = $module->{package};
|
||||
}
|
||||
else {
|
||||
$command = "$^X install-module.pl \%s";
|
||||
# Non-Windows installations need to use module names, because
|
||||
# CPAN doesn't understand package names.
|
||||
$package = $module->{module};
|
||||
}
|
||||
return sprintf $command, $package;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Requirements - Functions and variables dealing
|
||||
with Bugzilla's perl-module requirements.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by C<checksetup.pl> to determine whether
|
||||
or not all of Bugzilla's prerequisites are installed. (That is, all the
|
||||
perl modules it requires.)
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<REQUIRED_MODULES>
|
||||
|
||||
An arrayref of hashrefs that describes the perl modules required by
|
||||
Bugzilla. The hashes have two keys, C<name> and C<version>, which
|
||||
represent the name of the module and the version that we require.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<check_requirements>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This checks what optional or required perl modules are installed, like
|
||||
C<checksetup.pl> does.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$output> - C<true> if you want the function to print out information
|
||||
about what it's doing, and the versions of everything installed.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hashref containing these values:
|
||||
|
||||
=over
|
||||
|
||||
=item C<pass> - Whether or not we have all the mandatory requirements.
|
||||
|
||||
=item C<missing> - An arrayref containing any required modules that
|
||||
are not installed or that are not up-to-date. Each item in the array is
|
||||
a hashref in the format of items from L</REQUIRED_MODULES>.
|
||||
|
||||
=item C<optional> - The same as C<missing>, but for optional modules.
|
||||
|
||||
=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
|
||||
|
||||
=item C<any_missing> - True if there are any missing modules, even optional
|
||||
modules.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<check_graphviz($output)>
|
||||
|
||||
Description: Checks if the graphviz binary specified in the
|
||||
C<webdotbase> parameter is a valid binary, or a valid URL.
|
||||
|
||||
Params: C<$output> - C<$true> if you want the function to
|
||||
print out information about what it's doing.
|
||||
|
||||
Returns: C<1> if the check was successful, C<0> otherwise.
|
||||
|
||||
=item C<have_vers($module, $output)>
|
||||
|
||||
Description: Tells you whether or not you have the appropriate
|
||||
version of the module requested. It also prints
|
||||
out a message to the user explaining the check
|
||||
and the result.
|
||||
|
||||
Params: C<$module> - A hashref, in the format of an item from
|
||||
L</REQUIRED_MODULES>.
|
||||
C<$output> - Set to true if you want this function to
|
||||
print information to STDOUT about what it's
|
||||
doing.
|
||||
|
||||
Returns: C<1> if you have the module installed and you have the
|
||||
appropriate version. C<0> otherwise.
|
||||
|
||||
=item C<install_command($module)>
|
||||
|
||||
Description: Prints out the appropriate command to install the
|
||||
module specified, depending on whether you're
|
||||
on Windows or Linux.
|
||||
|
||||
Params: C<$module> - A hashref, in the format of an item from
|
||||
L</REQUIRED_MODULES>.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,553 +0,0 @@
|
||||
# -*- 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 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 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 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) {
|
||||
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'};
|
||||
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));
|
||||
}
|
||||
|
||||
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]));
|
||||
}
|
||||
|
||||
sub is_tainted {
|
||||
return not eval { my $foo = join('',@_), kill 0; 1; };
|
||||
}
|
||||
|
||||
__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
|
||||
@@ -1,189 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Keyword;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
keyworddefs.id
|
||||
keyworddefs.name
|
||||
keyworddefs.description
|
||||
);
|
||||
|
||||
use constant DB_TABLE => 'keyworddefs';
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
description => \&_check_description,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
description
|
||||
);
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
|
||||
sub bug_count {
|
||||
my ($self) = @_;
|
||||
return $self->{'bug_count'} if defined $self->{'bug_count'};
|
||||
($self->{'bug_count'}) =
|
||||
Bugzilla->dbh->selectrow_array(
|
||||
'SELECT COUNT(*) FROM keywords WHERE keywordid = ?',
|
||||
undef, $self->id);
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Mutators #####
|
||||
###############################
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub keyword_count {
|
||||
my ($count) =
|
||||
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM keyworddefs');
|
||||
return $count;
|
||||
}
|
||||
|
||||
sub get_all_with_bug_count {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $keywords =
|
||||
$dbh->selectall_arrayref('SELECT ' . join(', ', DB_COLUMNS) . ',
|
||||
COUNT(keywords.bug_id) AS bug_count
|
||||
FROM keyworddefs
|
||||
LEFT JOIN keywords
|
||||
ON keyworddefs.id = keywords.keywordid ' .
|
||||
$dbh->sql_group_by('keyworddefs.id',
|
||||
'keyworddefs.name,
|
||||
keyworddefs.description') . '
|
||||
ORDER BY keyworddefs.name', {'Slice' => {}});
|
||||
if (!$keywords) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach my $keyword (@$keywords) {
|
||||
bless($keyword, $class);
|
||||
}
|
||||
return $keywords;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Validators ###
|
||||
###############################
|
||||
|
||||
sub _check_name {
|
||||
my ($self, $name) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name eq "" && ThrowUserError("keyword_blank_name");
|
||||
if ($name =~ /[\s,]/) {
|
||||
ThrowUserError("keyword_invalid_name");
|
||||
}
|
||||
|
||||
# We only want to validate the non-existence of the name if
|
||||
# we're creating a new Keyword or actually renaming the keyword.
|
||||
if (!ref($self) || $self->name ne $name) {
|
||||
my $keyword = new Bugzilla::Keyword({ name => $name });
|
||||
ThrowUserError("keyword_already_exists", { name => $name }) if $keyword;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($self, $desc) = @_;
|
||||
$desc = trim($desc);
|
||||
$desc eq '' && ThrowUserError("keyword_blank_description");
|
||||
return $desc;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Keyword - A Keyword that can be added to a bug.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Keyword;
|
||||
|
||||
my $count = Bugzilla::Keyword::keyword_count;
|
||||
|
||||
my $description = $keyword->description;
|
||||
|
||||
my $keywords = Bugzilla::Keyword->get_all_with_bug_count();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Keyword represents a keyword that can be added to a bug.
|
||||
|
||||
This implements all standard C<Bugzilla::Object> methods. See
|
||||
L<Bugzilla::Object> for more details.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
This is only a list of subroutines specific to C<Bugzilla::Keyword>.
|
||||
See L<Bugzilla::Object> for more subroutines that this object
|
||||
implements.
|
||||
|
||||
=over
|
||||
|
||||
=item C<keyword_count()>
|
||||
|
||||
Description: A utility function to get the total number
|
||||
of keywords defined. Mostly used to see
|
||||
if there are any keywords defined at all.
|
||||
Params: none
|
||||
Returns: An integer, the count of keywords.
|
||||
|
||||
=item C<get_all_with_bug_count()>
|
||||
|
||||
Description: Returns all defined keywords. This is an efficient way
|
||||
to get the associated bug counts, as only one SQL query
|
||||
is executed with this method, instead of one per keyword
|
||||
when calling get_all and then bug_count.
|
||||
Params: none
|
||||
Returns: A reference to an array of Keyword objects, or an empty
|
||||
arrayref if there are no keywords.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,192 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>,
|
||||
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Alan Raetz <al_raetz@yahoo.com>
|
||||
# Jacob Steenhagen <jake@actex.net>
|
||||
# Matthew Tuck <matty@chariot.net.au>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Mailer;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Mailer::EXPORT = qw(MessageToMTA build_thread_marker);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Date::Format qw(time2str);
|
||||
|
||||
use Encode qw(encode);
|
||||
use Encode::MIME::Header;
|
||||
use Email::Address;
|
||||
use Email::MIME;
|
||||
# Loading this gives us encoding_set.
|
||||
use Email::MIME::Modifier;
|
||||
use Email::Send;
|
||||
|
||||
sub MessageToMTA {
|
||||
my ($msg) = (@_);
|
||||
my $method = Bugzilla->params->{'mail_delivery_method'};
|
||||
return if $method eq 'None';
|
||||
|
||||
my $email = ref($msg) ? $msg : Email::MIME->new($msg);
|
||||
|
||||
# We add this header to mark the mail as "auto-generated" and
|
||||
# thus to hopefully avoid auto replies.
|
||||
$email->header_set('Auto-Submitted', 'auto-generated');
|
||||
|
||||
$email->walk_parts(sub {
|
||||
my ($part) = @_;
|
||||
return if $part->parts > 1; # Top-level
|
||||
my $content_type = $part->content_type || '';
|
||||
if ($content_type !~ /;/) {
|
||||
my $body = $part->body;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
$part->charset_set('UTF-8');
|
||||
# encoding_set works only with bytes, not with utf8 strings.
|
||||
my $raw = $part->body_raw;
|
||||
if (utf8::is_utf8($raw)) {
|
||||
utf8::encode($raw);
|
||||
$part->body_set($raw);
|
||||
}
|
||||
}
|
||||
$part->encoding_set('quoted-printable') if !is_7bit_clean($body);
|
||||
}
|
||||
});
|
||||
|
||||
# MIME-Version must be set otherwise some mailsystems ignore the charset
|
||||
$email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
|
||||
|
||||
# Encode the headers correctly in quoted-printable
|
||||
foreach my $header ($email->header_names) {
|
||||
my @values = $email->header($header);
|
||||
# We don't recode headers that happen multiple times.
|
||||
next if scalar(@values) > 1;
|
||||
if (my $value = $values[0]) {
|
||||
if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
|
||||
utf8::decode($value);
|
||||
}
|
||||
|
||||
# avoid excessive line wrapping done by Encode.
|
||||
local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
|
||||
|
||||
my $encoded = encode('MIME-Q', $value);
|
||||
$email->header_set($header, $encoded);
|
||||
}
|
||||
}
|
||||
|
||||
my $from = $email->header('From');
|
||||
|
||||
my ($hostname, @args);
|
||||
if ($method eq "Sendmail") {
|
||||
if (ON_WINDOWS) {
|
||||
$Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
|
||||
}
|
||||
push @args, "-i";
|
||||
# We want to make sure that we pass *only* an email address.
|
||||
if ($from) {
|
||||
my ($email_obj) = Email::Address->parse($from);
|
||||
if ($email_obj) {
|
||||
my $from_email = $email_obj->address;
|
||||
push(@args, "-f$from_email") if $from_email;
|
||||
}
|
||||
}
|
||||
push(@args, "-ODeliveryMode=deferred")
|
||||
if !Bugzilla->params->{"sendmailnow"};
|
||||
}
|
||||
else {
|
||||
# Sendmail will automatically append our hostname to the From
|
||||
# address, but other mailers won't.
|
||||
my $urlbase = Bugzilla->params->{'urlbase'};
|
||||
$urlbase =~ m|//([^:/]+)[:/]?|;
|
||||
$hostname = $1;
|
||||
$from .= "\@$hostname" if $from !~ /@/;
|
||||
$email->header_set('From', $from);
|
||||
|
||||
# Sendmail adds a Date: header also, but others may not.
|
||||
if (!defined $email->header('Date')) {
|
||||
$email->header_set('Date', time2str("%a, %e %b %Y %T %z", time()));
|
||||
}
|
||||
}
|
||||
|
||||
if ($method eq "SMTP") {
|
||||
push @args, Host => Bugzilla->params->{"smtpserver"},
|
||||
username => Bugzilla->params->{"smtp_username"},
|
||||
password => Bugzilla->params->{"smtp_password"},
|
||||
Hello => $hostname,
|
||||
Debug => Bugzilla->params->{'smtp_debug'};
|
||||
}
|
||||
|
||||
if ($method eq "Test") {
|
||||
my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
|
||||
open TESTFILE, '>>', $filename;
|
||||
# From - <date> is required to be a valid mbox file.
|
||||
print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
|
||||
close TESTFILE;
|
||||
}
|
||||
else {
|
||||
# This is useful for both Sendmail and Qmail, so we put it out here.
|
||||
local $ENV{PATH} = SENDMAIL_PATH;
|
||||
my $mailer = Email::Send->new({ mailer => $method,
|
||||
mailer_args => \@args });
|
||||
my $retval = $mailer->send($email);
|
||||
ThrowCodeError('mail_send_error', { msg => $retval, mail => $email })
|
||||
if !$retval;
|
||||
}
|
||||
}
|
||||
|
||||
# Builds header suitable for use as a threading marker in email notifications
|
||||
sub build_thread_marker {
|
||||
my ($bug_id, $user_id, $is_new) = @_;
|
||||
|
||||
if (!defined $user_id) {
|
||||
$user_id = Bugzilla->user->id;
|
||||
}
|
||||
|
||||
my $sitespec = '@' . Bugzilla->params->{'urlbase'};
|
||||
$sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
|
||||
$sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
|
||||
if ($2) {
|
||||
$sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
|
||||
}
|
||||
|
||||
my $threadingmarker;
|
||||
if ($is_new) {
|
||||
$threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
|
||||
}
|
||||
else {
|
||||
$threadingmarker = "In-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
|
||||
"\nReferences: <bug-$bug_id-$user_id$sitespec>";
|
||||
}
|
||||
|
||||
return $threadingmarker;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,375 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Milestone;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DEFAULT_SORTKEY => 0;
|
||||
|
||||
use constant DB_TABLE => 'milestones';
|
||||
use constant NAME_FIELD => 'value';
|
||||
use constant LIST_ORDER => 'sortkey, value';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
product_id
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(
|
||||
name
|
||||
product
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
value
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
product => \&_check_product,
|
||||
sortkey => \&_check_sortkey,
|
||||
};
|
||||
|
||||
use constant UPDATE_VALIDATORS => {
|
||||
value => \&_check_value,
|
||||
};
|
||||
|
||||
################################
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND value = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
return $class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
my $product = delete $params->{product};
|
||||
$params->{product_id} = $product->id;
|
||||
$params->{value} = $class->_check_value($params->{name}, $product);
|
||||
delete $params->{name};
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
if (exists $changes->{value}) {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
# The milestone value is stored in the bugs table instead of its ID.
|
||||
$dbh->do('UPDATE bugs SET target_milestone = ?
|
||||
WHERE target_milestone = ? AND product_id = ?',
|
||||
undef, ($self->name, $changes->{value}->[0], $self->product_id));
|
||||
|
||||
# The default milestone also stores the value instead of the ID.
|
||||
$dbh->do('UPDATE products SET defaultmilestone = ?
|
||||
WHERE id = ? AND defaultmilestone = ?',
|
||||
undef, ($self->name, $self->product_id, $changes->{value}->[0]));
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The default milestone cannot be deleted.
|
||||
if ($self->name eq $self->product->default_milestone) {
|
||||
ThrowUserError('milestone_is_default', { milestone => $self });
|
||||
}
|
||||
|
||||
if ($self->bug_count) {
|
||||
# We don't want to delete bugs when deleting a milestone.
|
||||
# Bugs concerned are reassigned to the default milestone.
|
||||
my $bug_ids =
|
||||
$dbh->selectcol_arrayref('SELECT bug_id FROM bugs
|
||||
WHERE product_id = ? AND target_milestone = ?',
|
||||
undef, ($self->product->id, $self->name));
|
||||
|
||||
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
|
||||
|
||||
$dbh->do('UPDATE bugs SET target_milestone = ?, delta_ts = ?
|
||||
WHERE ' . $dbh->sql_in('bug_id', $bug_ids),
|
||||
undef, ($self->product->default_milestone, $timestamp));
|
||||
|
||||
require Bugzilla::Bug;
|
||||
import Bugzilla::Bug qw(LogActivityEntry);
|
||||
foreach my $bug_id (@$bug_ids) {
|
||||
LogActivityEntry($bug_id, 'target_milestone',
|
||||
$self->name,
|
||||
$self->product->default_milestone,
|
||||
Bugzilla->user->id, $timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
|
||||
}
|
||||
|
||||
################################
|
||||
# Validators
|
||||
################################
|
||||
|
||||
sub _check_value {
|
||||
my ($invocant, $name, $product) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError('milestone_blank_name');
|
||||
if (length($name) > MAX_MILESTONE_SIZE) {
|
||||
ThrowUserError('milestone_name_too_long', {name => $name});
|
||||
}
|
||||
|
||||
$product = $invocant->product if (ref $invocant);
|
||||
my $milestone = new Bugzilla::Milestone({product => $product, name => $name});
|
||||
if ($milestone && (!ref $invocant || $milestone->id != $invocant->id)) {
|
||||
ThrowUserError('milestone_already_exists', { name => $milestone->name,
|
||||
product => $product->name });
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_sortkey {
|
||||
my ($invocant, $sortkey) = @_;
|
||||
|
||||
# Keep a copy in case detaint_signed() clears the sortkey
|
||||
my $stored_sortkey = $sortkey;
|
||||
|
||||
if (!detaint_signed($sortkey) || $sortkey < MIN_SMALLINT || $sortkey > MAX_SMALLINT) {
|
||||
ThrowUserError('milestone_sortkey_invalid', {sortkey => $stored_sortkey});
|
||||
}
|
||||
return $sortkey;
|
||||
}
|
||||
|
||||
sub _check_product {
|
||||
my ($invocant, $product) = @_;
|
||||
return Bugzilla->user->check_can_admin_product($product->name);
|
||||
}
|
||||
|
||||
################################
|
||||
# Methods
|
||||
################################
|
||||
|
||||
sub set_name { $_[0]->set('value', $_[1]); }
|
||||
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE product_id = ? AND target_milestone = ?},
|
||||
undef, $self->product_id, $self->name) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
################################
|
||||
##### Accessors ######
|
||||
################################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
|
||||
sub product {
|
||||
my $self = shift;
|
||||
|
||||
require Bugzilla::Product;
|
||||
$self->{'product'} ||= new Bugzilla::Product($self->product_id);
|
||||
return $self->{'product'};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Milestone - Bugzilla product milestone class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Milestone;
|
||||
|
||||
my $milestone = new Bugzilla::Milestone({ name => $name, product => $product });
|
||||
|
||||
my $name = $milestone->name;
|
||||
my $product_id = $milestone->product_id;
|
||||
my $product = $milestone->product;
|
||||
my $sortkey = $milestone->sortkey;
|
||||
|
||||
my $milestone = Bugzilla::Milestone->create(
|
||||
{ name => $name, product => $product, sortkey => $sortkey });
|
||||
|
||||
$milestone->set_name($new_name);
|
||||
$milestone->set_sortkey($new_sortkey);
|
||||
$milestone->update();
|
||||
|
||||
$milestone->remove_from_db;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Milestone.pm represents a Product Milestone object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new({name => $name, product => $product})>
|
||||
|
||||
Description: The constructor is used to load an existing milestone
|
||||
by passing a product object and a milestone name.
|
||||
|
||||
Params: $product - a Bugzilla::Product object.
|
||||
$name - the name of a milestone (string).
|
||||
|
||||
Returns: A Bugzilla::Milestone object.
|
||||
|
||||
=item C<name()>
|
||||
|
||||
Description: Name (value) of the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The name of the milestone.
|
||||
|
||||
=item C<product_id()>
|
||||
|
||||
Description: ID of the product the milestone belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The ID of a product.
|
||||
|
||||
=item C<product()>
|
||||
|
||||
Description: The product object of the product the milestone belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::Product object.
|
||||
|
||||
=item C<sortkey()>
|
||||
|
||||
Description: Sortkey of the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The sortkey of the milestone.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<set_name($new_name)>
|
||||
|
||||
Description: Changes the name of the milestone.
|
||||
|
||||
Params: $new_name - new name of the milestone (string). This name
|
||||
must be unique within the product.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_sortkey($new_sortkey)>
|
||||
|
||||
Description: Changes the sortkey of the milestone.
|
||||
|
||||
Params: $new_sortkey - new sortkey of the milestone (signed integer).
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<update()>
|
||||
|
||||
Description: Writes the new name and/or the new sortkey into the DB.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hashref with changes made to the milestone object.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Deletes the current milestone from the DB. The object itself
|
||||
is not destroyed.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<create({name => $name, product => $product, sortkey => $sortkey})>
|
||||
|
||||
Description: Create a new milestone for the given product.
|
||||
|
||||
Params: $name - name of the new milestone (string). This name
|
||||
must be unique within the product.
|
||||
$product - a Bugzilla::Product object.
|
||||
$sortkey - the sortkey of the new milestone (signed integer)
|
||||
|
||||
Returns: A Bugzilla::Milestone object.
|
||||
|
||||
=back
|
||||
@@ -1,789 +0,0 @@
|
||||
# -*- 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>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Object;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use Date::Parse;
|
||||
|
||||
use constant NAME_FIELD => 'name';
|
||||
use constant ID_FIELD => 'id';
|
||||
use constant LIST_ORDER => NAME_FIELD;
|
||||
|
||||
use constant UPDATE_VALIDATORS => {};
|
||||
use constant NUMERIC_COLUMNS => ();
|
||||
use constant DATE_COLUMNS => ();
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $object = $class->_init(@_);
|
||||
bless($object, $class) if $object;
|
||||
return $object;
|
||||
}
|
||||
|
||||
|
||||
# Note: Because this uses sql_istrcmp, if you make a new object use
|
||||
# Bugzilla::Object, make sure that you modify bz_setup_database
|
||||
# in Bugzilla::DB::Pg appropriately, to add the right LOWER
|
||||
# index. You can see examples already there.
|
||||
sub _init {
|
||||
my $class = shift;
|
||||
my ($param) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $columns = join(',', $class->DB_COLUMNS);
|
||||
my $table = $class->DB_TABLE;
|
||||
my $name_field = $class->NAME_FIELD;
|
||||
my $id_field = $class->ID_FIELD;
|
||||
|
||||
my $id = $param unless (ref $param eq 'HASH');
|
||||
my $object;
|
||||
|
||||
if (defined $id) {
|
||||
# We special-case if somebody specifies an ID, so that we can
|
||||
# validate it as numeric.
|
||||
detaint_natural($id)
|
||||
|| ThrowCodeError('param_must_be_numeric',
|
||||
{function => $class . '::_init'});
|
||||
|
||||
$object = $dbh->selectrow_hashref(qq{
|
||||
SELECT $columns FROM $table
|
||||
WHERE $id_field = ?}, undef, $id);
|
||||
} else {
|
||||
unless (defined $param->{name} || (defined $param->{'condition'}
|
||||
&& defined $param->{'values'}))
|
||||
{
|
||||
ThrowCodeError('bad_arg', { argument => 'param',
|
||||
function => $class . '::new' });
|
||||
}
|
||||
|
||||
my ($condition, @values);
|
||||
if (defined $param->{name}) {
|
||||
$condition = $dbh->sql_istrcmp($name_field, '?');
|
||||
push(@values, $param->{name});
|
||||
}
|
||||
elsif (defined $param->{'condition'} && defined $param->{'values'}) {
|
||||
caller->isa('Bugzilla::Object')
|
||||
|| ThrowCodeError('protection_violation',
|
||||
{ caller => caller,
|
||||
function => $class . '::new',
|
||||
argument => 'condition/values' });
|
||||
$condition = $param->{'condition'};
|
||||
push(@values, @{$param->{'values'}});
|
||||
}
|
||||
|
||||
map { trick_taint($_) } @values;
|
||||
$object = $dbh->selectrow_hashref(
|
||||
"SELECT $columns FROM $table WHERE $condition", undef, @values);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
sub check {
|
||||
my ($invocant, $param) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
# If we were just passed a name, then just use the name.
|
||||
if (!ref $param) {
|
||||
$param = { name => $param };
|
||||
}
|
||||
# Don't allow empty names.
|
||||
if (exists $param->{name}) {
|
||||
$param->{name} = trim($param->{name});
|
||||
$param->{name} || ThrowUserError('object_name_not_specified',
|
||||
{ class => $class });
|
||||
}
|
||||
my $obj = $class->new($param)
|
||||
|| ThrowUserError('object_does_not_exist', {%$param, class => $class});
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub new_from_list {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my ($id_list) = @_;
|
||||
my $id_field = $class->ID_FIELD;
|
||||
|
||||
my @detainted_ids;
|
||||
foreach my $id (@$id_list) {
|
||||
detaint_natural($id) ||
|
||||
ThrowCodeError('param_must_be_numeric',
|
||||
{function => $class . '::new_from_list'});
|
||||
push(@detainted_ids, $id);
|
||||
}
|
||||
# We don't do $invocant->match because some classes have
|
||||
# their own implementation of match which is not compatible
|
||||
# with this one. However, match() still needs to have the right $invocant
|
||||
# in order to do $class->DB_TABLE and so on.
|
||||
return match($invocant, { $id_field => \@detainted_ids });
|
||||
}
|
||||
|
||||
# Note: Future extensions to this could be:
|
||||
# * Add a MATCH_JOIN constant so that we can join against
|
||||
# certain other tables for the WHERE criteria.
|
||||
sub match {
|
||||
my ($invocant, $criteria) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return [$class->get_all] if !$criteria;
|
||||
|
||||
my (@terms, @values);
|
||||
foreach my $field (keys %$criteria) {
|
||||
my $value = $criteria->{$field};
|
||||
if (ref $value eq 'ARRAY') {
|
||||
# IN () is invalid SQL, and if we have an empty list
|
||||
# to match against, we're just returning an empty
|
||||
# array anyhow.
|
||||
return [] if !scalar @$value;
|
||||
|
||||
my @qmarks = ("?") x @$value;
|
||||
push(@terms, $dbh->sql_in($field, \@qmarks));
|
||||
push(@values, @$value);
|
||||
}
|
||||
elsif ($value eq NOT_NULL) {
|
||||
push(@terms, "$field IS NOT NULL");
|
||||
}
|
||||
elsif ($value eq IS_NULL) {
|
||||
push(@terms, "$field IS NULL");
|
||||
}
|
||||
else {
|
||||
push(@terms, "$field = ?");
|
||||
push(@values, $value);
|
||||
}
|
||||
}
|
||||
|
||||
my $where = join(' AND ', @terms);
|
||||
return $class->_do_list_select($where, \@values);
|
||||
}
|
||||
|
||||
sub _do_list_select {
|
||||
my ($class, $where, $values) = @_;
|
||||
my $table = $class->DB_TABLE;
|
||||
my $cols = join(',', $class->DB_COLUMNS);
|
||||
my $order = $class->LIST_ORDER;
|
||||
|
||||
my $sql = "SELECT $cols FROM $table";
|
||||
if (defined $where) {
|
||||
$sql .= " WHERE $where ";
|
||||
}
|
||||
$sql .= " ORDER BY $order";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @$values);
|
||||
bless ($_, $class) foreach @$objects;
|
||||
return $objects
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{$_[0]->ID_FIELD}; }
|
||||
sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub set {
|
||||
my ($self, $field, $value) = @_;
|
||||
|
||||
# This method is protected. It's used to help implement set_ functions.
|
||||
caller->isa('Bugzilla::Object')
|
||||
|| ThrowCodeError('protection_violation',
|
||||
{ caller => caller,
|
||||
superclass => __PACKAGE__,
|
||||
function => 'Bugzilla::Object->set' });
|
||||
|
||||
my %validators = (%{$self->VALIDATORS}, %{$self->UPDATE_VALIDATORS});
|
||||
if (exists $validators{$field}) {
|
||||
my $validator = $validators{$field};
|
||||
$value = $self->$validator($value, $field);
|
||||
trick_taint($value) if (defined $value && !ref($value));
|
||||
|
||||
if ($self->can('_set_global_validator')) {
|
||||
$self->_set_global_validator($value, $field);
|
||||
}
|
||||
}
|
||||
|
||||
$self->{$field} = $value;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $table = $self->DB_TABLE;
|
||||
my $id_field = $self->ID_FIELD;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $old_self = $self->new($self->id);
|
||||
|
||||
my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
|
||||
my %date = map { $_ => 1 } $self->DATE_COLUMNS;
|
||||
my (@update_columns, @values, %changes);
|
||||
foreach my $column ($self->UPDATE_COLUMNS) {
|
||||
my ($old, $new) = ($old_self->{$column}, $self->{$column});
|
||||
# This has to be written this way in order to allow us to set a field
|
||||
# from undef or to undef, and avoid warnings about comparing an undef
|
||||
# with the "eq" operator.
|
||||
if (!defined $new || !defined $old) {
|
||||
next if !defined $new && !defined $old;
|
||||
}
|
||||
elsif ( ($numeric{$column} && $old == $new)
|
||||
|| ($date{$column} && str2time($old) == str2time($new))
|
||||
|| $old eq $new ) {
|
||||
next;
|
||||
}
|
||||
|
||||
trick_taint($new) if defined $new;
|
||||
push(@values, $new);
|
||||
push(@update_columns, $column);
|
||||
# We don't use $new because we don't want to detaint this for
|
||||
# the caller.
|
||||
$changes{$column} = [$old, $self->{$column}];
|
||||
}
|
||||
|
||||
my $columns = join(', ', map {"$_ = ?"} @update_columns);
|
||||
|
||||
$dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
|
||||
@values, $self->id) if @values;
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return \%changes;
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub create {
|
||||
my ($class, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
$class->check_required_create_fields($params);
|
||||
my $field_values = $class->run_create_validators($params);
|
||||
my $object = $class->insert_create_data($field_values);
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
sub check_required_create_fields {
|
||||
my ($class, $params) = @_;
|
||||
|
||||
foreach my $field ($class->REQUIRED_CREATE_FIELDS) {
|
||||
ThrowCodeError('param_required',
|
||||
{ function => "${class}->create", param => $field })
|
||||
if !exists $params->{$field};
|
||||
}
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my ($class, $params) = @_;
|
||||
|
||||
my $validators = $class->VALIDATORS;
|
||||
|
||||
my %field_values;
|
||||
# We do the sort just to make sure that validation always
|
||||
# happens in a consistent order.
|
||||
foreach my $field (sort keys %$params) {
|
||||
my $value;
|
||||
if (exists $validators->{$field}) {
|
||||
my $validator = $validators->{$field};
|
||||
$value = $class->$validator($params->{$field}, $field);
|
||||
}
|
||||
else {
|
||||
$value = $params->{$field};
|
||||
}
|
||||
# We want people to be able to explicitly set fields to NULL,
|
||||
# and that means they can be set to undef.
|
||||
trick_taint($value) if defined $value && !ref($value);
|
||||
$field_values{$field} = $value;
|
||||
}
|
||||
|
||||
return \%field_values;
|
||||
}
|
||||
|
||||
sub insert_create_data {
|
||||
my ($class, $field_values) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my (@field_names, @values);
|
||||
while (my ($field, $value) = each %$field_values) {
|
||||
push(@field_names, $field);
|
||||
push(@values, $value);
|
||||
}
|
||||
|
||||
my $qmarks = '?,' x @field_names;
|
||||
chop($qmarks);
|
||||
my $table = $class->DB_TABLE;
|
||||
$dbh->do("INSERT INTO $table (" . join(', ', @field_names)
|
||||
. ") VALUES ($qmarks)", undef, @values);
|
||||
my $id = $dbh->bz_last_key($table, $class->ID_FIELD);
|
||||
return $class->new($id);
|
||||
}
|
||||
|
||||
sub get_all {
|
||||
my $class = shift;
|
||||
return @{$class->_do_list_select()};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Validators ######
|
||||
###############################
|
||||
|
||||
sub check_boolean { return $_[1] ? 1 : 0 }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Object - A base class for objects in Bugzilla.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $object = new Bugzilla::Object(1);
|
||||
my $object = new Bugzilla::Object({name => 'TestProduct'});
|
||||
|
||||
my $id = $object->id;
|
||||
my $name = $object->name;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Object is a base class for Bugzilla objects. You never actually
|
||||
create a Bugzilla::Object directly, you only make subclasses of it.
|
||||
|
||||
Basically, Bugzilla::Object exists to allow developers to create objects
|
||||
more easily. All you have to do is define C<DB_TABLE>, C<DB_COLUMNS>,
|
||||
and sometimes C<LIST_ORDER> and you have a whole new object.
|
||||
|
||||
You should also define accessors for any columns other than C<name>
|
||||
or C<id>.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
Frequently, these will be the only things you have to define in your
|
||||
subclass in order to have a fully-functioning object. C<DB_TABLE>
|
||||
and C<DB_COLUMNS> are required.
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_TABLE>
|
||||
|
||||
The name of the table that these objects are stored in. For example,
|
||||
for C<Bugzilla::Keyword> this would be C<keyworddefs>.
|
||||
|
||||
=item C<DB_COLUMNS>
|
||||
|
||||
The names of the columns that you want to read out of the database
|
||||
and into this object. This should be an array.
|
||||
|
||||
=item C<NAME_FIELD>
|
||||
|
||||
The name of the column that should be considered to be the unique
|
||||
"name" of this object. The 'name' is a B<string> that uniquely identifies
|
||||
this Object in the database. Defaults to 'name'. When you specify
|
||||
C<{name => $name}> to C<new()>, this is the column that will be
|
||||
matched against in the DB.
|
||||
|
||||
=item C<ID_FIELD>
|
||||
|
||||
The name of the column that represents the unique B<integer> ID
|
||||
of this object in the database. Defaults to 'id'.
|
||||
|
||||
=item C<LIST_ORDER>
|
||||
|
||||
The order that C<new_from_list> and C<get_all> should return objects
|
||||
in. This should be the name of a database column. Defaults to
|
||||
L</NAME_FIELD>.
|
||||
|
||||
=item C<REQUIRED_CREATE_FIELDS>
|
||||
|
||||
The list of fields that B<must> be specified when the user calls
|
||||
C<create()>. This should be an array.
|
||||
|
||||
=item C<VALIDATORS>
|
||||
|
||||
A hashref that points to a function that will validate each param to
|
||||
L</create>.
|
||||
|
||||
Validators are called both by L</create> and L</set>. When
|
||||
they are called by L</create>, the first argument will be the name
|
||||
of the class (what we normally call C<$class>).
|
||||
|
||||
When they are called by L</set>, the first argument will be
|
||||
a reference to the current object (what we normally call C<$self>).
|
||||
|
||||
The second argument will be the value passed to L</create> or
|
||||
L</set>for that field.
|
||||
|
||||
The third argument will be the name of the field being validated.
|
||||
This may be required by validators which validate several distinct fields.
|
||||
|
||||
These functions should call L<Bugzilla::Error/ThrowUserError> if they fail.
|
||||
|
||||
The validator must return the validated value.
|
||||
|
||||
=item C<UPDATE_VALIDATORS>
|
||||
|
||||
This is just like L</VALIDATORS>, but these validators are called only
|
||||
when updating an object, not when creating it. Any validator that appears
|
||||
here must not appear in L</VALIDATORS>.
|
||||
|
||||
L<Bugzilla::Bug> has good examples in its code of when to use this.
|
||||
|
||||
=item C<UPDATE_COLUMNS>
|
||||
|
||||
A list of columns to update when L</update> is called.
|
||||
If a field can't be changed, it shouldn't be listed here. (For example,
|
||||
the L</ID_FIELD> usually can't be updated.)
|
||||
|
||||
=item C<NUMERIC_COLUMNS>
|
||||
|
||||
When L</update> is called, it compares each column in the object to its
|
||||
current value in the database. It only updates columns that have changed.
|
||||
|
||||
Any column listed in NUMERIC_COLUMNS is treated as a number, not as a string,
|
||||
during these comparisons.
|
||||
|
||||
=item C<DATE_COLUMNS>
|
||||
|
||||
This is much like L</NUMERIC_COLUMNS>, except that it treats strings as
|
||||
dates when being compared. So, for example, C<2007-01-01> would be
|
||||
equal to C<2007-01-01 00:00:00>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Constructors
|
||||
|
||||
=over
|
||||
|
||||
=item C<new>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
The constructor is used to load an existing object from the database,
|
||||
by id or by name.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
If you pass an integer, the integer is the id of the object,
|
||||
from the database, that we want to read in. (id is defined
|
||||
as the value in the L</ID_FIELD> column).
|
||||
|
||||
If you pass in a hashref, you can pass a C<name> key. The
|
||||
value of the C<name> key is the case-insensitive name of the object
|
||||
(from L</NAME_FIELD>) in the DB.
|
||||
|
||||
B<Additional Parameters Available for Subclasses>
|
||||
|
||||
If you are a subclass of C<Bugzilla::Object>, you can pass
|
||||
C<condition> and C<values> as hash keys, instead of the above.
|
||||
|
||||
C<condition> is a set of SQL conditions for the WHERE clause, which contain
|
||||
placeholders.
|
||||
|
||||
C<values> is a reference to an array. The array contains the values
|
||||
for each placeholder in C<condition>, in order.
|
||||
|
||||
This is to allow subclasses to have complex parameters, and then to
|
||||
translate those parameters into C<condition> and C<values> when they
|
||||
call C<$self->SUPER::new> (which is this function, usually).
|
||||
|
||||
If you try to call C<new> outside of a subclass with the C<condition>
|
||||
and C<values> parameters, Bugzilla will throw an error. These parameters
|
||||
are intended B<only> for use by subclasses.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A fully-initialized object, or C<undef> if there is no object in the
|
||||
database matching the parameters you passed in.
|
||||
|
||||
=back
|
||||
|
||||
=item C<check>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Checks if there is an object in the database with the specified name, and
|
||||
throws an error if you specified an empty name, or if there is no object
|
||||
in the database with that name.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
The parameters are the same as for L</new>, except that if you don't pass
|
||||
a hashref, the single argument is the I<name> of the object, not the id.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A fully initialized object, guaranteed.
|
||||
|
||||
=item B<Notes For Implementors>
|
||||
|
||||
If you implement this in your subclass, make sure that you also update
|
||||
the C<object_name> block at the bottom of the F<global/user-error.html.tmpl>
|
||||
template.
|
||||
|
||||
=back
|
||||
|
||||
=item C<new_from_list(\@id_list)>
|
||||
|
||||
Description: Creates an array of objects, given an array of ids.
|
||||
|
||||
Params: \@id_list - A reference to an array of numbers, database ids.
|
||||
If any of these are not numeric, the function
|
||||
will throw an error. If any of these are not
|
||||
valid ids in the database, they will simply
|
||||
be skipped.
|
||||
|
||||
Returns: A reference to an array of objects.
|
||||
|
||||
=item C<match>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets a list of objects from the database based on certain criteria.
|
||||
|
||||
Basically, a simple way of doing a sort of "SELECT" statement (like SQL)
|
||||
to get objects.
|
||||
|
||||
All criteria are joined by C<AND>, so adding more criteria will give you
|
||||
a smaller set of results, not a larger set.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
A hashref, where the keys are column names of the table, pointing to the
|
||||
value that you want to match against for that column.
|
||||
|
||||
There are two special values, the constants C<NULL> and C<NOT_NULL>,
|
||||
which means "give me objects where this field is NULL or NOT NULL,
|
||||
respectively."
|
||||
|
||||
If you don't specify any criteria, calling this function is the same
|
||||
as doing C<[$class-E<gt>get_all]>.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
An arrayref of objects, or an empty arrayref if there are no matches.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Database Manipulation
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Description: Creates a new item in the database.
|
||||
Throws a User Error if any of the passed-in params
|
||||
are invalid.
|
||||
|
||||
Params: C<$params> - hashref - A value to put in each database
|
||||
field for this object. Certain values must be set (the
|
||||
ones specified in L</REQUIRED_CREATE_FIELDS>), and
|
||||
the function will throw a Code Error if you don't set
|
||||
them.
|
||||
|
||||
Returns: The Object just created in the database.
|
||||
|
||||
Notes: In order for this function to work in your subclass,
|
||||
your subclass's L</ID_FIELD> must be of C<SERIAL>
|
||||
type in the database. Your subclass also must
|
||||
define L</REQUIRED_CREATE_FIELDS> and L</VALIDATORS>.
|
||||
|
||||
Subclass Implementors: This function basically just
|
||||
calls L</check_required_create_fields>, then
|
||||
L</run_create_validators>, and then finally
|
||||
L</insert_create_data>. So if you have a complex system that
|
||||
you need to implement, you can do it by calling these
|
||||
three functions instead of C<SUPER::create>.
|
||||
|
||||
=item C<check_required_create_fields>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Part of L</create>. Throws an error if any of the L</REQUIRED_CREATE_FIELDS>
|
||||
have not been specified in C<$params>
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$params> - The same as C<$params> from L</create>.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=item C<run_create_validators>
|
||||
|
||||
Description: Runs the validation of input parameters for L</create>.
|
||||
This subroutine exists so that it can be overridden
|
||||
by subclasses who need to do special validations
|
||||
of their input parameters. This method is B<only> called
|
||||
by L</create>.
|
||||
|
||||
Params: The same as L</create>.
|
||||
|
||||
Returns: A hash, in a similar format as C<$params>, except that
|
||||
these are the values to be inserted into the database,
|
||||
not the values that were input to L</create>.
|
||||
|
||||
=item C<insert_create_data>
|
||||
|
||||
Part of L</create>.
|
||||
|
||||
Takes the return value from L</run_create_validators> and inserts the
|
||||
data into the database. Returns a newly created object.
|
||||
|
||||
=item C<update>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Saves the values currently in this object to the database.
|
||||
Only the fields specified in L</UPDATE_COLUMNS> will be
|
||||
updated, and they will only be updated if their values have changed.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hashref showing what changed during the update. The keys are the column
|
||||
names from L</UPDATE_COLUMNS>. If a field was not changed, it will not be
|
||||
in the hash at all. If the field was changed, the key will point to an arrayref.
|
||||
The first item of the arrayref will be the old value, and the second item
|
||||
will be the new value.
|
||||
|
||||
If there were no changes, we return a reference to an empty hash.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Subclass Helpers
|
||||
|
||||
These functions are intended only for use by subclasses. If
|
||||
you call them from anywhere else, they will throw a C<CodeError>.
|
||||
|
||||
=over
|
||||
|
||||
=item C<set>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Sets a certain hash member of this class to a certain value.
|
||||
Used for updating fields. Calls the validator for this field,
|
||||
if it exists. Subclasses should use this function
|
||||
to implement the various C<set_> mutators for their different
|
||||
fields.
|
||||
|
||||
If your class defines a method called C<_set_global_validator>,
|
||||
C<set> will call it with C<($value, $field)> as arguments, after running
|
||||
the validator for this particular field. C<_set_global_validator> does not
|
||||
return anything.
|
||||
|
||||
|
||||
See L</VALIDATORS> for more information.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$field> - The name of the hash member to update. This should
|
||||
be the same as the name of the field in L</VALIDATORS>, if it exists there.
|
||||
|
||||
=item C<$value> - The value that you're setting the field to.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Simple Validators
|
||||
|
||||
You can use these in your subclass L</VALIDATORS> or L</UPDATE_VALIDATORS>.
|
||||
Note that you have to reference them like C<\&Bugzilla::Object::check_boolean>,
|
||||
you can't just write C<\&check_boolean>.
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_boolean>
|
||||
|
||||
Returns C<1> if the passed-in value is true, C<0> otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_all>
|
||||
|
||||
Description: Returns all objects in this table from the database.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of objects, or an empty list if there are none.
|
||||
|
||||
Notes: Note that you must call this as C<$class->get_all>. For
|
||||
example, C<Bugzilla::Keyword->get_all>.
|
||||
C<Bugzilla::Keyword::get_all> will not work.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,469 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Product;
|
||||
|
||||
use Bugzilla::Version;
|
||||
use Bugzilla::Milestone;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use Bugzilla::Install::Requirements;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use constant DEFAULT_CLASSIFICATION_ID => 1;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'products';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
products.id
|
||||
products.name
|
||||
products.classification_id
|
||||
products.description
|
||||
products.milestoneurl
|
||||
products.disallownew
|
||||
products.votesperuser
|
||||
products.maxvotesperbug
|
||||
products.votestoconfirm
|
||||
products.defaultmilestone
|
||||
);
|
||||
|
||||
###############################
|
||||
#### Constructors #####
|
||||
###############################
|
||||
|
||||
# This is considerably faster than calling new_from_list three times
|
||||
# for each product in the list, particularly with hundreds or thousands
|
||||
# of products.
|
||||
sub preload {
|
||||
my ($products) = @_;
|
||||
my %prods = map { $_->id => $_ } @$products;
|
||||
my @prod_ids = keys %prods;
|
||||
return unless @prod_ids;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
foreach my $field (qw(component version milestone)) {
|
||||
my $classname = "Bugzilla::" . ucfirst($field);
|
||||
my $objects = $classname->match({ product_id => \@prod_ids });
|
||||
|
||||
# Now populate the products with this set of objects.
|
||||
foreach my $obj (@$objects) {
|
||||
my $product_id = $obj->product_id;
|
||||
$prods{$product_id}->{"${field}s"} ||= [];
|
||||
push(@{$prods{$product_id}->{"${field}s"}}, $obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub components {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{components}) {
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM components
|
||||
WHERE product_id = ?
|
||||
ORDER BY name}, undef, $self->id);
|
||||
|
||||
require Bugzilla::Component;
|
||||
$self->{components} = Bugzilla::Component->new_from_list($ids);
|
||||
}
|
||||
return $self->{components};
|
||||
}
|
||||
|
||||
sub group_controls {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{group_controls}) {
|
||||
my $query = qq{SELECT
|
||||
groups.id,
|
||||
group_control_map.entry,
|
||||
group_control_map.membercontrol,
|
||||
group_control_map.othercontrol,
|
||||
group_control_map.canedit,
|
||||
group_control_map.editcomponents,
|
||||
group_control_map.editbugs,
|
||||
group_control_map.canconfirm
|
||||
FROM groups
|
||||
LEFT JOIN group_control_map
|
||||
ON groups.id = group_control_map.group_id
|
||||
WHERE group_control_map.product_id = ?
|
||||
AND groups.isbuggroup != 0
|
||||
ORDER BY groups.name};
|
||||
$self->{group_controls} =
|
||||
$dbh->selectall_hashref($query, 'id', undef, $self->id);
|
||||
|
||||
# For each group ID listed above, create and store its group object.
|
||||
my @gids = keys %{$self->{group_controls}};
|
||||
my $groups = Bugzilla::Group->new_from_list(\@gids);
|
||||
$self->{group_controls}->{$_->id}->{group} = $_ foreach @$groups;
|
||||
}
|
||||
return $self->{group_controls};
|
||||
}
|
||||
|
||||
sub groups_mandatory_for {
|
||||
my ($self, $user) = @_;
|
||||
my $groups = $user->groups_as_string;
|
||||
my $mandatory = CONTROLMAPMANDATORY;
|
||||
# For membercontrol we don't check group_id IN, because if membercontrol
|
||||
# is Mandatory, the group is Mandatory for everybody, regardless of their
|
||||
# group membership.
|
||||
my $ids = Bugzilla->dbh->selectcol_arrayref(
|
||||
"SELECT group_id FROM group_control_map
|
||||
WHERE product_id = ?
|
||||
AND (membercontrol = $mandatory
|
||||
OR (othercontrol = $mandatory
|
||||
AND group_id NOT IN ($groups)))",
|
||||
undef, $self->id);
|
||||
return Bugzilla::Group->new_from_list($ids);
|
||||
}
|
||||
|
||||
sub groups_valid {
|
||||
my ($self) = @_;
|
||||
return $self->{groups_valid} if defined $self->{groups_valid};
|
||||
|
||||
# Note that we don't check OtherControl below, because there is no
|
||||
# valid NA/* combination.
|
||||
my $ids = Bugzilla->dbh->selectcol_arrayref(
|
||||
"SELECT DISTINCT group_id
|
||||
FROM group_control_map AS gcm
|
||||
INNER JOIN groups ON gcm.group_id = groups.id
|
||||
WHERE product_id = ? AND isbuggroup = 1
|
||||
AND membercontrol != " . CONTROLMAPNA, undef, $self->id);
|
||||
$self->{groups_valid} = Bugzilla::Group->new_from_list($ids);
|
||||
return $self->{groups_valid};
|
||||
}
|
||||
|
||||
sub versions {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{versions}) {
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM versions
|
||||
WHERE product_id = ?}, undef, $self->id);
|
||||
|
||||
$self->{versions} = Bugzilla::Version->new_from_list($ids);
|
||||
}
|
||||
return $self->{versions};
|
||||
}
|
||||
|
||||
sub milestones {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{milestones}) {
|
||||
my $ids = $dbh->selectcol_arrayref(q{
|
||||
SELECT id FROM milestones
|
||||
WHERE product_id = ?}, undef, $self->id);
|
||||
|
||||
$self->{milestones} = Bugzilla::Milestone->new_from_list($ids);
|
||||
}
|
||||
return $self->{milestones};
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(qq{
|
||||
SELECT COUNT(bug_id) FROM bugs
|
||||
WHERE product_id = ?}, undef, $self->id);
|
||||
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub bug_ids {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_ids'}) {
|
||||
$self->{'bug_ids'} =
|
||||
$dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs
|
||||
WHERE product_id = ?},
|
||||
undef, $self->id);
|
||||
}
|
||||
return $self->{'bug_ids'};
|
||||
}
|
||||
|
||||
sub user_has_access {
|
||||
my ($self, $user) = @_;
|
||||
|
||||
return Bugzilla->dbh->selectrow_array(
|
||||
'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END
|
||||
FROM products LEFT JOIN group_control_map
|
||||
ON group_control_map.product_id = products.id
|
||||
AND group_control_map.entry != 0
|
||||
AND group_id NOT IN (' . $user->groups_as_string . ')
|
||||
WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1),
|
||||
undef, $self->id);
|
||||
}
|
||||
|
||||
sub flag_types {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_types'}) {
|
||||
$self->{'flag_types'} = {};
|
||||
foreach my $type ('bug', 'attachment') {
|
||||
my %flagtypes;
|
||||
foreach my $component (@{$self->components}) {
|
||||
foreach my $flagtype (@{$component->flag_types->{$type}}) {
|
||||
$flagtypes{$flagtype->{'id'}} ||= $flagtype;
|
||||
}
|
||||
}
|
||||
$self->{'flag_types'}->{$type} = [sort { $a->{'sortkey'} <=> $b->{'sortkey'}
|
||||
|| $a->{'name'} cmp $b->{'name'} } values %flagtypes];
|
||||
}
|
||||
}
|
||||
return $self->{'flag_types'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub milestone_url { return $_[0]->{'milestoneurl'}; }
|
||||
sub disallow_new { return $_[0]->{'disallownew'}; }
|
||||
sub votes_per_user { return $_[0]->{'votesperuser'}; }
|
||||
sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'}; }
|
||||
sub votes_to_confirm { return $_[0]->{'votestoconfirm'}; }
|
||||
sub default_milestone { return $_[0]->{'defaultmilestone'}; }
|
||||
sub classification_id { return $_[0]->{'classification_id'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub check_product {
|
||||
my ($product_name) = @_;
|
||||
|
||||
unless ($product_name) {
|
||||
ThrowUserError('product_not_specified');
|
||||
}
|
||||
my $product = new Bugzilla::Product({name => $product_name});
|
||||
unless ($product) {
|
||||
ThrowUserError('product_doesnt_exist',
|
||||
{'product' => $product_name});
|
||||
}
|
||||
return $product;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Product - Bugzilla product class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Product;
|
||||
|
||||
my $product = new Bugzilla::Product(1);
|
||||
my $product = new Bugzilla::Product({ name => 'AcmeProduct' });
|
||||
|
||||
my @components = $product->components();
|
||||
my $groups_controls = $product->group_controls();
|
||||
my @milestones = $product->milestones();
|
||||
my @versions = $product->versions();
|
||||
my $bugcount = $product->bug_count();
|
||||
my $bug_ids = $product->bug_ids();
|
||||
my $has_access = $product->user_has_access($user);
|
||||
my $flag_types = $product->flag_types();
|
||||
|
||||
my $id = $product->id;
|
||||
my $name = $product->name;
|
||||
my $description = $product->description;
|
||||
my $milestoneurl = $product->milestone_url;
|
||||
my disallownew = $product->disallow_new;
|
||||
my votesperuser = $product->votes_per_user;
|
||||
my maxvotesperbug = $product->max_votes_per_bug;
|
||||
my votestoconfirm = $product->votes_to_confirm;
|
||||
my $defaultmilestone = $product->default_milestone;
|
||||
my $classificationid = $product->classification_id;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Product.pm represents a product object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus provides all methods that
|
||||
L<Bugzilla::Object> provides.
|
||||
|
||||
The methods that are specific to C<Bugzilla::Product> are listed
|
||||
below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<components>
|
||||
|
||||
Description: Returns an array of component objects belonging to
|
||||
the product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of Bugzilla::Component object.
|
||||
|
||||
=item C<group_controls()>
|
||||
|
||||
Description: Returns a hash (group id as key) with all product
|
||||
group controls.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hash with group id as key and hash containing
|
||||
a Bugzilla::Group object and the properties of group
|
||||
relative to the product.
|
||||
|
||||
=item C<groups_mandatory_for>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Tells you what groups are mandatory for bugs in this product.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
C<$user> - The user who you want to check.
|
||||
|
||||
=item B<Returns> An arrayref of C<Bugzilla::Group> objects.
|
||||
|
||||
=back
|
||||
|
||||
=item C<groups_valid>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns an arrayref of L<Bugzilla::Group> objects, representing groups
|
||||
that bugs could validly be restricted to within this product. Used mostly
|
||||
by L<Bugzilla::Bug> to assure that you're adding valid groups to a bug.
|
||||
|
||||
B<Note>: This doesn't check whether or not the current user can add/remove
|
||||
bugs to/from these groups. It just tells you that bugs I<could be in> these
|
||||
groups, in this product.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns> An arrayref of L<Bugzilla::Group> objects.
|
||||
|
||||
=back
|
||||
|
||||
=item C<versions>
|
||||
|
||||
Description: Returns all valid versions for that product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of Bugzilla::Version objects.
|
||||
|
||||
=item C<milestones>
|
||||
|
||||
Description: Returns all valid milestones for that product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of Bugzilla::Milestone objects.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<bug_ids()>
|
||||
|
||||
Description: Returns the IDs of bugs that belong to the product.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An array of integer.
|
||||
|
||||
=item C<user_has_access()>
|
||||
|
||||
Description: Tells you whether or not the user is allowed to enter
|
||||
bugs into this product, based on the C<entry> group
|
||||
control. To see whether or not a user can actually
|
||||
enter a bug into a product, use C<$user->can_enter_product>.
|
||||
|
||||
Params: C<$user> - A Bugzilla::User object.
|
||||
|
||||
Returns C<1> If this user's groups allow him C<entry> access to
|
||||
this Product, C<0> otherwise.
|
||||
|
||||
=item C<flag_types()>
|
||||
|
||||
Description: Returns flag types available for at least one of
|
||||
its components.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Two references to an array of flagtype objects.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<preload>
|
||||
|
||||
When passed an arrayref of C<Bugzilla::Product> objects, preloads their
|
||||
L</milestones>, L</components>, and L</versions>, which is much faster
|
||||
than calling those accessors on every item in the array individually.
|
||||
|
||||
This function is not exported, so must be called like
|
||||
C<Bugzilla::Product::preload($products)>.
|
||||
|
||||
=item C<check_product($product_name)>
|
||||
|
||||
Description: Checks if the product name was passed in and if is a valid
|
||||
product.
|
||||
|
||||
Params: $product_name - String with a product name.
|
||||
|
||||
Returns: Bugzilla::Product object.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::Object>
|
||||
|
||||
=cut
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,530 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): C. Begle
|
||||
# Jesse Ruderman
|
||||
# Andreas Franke <afranke@mathweb.org>
|
||||
# Stephen Lee <slee@uk.bnsmc.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Search::Quicksearch;
|
||||
|
||||
# Make it harder for us to do dangerous things in Perl.
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Keyword;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Search::Quicksearch::EXPORT = qw(quicksearch);
|
||||
|
||||
# Word renamings
|
||||
use constant MAPPINGS => {
|
||||
# Status, Resolution, Platform, OS, Priority, Severity
|
||||
"status" => "bug_status",
|
||||
"resolution" => "resolution", # no change
|
||||
"platform" => "rep_platform",
|
||||
"os" => "op_sys",
|
||||
"opsys" => "op_sys",
|
||||
"priority" => "priority", # no change
|
||||
"pri" => "priority",
|
||||
"severity" => "bug_severity",
|
||||
"sev" => "bug_severity",
|
||||
# People: AssignedTo, Reporter, QA Contact, CC, Added comment (?)
|
||||
"owner" => "assigned_to", # deprecated since bug 76507
|
||||
"assignee" => "assigned_to",
|
||||
"assignedto" => "assigned_to",
|
||||
"reporter" => "reporter", # no change
|
||||
"rep" => "reporter",
|
||||
"qa" => "qa_contact",
|
||||
"qacontact" => "qa_contact",
|
||||
"cc" => "cc", # no change
|
||||
# Product, Version, Component, Target Milestone
|
||||
"product" => "product", # no change
|
||||
"prod" => "product",
|
||||
"version" => "version", # no change
|
||||
"ver" => "version",
|
||||
"component" => "component", # no change
|
||||
"comp" => "component",
|
||||
"milestone" => "target_milestone",
|
||||
"target" => "target_milestone",
|
||||
"targetmilestone" => "target_milestone",
|
||||
# Summary, Description, URL, Status whiteboard, Keywords
|
||||
"summary" => "short_desc",
|
||||
"shortdesc" => "short_desc",
|
||||
"desc" => "longdesc",
|
||||
"description" => "longdesc",
|
||||
#"comment" => "longdesc", # ???
|
||||
# reserve "comment" for "added comment" email search?
|
||||
"longdesc" => "longdesc",
|
||||
"url" => "bug_file_loc",
|
||||
"whiteboard" => "status_whiteboard",
|
||||
"statuswhiteboard" => "status_whiteboard",
|
||||
"sw" => "status_whiteboard",
|
||||
"keywords" => "keywords", # no change
|
||||
"kw" => "keywords",
|
||||
"group" => "bug_group",
|
||||
"flag" => "flagtypes.name",
|
||||
"requestee" => "requestees.login_name",
|
||||
"req" => "requestees.login_name",
|
||||
"setter" => "setters.login_name",
|
||||
"set" => "setters.login_name",
|
||||
# Attachments
|
||||
"attachment" => "attachments.description",
|
||||
"attachmentdesc" => "attachments.description",
|
||||
"attachdesc" => "attachments.description",
|
||||
"attachmentdata" => "attach_data.thedata",
|
||||
"attachdata" => "attach_data.thedata",
|
||||
"attachmentmimetype" => "attachments.mimetype",
|
||||
"attachmimetype" => "attachments.mimetype"
|
||||
};
|
||||
|
||||
# We might want to put this into localconfig or somewhere
|
||||
use constant PLATFORMS => ('pc', 'sun', 'macintosh', 'mac');
|
||||
use constant OPSYSTEMS => ('windows', 'win', 'linux');
|
||||
use constant PRODUCT_EXCEPTIONS => (
|
||||
'row', # [Browser]
|
||||
# ^^^
|
||||
'new', # [MailNews]
|
||||
# ^^^
|
||||
);
|
||||
use constant COMPONENT_EXCEPTIONS => (
|
||||
'hang' # [Bugzilla: Component/Keyword Changes]
|
||||
# ^^^^
|
||||
);
|
||||
|
||||
# Quicksearch-wide globals for boolean charts.
|
||||
our ($chart, $and, $or);
|
||||
|
||||
sub quicksearch {
|
||||
my ($searchstring) = (@_);
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $urlbase = correct_urlbase();
|
||||
|
||||
$chart = 0;
|
||||
$and = 0;
|
||||
$or = 0;
|
||||
|
||||
# Remove leading and trailing commas and whitespace.
|
||||
$searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
|
||||
ThrowUserError('buglist_parameters_required') unless ($searchstring);
|
||||
|
||||
if ($searchstring =~ m/^[0-9,\s]*$/) {
|
||||
# Bug number(s) only.
|
||||
|
||||
# Allow separation by comma or whitespace.
|
||||
$searchstring =~ s/[,\s]+/,/g;
|
||||
|
||||
if (index($searchstring, ',') < $[) {
|
||||
# Single bug number; shortcut to show_bug.cgi.
|
||||
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$searchstring");
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
# List of bug numbers.
|
||||
$cgi->param('bug_id', $searchstring);
|
||||
$cgi->param('order', 'bugs.bug_id');
|
||||
$cgi->param('bugidtype', 'include');
|
||||
}
|
||||
}
|
||||
else {
|
||||
# It's not just a bug number or a list of bug numbers.
|
||||
# Maybe it's an alias?
|
||||
if ($searchstring =~ /^([^,\s]+)$/) {
|
||||
if (Bugzilla->dbh->selectrow_array(q{SELECT COUNT(*)
|
||||
FROM bugs
|
||||
WHERE alias = ?},
|
||||
undef,
|
||||
$1)) {
|
||||
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$1");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
# It's no alias either, so it's a more complex query.
|
||||
my $legal_statuses = get_legal_field_values('bug_status');
|
||||
my $legal_resolutions = get_legal_field_values('resolution');
|
||||
|
||||
# Globally translate " AND ", " OR ", " NOT " to space, pipe, dash.
|
||||
$searchstring =~ s/\s+AND\s+/ /g;
|
||||
$searchstring =~ s/\s+OR\s+/|/g;
|
||||
$searchstring =~ s/\s+NOT\s+/ -/g;
|
||||
|
||||
my @words = splitString($searchstring);
|
||||
my $searchComments =
|
||||
$#words < Bugzilla->params->{'quicksearch_comment_cutoff'};
|
||||
my @openStates = BUG_STATE_OPEN;
|
||||
my @closedStates;
|
||||
my @unknownFields;
|
||||
my (%states, %resolutions);
|
||||
|
||||
foreach (@$legal_statuses) {
|
||||
push(@closedStates, $_) unless is_open_state($_);
|
||||
}
|
||||
foreach (@openStates) { $states{$_} = 1 }
|
||||
if ($words[0] eq 'ALL') {
|
||||
foreach (@$legal_statuses) { $states{$_} = 1 }
|
||||
shift @words;
|
||||
}
|
||||
elsif ($words[0] eq 'OPEN') {
|
||||
shift @words;
|
||||
}
|
||||
elsif ($words[0] =~ /^\+[A-Z]+(,[A-Z]+)*$/) {
|
||||
# e.g. +DUP,FIX
|
||||
if (matchPrefixes(\%states,
|
||||
\%resolutions,
|
||||
[split(/,/, substr($words[0], 1))],
|
||||
\@closedStates,
|
||||
$legal_resolutions)) {
|
||||
shift @words;
|
||||
# Allowing additional resolutions means we need to keep
|
||||
# the "no resolution" resolution.
|
||||
$resolutions{'---'} = 1;
|
||||
}
|
||||
else {
|
||||
# Carry on if no match found.
|
||||
}
|
||||
}
|
||||
elsif ($words[0] =~ /^[A-Z]+(,[A-Z]+)*$/) {
|
||||
# e.g. NEW,ASSI,REOP,FIX
|
||||
undef %states;
|
||||
if (matchPrefixes(\%states,
|
||||
\%resolutions,
|
||||
[split(/,/, $words[0])],
|
||||
$legal_statuses,
|
||||
$legal_resolutions)) {
|
||||
shift @words;
|
||||
}
|
||||
else {
|
||||
# Carry on if no match found
|
||||
foreach (@openStates) { $states{$_} = 1 }
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Default: search for unresolved bugs only.
|
||||
# Put custom code here if you would like to change this behaviour.
|
||||
}
|
||||
|
||||
# If we have wanted resolutions, allow closed states
|
||||
if (keys(%resolutions)) {
|
||||
foreach (@closedStates) { $states{$_} = 1 }
|
||||
}
|
||||
|
||||
$cgi->param('bug_status', keys(%states));
|
||||
$cgi->param('resolution', keys(%resolutions));
|
||||
|
||||
# Loop over all main-level QuickSearch words.
|
||||
foreach my $qsword (@words) {
|
||||
my $negate = substr($qsword, 0, 1) eq '-';
|
||||
if ($negate) {
|
||||
$qsword = substr($qsword, 1);
|
||||
}
|
||||
|
||||
my $firstChar = substr($qsword, 0, 1);
|
||||
my $baseWord = substr($qsword, 1);
|
||||
my @subWords = split(/[\|,]/, $baseWord);
|
||||
if ($firstChar eq '+') {
|
||||
foreach (@subWords) {
|
||||
addChart('short_desc', 'substring', $qsword, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '#') {
|
||||
addChart('short_desc', 'anywords', $baseWord, $negate);
|
||||
if ($searchComments) {
|
||||
addChart('longdesc', 'anywords', $baseWord, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq ':') {
|
||||
foreach (@subWords) {
|
||||
addChart('product', 'substring', $_, $negate);
|
||||
addChart('component', 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '@') {
|
||||
foreach (@subWords) {
|
||||
addChart('assigned_to', 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '[') {
|
||||
addChart('short_desc', 'substring', $baseWord, $negate);
|
||||
addChart('status_whiteboard', 'substring', $baseWord, $negate);
|
||||
}
|
||||
elsif ($firstChar eq '!') {
|
||||
addChart('keywords', 'anywords', $baseWord, $negate);
|
||||
|
||||
}
|
||||
else { # No special first char
|
||||
|
||||
# Split by '|' to get all operands for a boolean OR.
|
||||
foreach my $or_operand (split(/\|/, $qsword)) {
|
||||
if ($or_operand =~ /^votes:([0-9]+)$/) {
|
||||
# votes:xx ("at least xx votes")
|
||||
addChart('votes', 'greaterthan', $1 - 1, $negate);
|
||||
}
|
||||
elsif ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
|
||||
# Flag and requestee shortcut
|
||||
addChart('flagtypes.name', 'substring', $1, $negate);
|
||||
$chart++; $and = $or = 0; # Next chart for boolean AND
|
||||
addChart('requestees.login_name', 'substring', $2, $negate);
|
||||
}
|
||||
elsif ($or_operand =~ /^([^:]+):([^:]+)$/) {
|
||||
# generic field1,field2,field3:value1,value2 notation
|
||||
my @fields = split(/,/, $1);
|
||||
my @values = split(/,/, $2);
|
||||
foreach my $field (@fields) {
|
||||
# Skip and record any unknown fields
|
||||
if (!defined(MAPPINGS->{$field})) {
|
||||
push(@unknownFields, $field);
|
||||
next;
|
||||
}
|
||||
$field = MAPPINGS->{$field};
|
||||
foreach (@values) {
|
||||
addChart($field, 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
# Having ruled out the special cases, we may now split
|
||||
# by comma, which is another legal boolean OR indicator.
|
||||
foreach my $word (split(/,/, $or_operand)) {
|
||||
# Platform and operating system
|
||||
if (grep({lc($word) eq $_} PLATFORMS)
|
||||
|| grep({lc($word) eq $_} OPSYSTEMS)) {
|
||||
addChart('rep_platform', 'substring',
|
||||
$word, $negate);
|
||||
addChart('op_sys', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
# Priority
|
||||
elsif ($word =~ m/^[pP]([1-5](-[1-5])?)$/) {
|
||||
addChart('priority', 'regexp',
|
||||
"[$1]", $negate);
|
||||
}
|
||||
# Severity
|
||||
elsif (grep({lc($word) eq substr($_, 0, 3)}
|
||||
@{get_legal_field_values('bug_severity')})) {
|
||||
addChart('bug_severity', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
# Votes (votes>xx)
|
||||
elsif ($word =~ m/^votes>([0-9]+)$/) {
|
||||
addChart('votes', 'greaterthan',
|
||||
$1, $negate);
|
||||
}
|
||||
# Votes (votes>=xx, votes=>xx)
|
||||
elsif ($word =~ m/^votes(>=|=>)([0-9]+)$/) {
|
||||
addChart('votes', 'greaterthan',
|
||||
$2-1, $negate);
|
||||
|
||||
}
|
||||
else { # Default QuickSearch word
|
||||
|
||||
if (!grep({lc($word) eq $_}
|
||||
PRODUCT_EXCEPTIONS) &&
|
||||
length($word)>2
|
||||
) {
|
||||
addChart('product', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if (!grep({lc($word) eq $_}
|
||||
COMPONENT_EXCEPTIONS) &&
|
||||
length($word)>2
|
||||
) {
|
||||
addChart('component', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if (grep({lc($word) eq $_}
|
||||
map($_->name, Bugzilla::Keyword->get_all))) {
|
||||
addChart('keywords', 'substring',
|
||||
$word, $negate);
|
||||
if (length($word)>2) {
|
||||
addChart('short_desc', 'substring',
|
||||
$word, $negate);
|
||||
addChart('status_whiteboard',
|
||||
'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
addChart('short_desc', 'substring',
|
||||
$word, $negate);
|
||||
addChart('status_whiteboard', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if ($searchComments) {
|
||||
addChart('longdesc', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
}
|
||||
# URL field (for IP addrs, host.names,
|
||||
# scheme://urls)
|
||||
if ($word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
|
||||
|| $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
|
||||
|| $word =~ /:[\\\/][\\\/]/
|
||||
|| $word =~ /localhost/
|
||||
|| $word =~ /mailto[:]?/
|
||||
# || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
|
||||
) {
|
||||
addChart('bug_file_loc', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
} # foreach my $word (split(/,/, $qsword))
|
||||
} # votes and generic field detection
|
||||
} # foreach (split(/\|/, $_))
|
||||
} # "switch" $firstChar
|
||||
$chart++;
|
||||
$and = 0;
|
||||
$or = 0;
|
||||
} # foreach (@words)
|
||||
|
||||
# Inform user about any unknown fields
|
||||
if (scalar(@unknownFields)) {
|
||||
ThrowUserError("quicksearch_unknown_field",
|
||||
{ fields => \@unknownFields });
|
||||
}
|
||||
|
||||
# Make sure we have some query terms left
|
||||
scalar($cgi->param())>0 || ThrowUserError("buglist_parameters_required");
|
||||
}
|
||||
|
||||
# List of quicksearch-specific CGI parameters to get rid of.
|
||||
my @params_to_strip = ('quicksearch', 'load', 'run');
|
||||
my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
|
||||
|
||||
if ($cgi->param('load')) {
|
||||
# Param 'load' asks us to display the query in the advanced search form.
|
||||
print $cgi->redirect(-uri => "${urlbase}query.cgi?format=advanced&"
|
||||
. $modified_query_string);
|
||||
}
|
||||
|
||||
# Otherwise, pass the modified query string to the caller.
|
||||
# We modified $cgi->params, so the caller can choose to look at that, too,
|
||||
# and disregard the return value.
|
||||
$cgi->delete(@params_to_strip);
|
||||
return $modified_query_string;
|
||||
}
|
||||
|
||||
###########################################################################
|
||||
# Helpers
|
||||
###########################################################################
|
||||
|
||||
# Split string on whitespace, retaining quoted strings as one
|
||||
sub splitString {
|
||||
my $string = shift;
|
||||
my @quoteparts;
|
||||
my @parts;
|
||||
my $i = 0;
|
||||
|
||||
# Now split on quote sign; be tolerant about unclosed quotes
|
||||
@quoteparts = split(/"/, $string);
|
||||
foreach my $part (@quoteparts) {
|
||||
# After every odd quote, quote special chars
|
||||
$part = url_quote($part) if $i++ % 2;
|
||||
}
|
||||
# Join again
|
||||
$string = join('"', @quoteparts);
|
||||
|
||||
# Now split on unescaped whitespace
|
||||
@parts = split(/\s+/, $string);
|
||||
foreach (@parts) {
|
||||
# Protect plus signs from becoming a blank.
|
||||
# If "+" appears as the first character, leave it alone
|
||||
# as it has a special meaning. Strings which start with
|
||||
# "+" must be quoted.
|
||||
s/(?<!^)\+/%2B/g;
|
||||
# Remove quotes
|
||||
s/"//g;
|
||||
}
|
||||
return @parts;
|
||||
}
|
||||
|
||||
# Expand found prefixes to states or resolutions
|
||||
sub matchPrefixes {
|
||||
my $hr_states = shift;
|
||||
my $hr_resolutions = shift;
|
||||
my $ar_prefixes = shift;
|
||||
my $ar_check_states = shift;
|
||||
my $ar_check_resolutions = shift;
|
||||
my $foundMatch = 0;
|
||||
|
||||
foreach my $prefix (@$ar_prefixes) {
|
||||
foreach (@$ar_check_states) {
|
||||
if (/^$prefix/) {
|
||||
$$hr_states{$_} = 1;
|
||||
$foundMatch = 1;
|
||||
}
|
||||
}
|
||||
foreach (@$ar_check_resolutions) {
|
||||
if (/^$prefix/) {
|
||||
$$hr_resolutions{$_} = 1;
|
||||
$foundMatch = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $foundMatch;
|
||||
}
|
||||
|
||||
# Negate comparison type
|
||||
sub negateComparisonType {
|
||||
my $comparisonType = shift;
|
||||
|
||||
if ($comparisonType eq 'substring') {
|
||||
return 'notsubstring';
|
||||
}
|
||||
elsif ($comparisonType eq 'anywords') {
|
||||
return 'nowords';
|
||||
}
|
||||
elsif ($comparisonType eq 'regexp') {
|
||||
return 'notregexp';
|
||||
}
|
||||
else {
|
||||
# Don't know how to negate that
|
||||
ThrowCodeError('unknown_comparison_type');
|
||||
}
|
||||
}
|
||||
|
||||
# Add a boolean chart
|
||||
sub addChart {
|
||||
my ($field, $comparisonType, $value, $negate) = @_;
|
||||
|
||||
$negate && ($comparisonType = negateComparisonType($comparisonType));
|
||||
makeChart("$chart-$and-$or", $field, $comparisonType, $value);
|
||||
if ($negate) {
|
||||
$and++;
|
||||
$or = 0;
|
||||
}
|
||||
else {
|
||||
$or++;
|
||||
}
|
||||
}
|
||||
|
||||
# Create the CGI parameters for a boolean chart
|
||||
sub makeChart {
|
||||
my ($expr, $field, $type, $value) = @_;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->param("field$expr", $field);
|
||||
$cgi->param("type$expr", $type);
|
||||
$cgi->param("value$expr", url_decode($value));
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,314 +0,0 @@
|
||||
# -*- 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>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Search::Saved;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::CGI;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Search qw(IsValidQueryType);
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
#############
|
||||
# Constants #
|
||||
#############
|
||||
|
||||
use constant DB_TABLE => 'namedqueries';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
userid
|
||||
name
|
||||
query
|
||||
query_type
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name query);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
query => \&_check_query,
|
||||
query_type => \&_check_query_type,
|
||||
link_in_footer => \&_check_link_in_footer,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(name query query_type);
|
||||
|
||||
##############
|
||||
# Validators #
|
||||
##############
|
||||
|
||||
sub _check_link_in_footer { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name) = @_;
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError("query_name_missing");
|
||||
$name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
|
||||
if (length($name) > MAX_LEN_QUERY_NAME) {
|
||||
ThrowUserError("query_name_too_long");
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_query {
|
||||
my ($invocant, $query) = @_;
|
||||
$query || ThrowUserError("buglist_parameters_required");
|
||||
my $cgi = new Bugzilla::CGI($query);
|
||||
$cgi->clean_search_url;
|
||||
# Don't store the query name as a parameter.
|
||||
$cgi->delete('known_name');
|
||||
return $cgi->query_string;
|
||||
}
|
||||
|
||||
sub _check_query_type {
|
||||
my ($invocant, $type) = @_;
|
||||
# Right now the only query type is LIST_OF_BUGS.
|
||||
return $type ? LIST_OF_BUGS : QUERY_LIST;
|
||||
}
|
||||
|
||||
#########################
|
||||
# Database Manipulation #
|
||||
#########################
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$class->check_required_create_fields(@_);
|
||||
$dbh->bz_start_transaction();
|
||||
my $params = $class->run_create_validators(@_);
|
||||
|
||||
# Right now you can only create a Saved Search for the current user.
|
||||
$params->{userid} = Bugzilla->user->id;
|
||||
|
||||
my $lif = delete $params->{link_in_footer};
|
||||
my $obj = $class->insert_create_data($params);
|
||||
if ($lif) {
|
||||
$dbh->do('INSERT INTO namedqueries_link_in_footer
|
||||
(user_id, namedquery_id) VALUES (?,?)',
|
||||
undef, $params->{userid}, $obj->id);
|
||||
}
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub preload {
|
||||
my ($searches) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless scalar @$searches;
|
||||
|
||||
my @query_ids = map { $_->id } @$searches;
|
||||
my $queries_in_footer = $dbh->selectcol_arrayref(
|
||||
'SELECT namedquery_id
|
||||
FROM namedqueries_link_in_footer
|
||||
WHERE ' . $dbh->sql_in('namedquery_id', \@query_ids) . ' AND user_id = ?',
|
||||
undef, Bugzilla->user->id);
|
||||
|
||||
my %links_in_footer = map { $_ => 1 } @$queries_in_footer;
|
||||
foreach my $query (@$searches) {
|
||||
$query->{link_in_footer} = ($links_in_footer{$query->id}) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
#####################
|
||||
# Complex Accessors #
|
||||
#####################
|
||||
|
||||
sub edit_link {
|
||||
my ($self) = @_;
|
||||
return $self->{edit_link} if defined $self->{edit_link};
|
||||
my $cgi = new Bugzilla::CGI($self->url);
|
||||
if (!$cgi->param('query_type')
|
||||
|| !IsValidQueryType($cgi->param('query_type')))
|
||||
{
|
||||
$cgi->param('query_type', 'advanced');
|
||||
}
|
||||
$self->{edit_link} = $cgi->canonicalise_query;
|
||||
return $self->{edit_link};
|
||||
}
|
||||
|
||||
sub used_in_whine {
|
||||
my ($self) = @_;
|
||||
return $self->{used_in_whine} if exists $self->{used_in_whine};
|
||||
($self->{used_in_whine}) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT 1 FROM whine_events INNER JOIN whine_queries
|
||||
ON whine_events.id = whine_queries.eventid
|
||||
WHERE whine_events.owner_userid = ? AND query_name = ?', undef,
|
||||
$self->{userid}, $self->name) || 0;
|
||||
return $self->{used_in_whine};
|
||||
}
|
||||
|
||||
sub link_in_footer {
|
||||
my ($self, $user) = @_;
|
||||
# We only cache link_in_footer for the current Bugzilla->user.
|
||||
return $self->{link_in_footer} if exists $self->{link_in_footer} && !$user;
|
||||
my $user_id = $user ? $user->id : Bugzilla->user->id;
|
||||
my $link_in_footer = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT 1 FROM namedqueries_link_in_footer
|
||||
WHERE namedquery_id = ? AND user_id = ?',
|
||||
undef, $self->id, $user_id) || 0;
|
||||
$self->{link_in_footer} = $link_in_footer if !$user;
|
||||
return $link_in_footer;
|
||||
}
|
||||
|
||||
sub shared_with_group {
|
||||
my ($self) = @_;
|
||||
return $self->{shared_with_group} if exists $self->{shared_with_group};
|
||||
# Bugzilla only currently supports sharing with one group, even
|
||||
# though the database backend allows for an infinite number.
|
||||
my ($group_id) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT group_id FROM namedquery_group_map WHERE namedquery_id = ?',
|
||||
undef, $self->id);
|
||||
$self->{shared_with_group} = $group_id ? new Bugzilla::Group($group_id)
|
||||
: undef;
|
||||
return $self->{shared_with_group};
|
||||
}
|
||||
|
||||
sub shared_with_users {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!exists $self->{shared_with_users}) {
|
||||
$self->{shared_with_users} =
|
||||
$dbh->selectrow_array('SELECT COUNT(*)
|
||||
FROM namedqueries_link_in_footer
|
||||
INNER JOIN namedqueries
|
||||
ON namedquery_id = id
|
||||
WHERE namedquery_id = ?
|
||||
AND user_id != userid',
|
||||
undef, $self->id);
|
||||
}
|
||||
return $self->{shared_with_users};
|
||||
}
|
||||
|
||||
####################
|
||||
# Simple Accessors #
|
||||
####################
|
||||
|
||||
sub bug_ids_only { return ($_[0]->{'query_type'} == LIST_OF_BUGS) ? 1 : 0; }
|
||||
sub url { return $_[0]->{'query'}; }
|
||||
|
||||
sub user {
|
||||
my ($self) = @_;
|
||||
return $self->{user} if defined $self->{user};
|
||||
$self->{user} = new Bugzilla::User($self->{userid});
|
||||
return $self->{user};
|
||||
}
|
||||
|
||||
############
|
||||
# Mutators #
|
||||
############
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_url { $_[0]->set('query', $_[1]); }
|
||||
sub set_query_type { $_[0]->set('query_type', $_[1]); }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Search::Saved - A saved search
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Search::Saved;
|
||||
|
||||
my $query = new Bugzilla::Search::Saved($query_id);
|
||||
|
||||
my $edit_link = $query->edit_link;
|
||||
my $search_url = $query->url;
|
||||
my $owner = $query->user;
|
||||
my $num_subscribers = $query->shared_with_users;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module exists to represent a L<Bugzilla::Search> that has been
|
||||
saved to the database.
|
||||
|
||||
This is an implementation of L<Bugzilla::Object>, and so has all the
|
||||
same methods available as L<Bugzilla::Object>, in addition to what is
|
||||
documented below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Constructors and Database Manipulation
|
||||
|
||||
=over
|
||||
|
||||
=item C<new>
|
||||
|
||||
Does not accept a bare C<name> argument. Instead, accepts only an id.
|
||||
|
||||
See also: L<Bugzilla::Object/new>.
|
||||
|
||||
=item C<preload>
|
||||
|
||||
Sets C<link_in_footer> for all given saved searches at once, for the
|
||||
currently logged in user. This is much faster than calling this method
|
||||
for each saved search individually.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Accessors
|
||||
|
||||
These return data about the object, without modifying the object.
|
||||
|
||||
=over
|
||||
|
||||
=item C<edit_link>
|
||||
|
||||
A url with which you can edit the search.
|
||||
|
||||
=item C<url>
|
||||
|
||||
The CGI parameters for the search, as a string.
|
||||
|
||||
=item C<link_in_footer>
|
||||
|
||||
Whether or not this search should be displayed in the footer for the
|
||||
I<current user> (not the owner of the search, but the person actually
|
||||
using Bugzilla right now).
|
||||
|
||||
=item C<bug_ids_only>
|
||||
|
||||
True if the search contains only a list of Bug IDs.
|
||||
|
||||
=item C<shared_with_group>
|
||||
|
||||
The L<Bugzilla::Group> that this search is shared with. C<undef> if
|
||||
this search isn't shared.
|
||||
|
||||
=item C<shared_with_users>
|
||||
|
||||
Returns how many users (besides the author of the saved search) are
|
||||
using the saved search, i.e. have it displayed in their footer.
|
||||
|
||||
=back
|
||||
@@ -1,261 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
use strict;
|
||||
|
||||
# This module implements a series - a set of data to be plotted on a chart.
|
||||
#
|
||||
# This Series is in the database if and only if self->{'series_id'} is defined.
|
||||
# Note that the series being in the database does not mean that the fields of
|
||||
# this object are the same as the DB entries, as the object may have been
|
||||
# altered.
|
||||
|
||||
package Bugzilla::Series;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
|
||||
my $arg_count = scalar(@_);
|
||||
|
||||
# new() can return undef if you pass in a series_id and the user doesn't
|
||||
# have sufficient permissions. If you create a new series in this way,
|
||||
# you need to check for an undef return, and act appropriately.
|
||||
my $retval = $self;
|
||||
|
||||
# There are three ways of creating Series objects. Two (CGI and Parameters)
|
||||
# are for use when creating a new series. One (Database) is for retrieving
|
||||
# information on existing series.
|
||||
if ($arg_count == 1) {
|
||||
if (ref($_[0])) {
|
||||
# We've been given a CGI object to create a new Series from.
|
||||
# This series may already exist - external code needs to check
|
||||
# before it calls writeToDatabase().
|
||||
$self->initFromCGI($_[0]);
|
||||
}
|
||||
else {
|
||||
# We've been given a series_id, which should represent an existing
|
||||
# Series.
|
||||
$retval = $self->initFromDatabase($_[0]);
|
||||
}
|
||||
}
|
||||
elsif ($arg_count >= 6 && $arg_count <= 8) {
|
||||
# We've been given a load of parameters to create a new Series from.
|
||||
# Currently, undef is always passed as the first parameter; this allows
|
||||
# you to call writeToDatabase() unconditionally.
|
||||
$self->initFromParameters(@_);
|
||||
}
|
||||
else {
|
||||
die("Bad parameters passed in - invalid number of args: $arg_count");
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
sub initFromDatabase {
|
||||
my $self = shift;
|
||||
my $series_id = shift;
|
||||
|
||||
detaint_natural($series_id)
|
||||
|| ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
|
||||
"cc2.name, series.name, series.creator, series.frequency, " .
|
||||
"series.query, series.is_public " .
|
||||
"FROM series " .
|
||||
"LEFT JOIN series_categories AS cc1 " .
|
||||
" ON series.category = cc1.id " .
|
||||
"LEFT JOIN series_categories AS cc2 " .
|
||||
" ON series.subcategory = cc2.id " .
|
||||
"LEFT JOIN category_group_map AS cgm " .
|
||||
" ON series.category = cgm.category_id " .
|
||||
"LEFT JOIN user_group_map AS ugm " .
|
||||
" ON cgm.group_id = ugm.group_id " .
|
||||
" AND ugm.user_id = " . Bugzilla->user->id .
|
||||
" AND isbless = 0 " .
|
||||
"WHERE series.series_id = $series_id AND " .
|
||||
"(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
|
||||
"(ugm.group_id IS NOT NULL)) " .
|
||||
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name, series.creator, series.frequency, ' .
|
||||
'series.query, series.is_public'));
|
||||
|
||||
if (@series) {
|
||||
$self->initFromParameters(@series);
|
||||
return $self;
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub initFromParameters {
|
||||
# Pass undef as the first parameter if you are creating a new series.
|
||||
my $self = shift;
|
||||
|
||||
($self->{'series_id'}, $self->{'category'}, $self->{'subcategory'},
|
||||
$self->{'name'}, $self->{'creator'}, $self->{'frequency'},
|
||||
$self->{'query'}, $self->{'public'}) = @_;
|
||||
|
||||
# If the first parameter is undefined, check if this series already
|
||||
# exists and update it series_id accordingly
|
||||
$self->{'series_id'} ||= $self->existsInDatabase();
|
||||
}
|
||||
|
||||
sub initFromCGI {
|
||||
my $self = shift;
|
||||
my $cgi = shift;
|
||||
|
||||
$self->{'series_id'} = $cgi->param('series_id') || undef;
|
||||
if (defined($self->{'series_id'})) {
|
||||
detaint_natural($self->{'series_id'})
|
||||
|| ThrowCodeError("invalid_series_id",
|
||||
{ 'series_id' => $self->{'series_id'} });
|
||||
}
|
||||
|
||||
$self->{'category'} = $cgi->param('category')
|
||||
|| $cgi->param('newcategory')
|
||||
|| ThrowUserError("missing_category");
|
||||
|
||||
$self->{'subcategory'} = $cgi->param('subcategory')
|
||||
|| $cgi->param('newsubcategory')
|
||||
|| ThrowUserError("missing_subcategory");
|
||||
|
||||
$self->{'name'} = $cgi->param('name')
|
||||
|| ThrowUserError("missing_name");
|
||||
|
||||
$self->{'creator'} = Bugzilla->user->id;
|
||||
|
||||
$self->{'frequency'} = $cgi->param('frequency');
|
||||
detaint_natural($self->{'frequency'})
|
||||
|| ThrowUserError("missing_frequency");
|
||||
|
||||
$self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
|
||||
"category", "subcategory", "name",
|
||||
"frequency", "public", "query_format");
|
||||
trick_taint($self->{'query'});
|
||||
|
||||
$self->{'public'} = $cgi->param('public') ? 1 : 0;
|
||||
|
||||
# Change 'admin' here and in series.html.tmpl, or remove the check
|
||||
# completely, if you want to change who can make series public.
|
||||
$self->{'public'} = 0 unless Bugzilla->user->in_group('admin');
|
||||
}
|
||||
|
||||
sub writeToDatabase {
|
||||
my $self = shift;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $category_id = getCategoryID($self->{'category'});
|
||||
my $subcategory_id = getCategoryID($self->{'subcategory'});
|
||||
|
||||
my $exists;
|
||||
if ($self->{'series_id'}) {
|
||||
$exists =
|
||||
$dbh->selectrow_array("SELECT series_id FROM series
|
||||
WHERE series_id = $self->{'series_id'}");
|
||||
}
|
||||
|
||||
# Is this already in the database?
|
||||
if ($exists) {
|
||||
# Update existing series
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do("UPDATE series SET " .
|
||||
"category = ?, subcategory = ?," .
|
||||
"name = ?, frequency = ?, is_public = ? " .
|
||||
"WHERE series_id = ?", undef,
|
||||
$category_id, $subcategory_id, $self->{'name'},
|
||||
$self->{'frequency'}, $self->{'public'},
|
||||
$self->{'series_id'});
|
||||
}
|
||||
else {
|
||||
# Insert the new series into the series table
|
||||
$dbh->do("INSERT INTO series (creator, category, subcategory, " .
|
||||
"name, frequency, query, is_public) VALUES " .
|
||||
"(?, ?, ?, ?, ?, ?, ?)", undef,
|
||||
$self->{'creator'}, $category_id, $subcategory_id, $self->{'name'},
|
||||
$self->{'frequency'}, $self->{'query'}, $self->{'public'});
|
||||
|
||||
# Retrieve series_id
|
||||
$self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
|
||||
"FROM series");
|
||||
$self->{'series_id'}
|
||||
|| ThrowCodeError("missing_series_id", { 'series' => $self });
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
# Check whether a series with this name, category and subcategory exists in
|
||||
# the DB and, if so, returns its series_id.
|
||||
sub existsInDatabase {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $category_id = getCategoryID($self->{'category'});
|
||||
my $subcategory_id = getCategoryID($self->{'subcategory'});
|
||||
|
||||
trick_taint($self->{'name'});
|
||||
my $series_id = $dbh->selectrow_array("SELECT series_id " .
|
||||
"FROM series WHERE category = $category_id " .
|
||||
"AND subcategory = $subcategory_id AND name = " .
|
||||
$dbh->quote($self->{'name'}));
|
||||
|
||||
return($series_id);
|
||||
}
|
||||
|
||||
# Get a category or subcategory IDs, creating the category if it doesn't exist.
|
||||
sub getCategoryID {
|
||||
my ($category) = @_;
|
||||
my $category_id;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# This seems for the best idiom for "Do A. Then maybe do B and A again."
|
||||
while (1) {
|
||||
# We are quoting this to put it in the DB, so we can remove taint
|
||||
trick_taint($category);
|
||||
|
||||
$category_id = $dbh->selectrow_array("SELECT id " .
|
||||
"from series_categories " .
|
||||
"WHERE name =" . $dbh->quote($category));
|
||||
|
||||
last if defined($category_id);
|
||||
|
||||
$dbh->do("INSERT INTO series_categories (name) " .
|
||||
"VALUES (" . $dbh->quote($category) . ")");
|
||||
}
|
||||
|
||||
return $category_id;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,278 +0,0 @@
|
||||
# -*- 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 Frédéric Buclin.
|
||||
# Portions created by Frédéric Buclin are Copyright (C) 2007
|
||||
# Frédéric Buclin. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Status;
|
||||
|
||||
use base qw(Bugzilla::Object Exporter);
|
||||
@Bugzilla::Status::EXPORT = qw(BUG_STATE_OPEN is_open_state closed_bug_statuses);
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DB_TABLE => 'bug_status';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
sortkey
|
||||
isactive
|
||||
is_open
|
||||
);
|
||||
|
||||
use constant NAME_FIELD => 'value';
|
||||
use constant LIST_ORDER => 'sortkey, value';
|
||||
|
||||
###############################
|
||||
##### Accessors ####
|
||||
###############################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
sub is_active { return $_[0]->{'isactive'}; }
|
||||
sub is_open { return $_[0]->{'is_open'}; }
|
||||
|
||||
###############################
|
||||
##### Methods ####
|
||||
###############################
|
||||
|
||||
sub BUG_STATE_OPEN {
|
||||
# XXX - We should cache this list.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
return @{$dbh->selectcol_arrayref('SELECT value FROM bug_status WHERE is_open = 1')};
|
||||
}
|
||||
|
||||
# Tells you whether or not the argument is a valid "open" state.
|
||||
sub is_open_state {
|
||||
my ($state) = @_;
|
||||
return (grep($_ eq $state, BUG_STATE_OPEN) ? 1 : 0);
|
||||
}
|
||||
|
||||
sub closed_bug_statuses {
|
||||
my @bug_statuses = Bugzilla::Status->get_all;
|
||||
@bug_statuses = grep { !$_->is_open } @bug_statuses;
|
||||
return @bug_statuses;
|
||||
}
|
||||
|
||||
sub can_change_to {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!ref($self) || !defined $self->{'can_change_to'}) {
|
||||
my ($cond, @args, $self_exists);
|
||||
if (ref($self)) {
|
||||
$cond = '= ?';
|
||||
push(@args, $self->id);
|
||||
$self_exists = 1;
|
||||
}
|
||||
else {
|
||||
$cond = 'IS NULL';
|
||||
# Let's do it so that the code below works in all cases.
|
||||
$self = {};
|
||||
}
|
||||
|
||||
my $new_status_ids = $dbh->selectcol_arrayref("SELECT new_status
|
||||
FROM status_workflow
|
||||
INNER JOIN bug_status
|
||||
ON id = new_status
|
||||
WHERE isactive = 1
|
||||
AND old_status $cond
|
||||
ORDER BY sortkey",
|
||||
undef, @args);
|
||||
|
||||
# Allow the bug status to remain unchanged.
|
||||
push(@$new_status_ids, $self->id) if $self_exists;
|
||||
$self->{'can_change_to'} = Bugzilla::Status->new_from_list($new_status_ids);
|
||||
}
|
||||
|
||||
return $self->{'can_change_to'};
|
||||
}
|
||||
|
||||
sub can_change_from {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'can_change_from'}) {
|
||||
my $old_status_ids = $dbh->selectcol_arrayref('SELECT old_status
|
||||
FROM status_workflow
|
||||
INNER JOIN bug_status
|
||||
ON id = old_status
|
||||
WHERE isactive = 1
|
||||
AND new_status = ?
|
||||
AND old_status IS NOT NULL',
|
||||
undef, $self->id);
|
||||
|
||||
# Allow the bug status to remain unchanged.
|
||||
push(@$old_status_ids, $self->id);
|
||||
$self->{'can_change_from'} = Bugzilla::Status->new_from_list($old_status_ids);
|
||||
}
|
||||
|
||||
return $self->{'can_change_from'};
|
||||
}
|
||||
|
||||
sub comment_required_on_change_from {
|
||||
my ($self, $old_status) = @_;
|
||||
my ($cond, $values) = $self->_status_condition($old_status);
|
||||
|
||||
my ($require_comment) = Bugzilla->dbh->selectrow_array(
|
||||
"SELECT require_comment FROM status_workflow
|
||||
WHERE $cond", undef, @$values);
|
||||
return $require_comment;
|
||||
}
|
||||
|
||||
# Used as a helper for various functions that have to deal with old_status
|
||||
# sometimes being NULL and sometimes having a value.
|
||||
sub _status_condition {
|
||||
my ($self, $old_status) = @_;
|
||||
my @values;
|
||||
my $cond = 'old_status IS NULL';
|
||||
# For newly-filed bugs
|
||||
if ($old_status) {
|
||||
$cond = 'old_status = ?';
|
||||
push(@values, $old_status->id);
|
||||
}
|
||||
$cond .= " AND new_status = ?";
|
||||
push(@values, $self->id);
|
||||
return ($cond, \@values);
|
||||
}
|
||||
|
||||
sub add_missing_bug_status_transitions {
|
||||
my $bug_status = shift || Bugzilla->params->{'duplicate_or_move_bug_status'};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $new_status = new Bugzilla::Status({name => $bug_status});
|
||||
# Silently discard invalid bug statuses.
|
||||
$new_status || return;
|
||||
|
||||
my $missing_statuses = $dbh->selectcol_arrayref('SELECT id
|
||||
FROM bug_status
|
||||
LEFT JOIN status_workflow
|
||||
ON old_status = id
|
||||
AND new_status = ?
|
||||
WHERE old_status IS NULL',
|
||||
undef, $new_status->id);
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO status_workflow
|
||||
(old_status, new_status) VALUES (?, ?)');
|
||||
|
||||
foreach my $old_status_id (@$missing_statuses) {
|
||||
next if ($old_status_id == $new_status->id);
|
||||
$sth->execute($old_status_id, $new_status->id);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Status - Bug status class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Status;
|
||||
|
||||
my $bug_status = new Bugzilla::Status({name => 'ASSIGNED'});
|
||||
my $bug_status = new Bugzilla::Status(4);
|
||||
|
||||
my @closed_bug_statuses = closed_bug_statuses();
|
||||
|
||||
Bugzilla::Status::add_missing_bug_status_transitions($bug_status);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Status.pm represents a bug status object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus provides all methods that
|
||||
L<Bugzilla::Object> provides.
|
||||
|
||||
The methods that are specific to C<Bugzilla::Status> are listed
|
||||
below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<closed_bug_statuses>
|
||||
|
||||
Description: Returns a list of C<Bugzilla::Status> objects which can have
|
||||
a resolution associated with them ("closed" bug statuses).
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<can_change_to>
|
||||
|
||||
Description: Returns the list of active statuses a bug can be changed to
|
||||
given the current bug status. If this method is called as a
|
||||
class method, then it returns all bug statuses available on
|
||||
bug creation.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<can_change_from>
|
||||
|
||||
Description: Returns the list of active statuses a bug can be changed from
|
||||
given the new bug status. If the bug status is available on
|
||||
bug creation, this method doesn't return this information.
|
||||
You have to call C<can_change_to> instead.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<comment_required_on_change_from>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Checks if a comment is required to change to this status from another
|
||||
status, according to the current settings in the workflow.
|
||||
|
||||
Note that this doesn't implement the checks enforced by the various
|
||||
C<commenton> parameters--those are checked by internal checks in
|
||||
L<Bugzilla::Bug>.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
C<$old_status> - The status you're changing from.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
C<1> if a comment is required on this change, C<0> if not.
|
||||
|
||||
=back
|
||||
|
||||
=item C<add_missing_bug_status_transitions>
|
||||
|
||||
Description: Insert all missing transitions to a given bug status.
|
||||
|
||||
Params: $bug_status - The value (name) of a bug status.
|
||||
|
||||
Returns: nothing.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,913 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Tobias Burnus <burnus@net-b.de>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Greg Hendricks <ghendricks@novell.com>
|
||||
# David D. Kilzer <ddkilzer@kilzer.net>
|
||||
|
||||
|
||||
package Bugzilla::Template;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Requirements;
|
||||
use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Template::Parser;
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use MIME::Base64;
|
||||
# for time2str - replace by TT Date plugin??
|
||||
use Date::Format ();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Find;
|
||||
use File::Path qw(rmtree mkpath);
|
||||
use File::Spec;
|
||||
use IO::Dir;
|
||||
|
||||
use base qw(Template);
|
||||
|
||||
# As per the Template::Base documentation, the _init() method is being called
|
||||
# by the new() constructor. We take advantage of this in order to plug our
|
||||
# UTF-8-aware Parser object in neatly after the original _init() method has
|
||||
# happened, in particular, after having set up the constants namespace.
|
||||
# See bug 413121 for details.
|
||||
sub _init {
|
||||
my $self = shift;
|
||||
my $config = $_[0];
|
||||
|
||||
$self->SUPER::_init(@_) || return undef;
|
||||
|
||||
$self->{PARSER} = $config->{PARSER}
|
||||
= new Bugzilla::Template::Parser($config);
|
||||
|
||||
# Now we need to re-create the default Service object, making it aware
|
||||
# of our Parser object.
|
||||
$self->{SERVICE} = $config->{SERVICE}
|
||||
= Template::Config->service($config);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# Convert the constants in the Bugzilla::Constants module into a hash we can
|
||||
# pass to the template object for reflection into its "constants" namespace
|
||||
# (which is like its "variables" namespace, but for constants). To do so, we
|
||||
# traverse the arrays of exported and exportable symbols and ignoring the rest
|
||||
# (which, if Constants.pm exports only constants, as it should, will be nothing else).
|
||||
sub _load_constants {
|
||||
my %constants;
|
||||
foreach my $constant (@Bugzilla::Constants::EXPORT,
|
||||
@Bugzilla::Constants::EXPORT_OK)
|
||||
{
|
||||
if (ref Bugzilla::Constants->$constant) {
|
||||
$constants{$constant} = Bugzilla::Constants->$constant;
|
||||
}
|
||||
else {
|
||||
my @list = (Bugzilla::Constants->$constant);
|
||||
$constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
|
||||
}
|
||||
}
|
||||
return \%constants;
|
||||
}
|
||||
|
||||
# Returns the path to the templates based on the Accept-Language
|
||||
# settings of the user and of the available languages
|
||||
# If no Accept-Language is present it uses the defined default
|
||||
# Templates may also be found in the extensions/ tree
|
||||
sub getTemplateIncludePath {
|
||||
my $cache = Bugzilla->request_cache;
|
||||
my $lang = $cache->{'language'} || '';
|
||||
$cache->{"template_include_path_$lang"} ||= template_include_path({
|
||||
use_languages => Bugzilla->languages,
|
||||
only_language => $lang });
|
||||
return $cache->{"template_include_path_$lang"};
|
||||
}
|
||||
|
||||
sub get_format {
|
||||
my $self = shift;
|
||||
my ($template, $format, $ctype) = @_;
|
||||
|
||||
$ctype ||= 'html';
|
||||
$format ||= '';
|
||||
|
||||
# Security - allow letters and a hyphen only
|
||||
$ctype =~ s/[^a-zA-Z\-]//g;
|
||||
$format =~ s/[^a-zA-Z\-]//g;
|
||||
trick_taint($ctype);
|
||||
trick_taint($format);
|
||||
|
||||
$template .= ($format ? "-$format" : "");
|
||||
$template .= ".$ctype.tmpl";
|
||||
|
||||
# Now check that the template actually exists. We only want to check
|
||||
# if the template exists; any other errors (eg parse errors) will
|
||||
# end up being detected later.
|
||||
eval {
|
||||
$self->context->template($template);
|
||||
};
|
||||
# This parsing may seem fragile, but it's OK:
|
||||
# http://lists.template-toolkit.org/pipermail/templates/2003-March/004370.html
|
||||
# Even if it is wrong, any sort of error is going to cause a failure
|
||||
# eventually, so the only issue would be an incorrect error message
|
||||
if ($@ && $@->info =~ /: not found$/) {
|
||||
ThrowUserError('format_not_found', {'format' => $format,
|
||||
'ctype' => $ctype});
|
||||
}
|
||||
|
||||
# Else, just return the info
|
||||
return
|
||||
{
|
||||
'template' => $template,
|
||||
'extension' => $ctype,
|
||||
'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
|
||||
};
|
||||
}
|
||||
|
||||
# This routine quoteUrls contains inspirations from the HTML::FromText CPAN
|
||||
# module by Gareth Rees <garethr@cre.canon.co.uk>. It has been heavily hacked,
|
||||
# all that is really recognizable from the original is bits of the regular
|
||||
# expressions.
|
||||
# This has been rewritten to be faster, mainly by substituting 'as we go'.
|
||||
# If you want to modify this routine, read the comments carefully
|
||||
|
||||
sub quoteUrls {
|
||||
my ($text, $curr_bugid) = (@_);
|
||||
return $text unless $text;
|
||||
|
||||
# We use /g for speed, but uris can have other things inside them
|
||||
# (http://foo/bug#3 for example). Filtering that out filters valid
|
||||
# bug refs out, so we have to do replacements.
|
||||
# mailto can't contain space or #, so we don't have to bother for that
|
||||
# Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
|
||||
# \0 is used because it's unlikely to occur in the text, so the cost of
|
||||
# doing this should be very small
|
||||
|
||||
# escape the 2nd escape char we're using
|
||||
my $chr1 = chr(1);
|
||||
$text =~ s/\0/$chr1\0/g;
|
||||
|
||||
# However, note that adding the title (for buglinks) can affect things
|
||||
# In particular, attachment matches go before bug titles, so that titles
|
||||
# with 'attachment 1' don't double match.
|
||||
# Dupe checks go afterwards, because that uses ^ and \Z, which won't occur
|
||||
# if it was substituted as a bug title (since that always involve leading
|
||||
# and trailing text)
|
||||
|
||||
# Because of entities, it's easier (and quicker) to do this before escaping
|
||||
|
||||
my @things;
|
||||
my $count = 0;
|
||||
my $tmp;
|
||||
|
||||
# Provide tooltips for full bug links (Bug 74355)
|
||||
my $urlbase_re = '(' . join('|',
|
||||
map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
|
||||
Bugzilla->params->{'sslbase'})) . ')';
|
||||
$text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
|
||||
~($things[$count++] = get_bug_link($3, $1, $5)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egox;
|
||||
|
||||
# non-mailto protocols
|
||||
my $safe_protocols = join('|', SAFE_PROTOCOLS);
|
||||
my $protocol_re = qr/($safe_protocols)/i;
|
||||
|
||||
$text =~ s~\b(${protocol_re}: # The protocol:
|
||||
[^\s<>\"]+ # Any non-whitespace
|
||||
[\w\/]) # so that we end in \w or /
|
||||
~($tmp = html_quote($1)) &&
|
||||
($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egox;
|
||||
|
||||
# We have to quote now, otherwise the html itself is escaped
|
||||
# THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
|
||||
|
||||
$text = html_quote($text);
|
||||
|
||||
# Color quoted text
|
||||
$text =~ s~^(>.+)$~<span class="quote">$1</span >~mg;
|
||||
$text =~ s~</span >\n<span class="quote">~\n~g;
|
||||
|
||||
# mailto:
|
||||
# Use |<nothing> so that $1 is defined regardless
|
||||
$text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
|
||||
~<a href=\"mailto:$2\">$1$2</a>~igx;
|
||||
|
||||
# attachment links - handle both cases separately for simplicity
|
||||
$text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
|
||||
~($things[$count++] = get_attachment_link($2, $1)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egmx;
|
||||
|
||||
$text =~ s~\b(attachment\s*\#?\s*(\d+))
|
||||
~($things[$count++] = get_attachment_link($2, $1)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egmxi;
|
||||
|
||||
# Current bug ID this comment belongs to
|
||||
my $current_bugurl = $curr_bugid ? "show_bug.cgi?id=$curr_bugid" : "";
|
||||
|
||||
# This handles bug a, comment b type stuff. Because we're using /g
|
||||
# we have to do this in one pattern, and so this is semi-messy.
|
||||
# Also, we can't use $bug_re?$comment_re? because that will match the
|
||||
# empty string
|
||||
my $bug_word = get_text('term', { term => 'bug' });
|
||||
my $bug_re = qr/\Q$bug_word\E\s*\#?\s*(\d+)/i;
|
||||
my $comment_re = qr/comment\s*\#?\s*(\d+)/i;
|
||||
$text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
|
||||
~ # We have several choices. $1 here is the link, and $2-4 are set
|
||||
# depending on which part matched
|
||||
(defined($2) ? get_bug_link($2,$1,$3) :
|
||||
"<a href=\"$current_bugurl#c$4\">$1</a>")
|
||||
~egox;
|
||||
|
||||
# Old duplicate markers. These don't use $bug_word because they are old
|
||||
# and were never customizable.
|
||||
$text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
|
||||
(\d+)
|
||||
(?=\ \*\*\*\Z)
|
||||
~get_bug_link($1, $1)
|
||||
~egmx;
|
||||
|
||||
# Now remove the encoding hacks
|
||||
$text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
|
||||
$text =~ s/$chr1\0/\0/g;
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
# Creates a link to an attachment, including its title.
|
||||
sub get_attachment_link {
|
||||
my ($attachid, $link_text) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($attachid)
|
||||
|| die "get_attachment_link() called with non-integer attachment number";
|
||||
|
||||
my ($bugid, $isobsolete, $desc) =
|
||||
$dbh->selectrow_array('SELECT bug_id, isobsolete, description
|
||||
FROM attachments WHERE attach_id = ?',
|
||||
undef, $attachid);
|
||||
|
||||
if ($bugid) {
|
||||
my $title = "";
|
||||
my $className = "";
|
||||
if (Bugzilla->user->can_see_bug($bugid)) {
|
||||
$title = $desc;
|
||||
}
|
||||
if ($isobsolete) {
|
||||
$className = "bz_obsolete";
|
||||
}
|
||||
# Prevent code injection in the title.
|
||||
$title = html_quote(clean_text($title));
|
||||
|
||||
$link_text =~ s/ \[details\]$//;
|
||||
my $linkval = "attachment.cgi?id=$attachid";
|
||||
# Whitespace matters here because these links are in <pre> tags.
|
||||
return qq|<span class="$className">|
|
||||
. qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
|
||||
. qq| <a href="${linkval}&action=edit" title="$title">[details]</a>|
|
||||
. qq|</span>|;
|
||||
}
|
||||
else {
|
||||
return qq{$link_text};
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a link to a bug, including its title.
|
||||
# It takes either two or three parameters:
|
||||
# - The bug number
|
||||
# - The link text, to place between the <a>..</a>
|
||||
# - An optional comment number, for linking to a particular
|
||||
# comment in the bug
|
||||
|
||||
sub get_bug_link {
|
||||
my ($bug_num, $link_text, $comment_num) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined($bug_num) || ($bug_num eq "")) {
|
||||
return "<missing bug number>";
|
||||
}
|
||||
my $quote_bug_num = html_quote($bug_num);
|
||||
detaint_natural($bug_num) || return "<invalid bug number: $quote_bug_num>";
|
||||
|
||||
my ($bug_state, $bug_res, $bug_desc) =
|
||||
$dbh->selectrow_array('SELECT bugs.bug_status, resolution, short_desc
|
||||
FROM bugs WHERE bugs.bug_id = ?',
|
||||
undef, $bug_num);
|
||||
|
||||
if ($bug_state) {
|
||||
# Initialize these variables to be "" so that we don't get warnings
|
||||
# if we don't change them below (which is highly likely).
|
||||
my ($pre, $title, $post) = ("", "", "");
|
||||
|
||||
$title = get_text('get_status', {status => $bug_state});
|
||||
if ($bug_state eq 'UNCONFIRMED') {
|
||||
$pre = "<i>";
|
||||
$post = "</i>";
|
||||
}
|
||||
elsif (!is_open_state($bug_state)) {
|
||||
$pre = '<span class="bz_closed">';
|
||||
$title .= ' ' . get_text('get_resolution', {resolution => $bug_res});
|
||||
$post = '</span>';
|
||||
}
|
||||
if (Bugzilla->user->can_see_bug($bug_num)) {
|
||||
$title .= " - $bug_desc";
|
||||
}
|
||||
# Prevent code injection in the title.
|
||||
$title = html_quote(clean_text($title));
|
||||
|
||||
my $linkval = "show_bug.cgi?id=$bug_num";
|
||||
if (defined $comment_num) {
|
||||
$linkval .= "#c$comment_num";
|
||||
}
|
||||
return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
|
||||
}
|
||||
else {
|
||||
return qq{$link_text};
|
||||
}
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Templatization Code
|
||||
|
||||
# The Template Toolkit throws an error if a loop iterates >1000 times.
|
||||
# We want to raise that limit.
|
||||
# NOTE: If you change this number, you MUST RE-RUN checksetup.pl!!!
|
||||
# If you do not re-run checksetup.pl, the change you make will not apply
|
||||
$Template::Directive::WHILE_MAX = 1000000;
|
||||
|
||||
# Use the Toolkit Template's Stash module to add utility pseudo-methods
|
||||
# to template variables.
|
||||
use Template::Stash;
|
||||
|
||||
# Add "contains***" methods to list variables that search for one or more
|
||||
# items in a list and return boolean values representing whether or not
|
||||
# one/all/any item(s) were found.
|
||||
$Template::Stash::LIST_OPS->{ contains } =
|
||||
sub {
|
||||
my ($list, $item) = @_;
|
||||
return grep($_ eq $item, @$list);
|
||||
};
|
||||
|
||||
$Template::Stash::LIST_OPS->{ containsany } =
|
||||
sub {
|
||||
my ($list, $items) = @_;
|
||||
foreach my $item (@$items) {
|
||||
return 1 if grep($_ eq $item, @$list);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
# Allow us to still get the scalar if we use the list operation ".0" on it,
|
||||
# as we often do for defaults in query.cgi and other places.
|
||||
$Template::Stash::SCALAR_OPS->{ 0 } =
|
||||
sub {
|
||||
return $_[0];
|
||||
};
|
||||
|
||||
# Add a "substr" method to the Template Toolkit's "scalar" object
|
||||
# that returns a substring of a string.
|
||||
$Template::Stash::SCALAR_OPS->{ substr } =
|
||||
sub {
|
||||
my ($scalar, $offset, $length) = @_;
|
||||
return substr($scalar, $offset, $length);
|
||||
};
|
||||
|
||||
# Add a "truncate" method to the Template Toolkit's "scalar" object
|
||||
# that truncates a string to a certain length.
|
||||
$Template::Stash::SCALAR_OPS->{ truncate } =
|
||||
sub {
|
||||
my ($string, $length, $ellipsis) = @_;
|
||||
$ellipsis ||= "";
|
||||
|
||||
return $string if !$length || length($string) <= $length;
|
||||
|
||||
my $strlen = $length - length($ellipsis);
|
||||
my $newstr = substr($string, 0, $strlen) . $ellipsis;
|
||||
return $newstr;
|
||||
};
|
||||
|
||||
# Create the template object that processes templates and specify
|
||||
# configuration parameters that apply to all templates.
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Construct the Template object
|
||||
|
||||
# Note that all of the failure cases here can't use templateable errors,
|
||||
# since we won't have a template to use...
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my %opts = @_;
|
||||
|
||||
# checksetup.pl will call us once for any template/lang directory.
|
||||
# We need a possibility to reset the cache, so that no files from
|
||||
# the previous language pollute the action.
|
||||
if ($opts{'clean_cache'}) {
|
||||
delete Bugzilla->request_cache->{template_include_path_};
|
||||
}
|
||||
|
||||
# IMPORTANT - If you make any configuration changes here, make sure to
|
||||
# make them in t/004.template.t and checksetup.pl.
|
||||
|
||||
return $class->new({
|
||||
# Colon-separated list of directories containing templates.
|
||||
INCLUDE_PATH => [\&getTemplateIncludePath],
|
||||
|
||||
# Remove white-space before template directives (PRE_CHOMP) and at the
|
||||
# beginning and end of templates and template blocks (TRIM) for better
|
||||
# looking, more compact content. Use the plus sign at the beginning
|
||||
# of directives to maintain white space (i.e. [%+ DIRECTIVE %]).
|
||||
PRE_CHOMP => 1,
|
||||
TRIM => 1,
|
||||
|
||||
COMPILE_DIR => bz_locations()->{'datadir'} . "/template",
|
||||
|
||||
# Initialize templates (f.e. by loading plugins like Hook).
|
||||
PRE_PROCESS => "global/initialize.none.tmpl",
|
||||
|
||||
# Functions for processing text within templates in various ways.
|
||||
# IMPORTANT! When adding a filter here that does not override a
|
||||
# built-in filter, please also add a stub filter to t/004template.t.
|
||||
FILTERS => {
|
||||
|
||||
# Render text in required style.
|
||||
|
||||
inactive => [
|
||||
sub {
|
||||
my($context, $isinactive) = @_;
|
||||
return sub {
|
||||
return $isinactive ? '<span class="bz_inactive">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
closed => [
|
||||
sub {
|
||||
my($context, $isclosed) = @_;
|
||||
return sub {
|
||||
return $isclosed ? '<span class="bz_closed">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
obsolete => [
|
||||
sub {
|
||||
my($context, $isobsolete) = @_;
|
||||
return sub {
|
||||
return $isobsolete ? '<span class="bz_obsolete">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
# Returns the text with backslashes, single/double quotes,
|
||||
# and newlines/carriage returns escaped for use in JS strings.
|
||||
js => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/([\\\'\"\/])/\\$1/g;
|
||||
$var =~ s/\n/\\n/g;
|
||||
$var =~ s/\r/\\r/g;
|
||||
$var =~ s/\@/\\x40/g; # anti-spam for email addresses
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Converts data to base64
|
||||
base64 => sub {
|
||||
my ($data) = @_;
|
||||
return encode_base64($data);
|
||||
},
|
||||
|
||||
# HTML collapses newlines in element attributes to a single space,
|
||||
# so form elements which may have whitespace (ie comments) need
|
||||
# to be encoded using 
|
||||
# See bugs 4928, 22983 and 32000 for more details
|
||||
html_linebreak => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/\r\n/\
/g;
|
||||
$var =~ s/\n\r/\
/g;
|
||||
$var =~ s/\r/\
/g;
|
||||
$var =~ s/\n/\
/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Prevents line break on hyphens and whitespaces.
|
||||
no_break => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/ /\ /g;
|
||||
$var =~ s/-/\‑/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
xml => \&Bugzilla::Util::xml_quote ,
|
||||
|
||||
# This filter escapes characters in a variable or value string for
|
||||
# use in a query string. It escapes all characters NOT in the
|
||||
# regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
|
||||
# a full URL that may have characters that need encoding.
|
||||
url_quote => \&Bugzilla::Util::url_quote ,
|
||||
|
||||
# This filter is similar to url_quote but used a \ instead of a %
|
||||
# as prefix. In addition it replaces a ' ' by a '_'.
|
||||
css_class_quote => \&Bugzilla::Util::css_class_quote ,
|
||||
|
||||
quoteUrls => [ sub {
|
||||
my ($context, $bug) = @_;
|
||||
return sub {
|
||||
my $text = shift;
|
||||
return quoteUrls($text, $bug);
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
bug_link => [ sub {
|
||||
my ($context, $bug) = @_;
|
||||
return sub {
|
||||
my $text = shift;
|
||||
return get_bug_link($bug, $text);
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
bug_list_link => sub
|
||||
{
|
||||
my $buglist = shift;
|
||||
return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
|
||||
},
|
||||
|
||||
# In CSV, quotes are doubled, and any value containing a quote or a
|
||||
# comma is enclosed in quotes.
|
||||
csv => sub
|
||||
{
|
||||
my ($var) = @_;
|
||||
$var =~ s/\"/\"\"/g;
|
||||
if ($var !~ /^-?(\d+\.)?\d*$/) {
|
||||
$var = "\"$var\"";
|
||||
}
|
||||
return $var;
|
||||
} ,
|
||||
|
||||
# Format a filesize in bytes to a human readable value
|
||||
unitconvert => sub
|
||||
{
|
||||
my ($data) = @_;
|
||||
my $retval = "";
|
||||
my %units = (
|
||||
'KB' => 1024,
|
||||
'MB' => 1024 * 1024,
|
||||
'GB' => 1024 * 1024 * 1024,
|
||||
);
|
||||
|
||||
if ($data < 1024) {
|
||||
return "$data bytes";
|
||||
}
|
||||
else {
|
||||
my $u;
|
||||
foreach $u ('GB', 'MB', 'KB') {
|
||||
if ($data >= $units{$u}) {
|
||||
return sprintf("%.2f %s", $data/$units{$u}, $u);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
# Format a time for display (more info in Bugzilla::Util)
|
||||
time => \&Bugzilla::Util::format_time,
|
||||
|
||||
# Bug 120030: Override html filter to obscure the '@' in user
|
||||
# visible strings.
|
||||
# Bug 319331: Handle BiDi disruptions.
|
||||
html => sub {
|
||||
my ($var) = Template::Filters::html_filter(@_);
|
||||
# Obscure '@'.
|
||||
$var =~ s/\@/\@/g;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
# Remove the following characters because they're
|
||||
# influencing BiDi:
|
||||
# --------------------------------------------------------
|
||||
# |Code |Name |UTF-8 representation|
|
||||
# |------|--------------------------|--------------------|
|
||||
# |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
|
||||
# |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
|
||||
# |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
|
||||
# |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
|
||||
# |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
|
||||
# --------------------------------------------------------
|
||||
#
|
||||
# The following are characters influencing BiDi, too, but
|
||||
# they can be spared from filtering because they don't
|
||||
# influence more than one character right or left:
|
||||
# --------------------------------------------------------
|
||||
# |Code |Name |UTF-8 representation|
|
||||
# |------|--------------------------|--------------------|
|
||||
# |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
|
||||
# |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
|
||||
# --------------------------------------------------------
|
||||
$var =~ s/[\x{202a}-\x{202e}]//g;
|
||||
}
|
||||
return $var;
|
||||
},
|
||||
|
||||
html_light => \&Bugzilla::Util::html_light_quote,
|
||||
|
||||
# iCalendar contentline filter
|
||||
ics => [ sub {
|
||||
my ($context, @args) = @_;
|
||||
return sub {
|
||||
my ($var) = shift;
|
||||
my ($par) = shift @args;
|
||||
my ($output) = "";
|
||||
|
||||
$var =~ s/[\r\n]/ /g;
|
||||
$var =~ s/([;\\\",])/\\$1/g;
|
||||
|
||||
if ($par) {
|
||||
$output = sprintf("%s:%s", $par, $var);
|
||||
} else {
|
||||
$output = $var;
|
||||
}
|
||||
|
||||
$output =~ s/(.{75,75})/$1\n /g;
|
||||
|
||||
return $output;
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
# Note that using this filter is even more dangerous than
|
||||
# using "none," and you should only use it when you're SURE
|
||||
# the output won't be displayed directly to a web browser.
|
||||
txt => sub {
|
||||
my ($var) = @_;
|
||||
# Trivial HTML tag remover
|
||||
$var =~ s/<[^>]*>//g;
|
||||
# And this basically reverses the html filter.
|
||||
$var =~ s/\@/@/g;
|
||||
$var =~ s/\</</g;
|
||||
$var =~ s/\>/>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
$var =~ s/\&/\&/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Wrap a displayed comment to the appropriate length
|
||||
wrap_comment => [
|
||||
sub {
|
||||
my ($context, $cols) = @_;
|
||||
return sub { wrap_comment($_[0], $cols) }
|
||||
}, 1],
|
||||
|
||||
# We force filtering of every variable in key security-critical
|
||||
# places; we have a none filter for people to use when they
|
||||
# really, really don't want a variable to be changed.
|
||||
none => sub { return $_[0]; } ,
|
||||
},
|
||||
|
||||
PLUGIN_BASE => 'Bugzilla::Template::Plugin',
|
||||
|
||||
CONSTANTS => _load_constants(),
|
||||
|
||||
# Default variables for all templates
|
||||
VARIABLES => {
|
||||
# Function for retrieving global parameters.
|
||||
'Param' => sub { return Bugzilla->params->{$_[0]}; },
|
||||
|
||||
# Function to create date strings
|
||||
'time2str' => \&Date::Format::time2str,
|
||||
|
||||
# Generic linear search function
|
||||
'lsearch' => \&Bugzilla::Util::lsearch,
|
||||
|
||||
# Currently logged in user, if any
|
||||
# If an sudo session is in progress, this is the user we're faking
|
||||
'user' => sub { return Bugzilla->user; },
|
||||
|
||||
# If an sudo session is in progress, this is the user who
|
||||
# started the session.
|
||||
'sudoer' => sub { return Bugzilla->sudoer; },
|
||||
|
||||
# SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
|
||||
'SendBugMail' => sub {
|
||||
my ($id, $mailrecipients) = (@_);
|
||||
require Bugzilla::BugMail;
|
||||
Bugzilla::BugMail::Send($id, $mailrecipients);
|
||||
},
|
||||
|
||||
# Allow templates to access the "corect" URLBase value
|
||||
'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
|
||||
|
||||
# Allow templates to access docs url with users' preferred language
|
||||
'docs_urlbase' => sub {
|
||||
my ($language) = include_languages();
|
||||
my $docs_urlbase = Bugzilla->params->{'docs_urlbase'};
|
||||
$docs_urlbase =~ s/\%lang\%/$language/;
|
||||
return $docs_urlbase;
|
||||
},
|
||||
|
||||
# These don't work as normal constants.
|
||||
DB_MODULE => \&Bugzilla::Constants::DB_MODULE,
|
||||
REQUIRED_MODULES =>
|
||||
\&Bugzilla::Install::Requirements::REQUIRED_MODULES,
|
||||
OPTIONAL_MODULES => sub {
|
||||
my @optional = @{OPTIONAL_MODULES()};
|
||||
@optional = sort {$a->{feature} cmp $b->{feature}}
|
||||
@optional;
|
||||
return \@optional;
|
||||
},
|
||||
},
|
||||
|
||||
}) || die("Template creation failed: " . $class->error());
|
||||
}
|
||||
|
||||
# Used as part of the two subroutines below.
|
||||
our (%_templates_to_precompile, $_current_path);
|
||||
|
||||
sub precompile_templates {
|
||||
my ($output) = @_;
|
||||
|
||||
# Remove the compiled templates.
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-e "$datadir/template") {
|
||||
print install_string('template_removing_dir') . "\n" if $output;
|
||||
|
||||
# XXX This frequently fails if the webserver made the files, because
|
||||
# then the webserver owns the directories. We could fix that by
|
||||
# doing a chmod/chown on all the directories here.
|
||||
rmtree("$datadir/template");
|
||||
|
||||
# Check that the directory was really removed
|
||||
if(-e "$datadir/template") {
|
||||
print "\n\n";
|
||||
print "The directory '$datadir/template' could not be removed.\n";
|
||||
print "Please remove it manually and rerun checksetup.pl.\n\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
print install_string('template_precompile') if $output;
|
||||
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
# Don't hang on templates which use the CGI library
|
||||
eval("use CGI qw(-no_debug)");
|
||||
|
||||
my $dir_reader = new IO::Dir($templatedir) || die "$templatedir: $!";
|
||||
my @language_dirs = grep { /^[a-z-]+$/i } $dir_reader->read;
|
||||
$dir_reader->close;
|
||||
|
||||
foreach my $dir (@language_dirs) {
|
||||
next if ($dir eq 'CVS');
|
||||
-d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom"
|
||||
|| next;
|
||||
local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
|
||||
my $template = Bugzilla::Template->create(clean_cache => 1);
|
||||
|
||||
# Precompile all the templates found in all the directories.
|
||||
%_templates_to_precompile = ();
|
||||
foreach my $subdir (qw(custom extension default), bz_locations()->{'project'}) {
|
||||
next unless $subdir; # If 'project' is empty.
|
||||
$_current_path = File::Spec->catdir($templatedir, $dir, $subdir);
|
||||
next unless -d $_current_path;
|
||||
# Traverse the template hierarchy.
|
||||
find({ wanted => \&_precompile_push, no_chdir => 1 }, $_current_path);
|
||||
}
|
||||
# The sort isn't totally necessary, but it makes debugging easier
|
||||
# by making the templates always be compiled in the same order.
|
||||
foreach my $file (sort keys %_templates_to_precompile) {
|
||||
# Compile the template but throw away the result. This has the side-
|
||||
# effect of writing the compiled version to disk.
|
||||
$template->context->template($file);
|
||||
}
|
||||
}
|
||||
|
||||
# Under mod_perl, we look for templates using the absolute path of the
|
||||
# template directory, which causes Template Toolkit to look for their
|
||||
# *compiled* versions using the full absolute path under the data/template
|
||||
# directory. (Like data/template/var/www/html/mod_perl/.) To avoid
|
||||
# re-compiling templates under mod_perl, we symlink to the
|
||||
# already-compiled templates. This doesn't work on Windows.
|
||||
if (!ON_WINDOWS) {
|
||||
my $abs_root = dirname(abs_path($templatedir));
|
||||
my $todir = "$datadir/template$abs_root";
|
||||
mkpath($todir);
|
||||
# We use abs2rel so that the symlink will look like
|
||||
# "../../../../template" which works, while just
|
||||
# "data/template/template/" doesn't work.
|
||||
my $fromdir = File::Spec->abs2rel("$datadir/template/template", $todir);
|
||||
# We eval for systems that can't symlink at all, where "symlink"
|
||||
# throws a fatal error.
|
||||
eval { symlink($fromdir, "$todir/template")
|
||||
or warn "Failed to symlink from $fromdir to $todir: $!" };
|
||||
}
|
||||
|
||||
# If anything created a Template object before now, clear it out.
|
||||
delete Bugzilla->request_cache->{template};
|
||||
# This is the single variable used to precompile templates,
|
||||
# which needs to be cleared as well.
|
||||
delete Bugzilla->request_cache->{template_include_path_};
|
||||
|
||||
print install_string('done') . "\n" if $output;
|
||||
}
|
||||
|
||||
# Helper for precompile_templates
|
||||
sub _precompile_push {
|
||||
my $name = $File::Find::name;
|
||||
return if (-d $name);
|
||||
return if ($name =~ /\/CVS\//);
|
||||
return if ($name !~ /\.tmpl$/);
|
||||
|
||||
$name =~ s/\Q$_current_path\E\///;
|
||||
$_templates_to_precompile{$name} = 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template - Wrapper around the Template Toolkit C<Template> object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $template = Bugzilla::Template->create;
|
||||
my $format = $template->get_format("foo/bar",
|
||||
scalar($cgi->param('format')),
|
||||
scalar($cgi->param('ctype')));
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is basically a wrapper so that the correct arguments get passed into
|
||||
the C<Template> constructor.
|
||||
|
||||
It should not be used directly by scripts or modules - instead, use
|
||||
C<Bugzilla-E<gt>instance-E<gt>template> to get an already created module.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<precompile_templates($output)>
|
||||
|
||||
Description: Compiles all of Bugzilla's templates in every language.
|
||||
Used mostly by F<checksetup.pl>.
|
||||
|
||||
Params: C<$output> - C<true> if you want the function to print
|
||||
out information about what it's doing.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_format($file, $format, $ctype)>
|
||||
|
||||
Description: Construct a format object from URL parameters.
|
||||
|
||||
Params: $file - Name of the template to display.
|
||||
$format - When the template exists under several formats
|
||||
(e.g. table or graph), specify the one to choose.
|
||||
$ctype - Content type, see Bugzilla::Constants::contenttypes.
|
||||
|
||||
Returns: A format object.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla>, L<Template>
|
||||
@@ -1,66 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (C) 2008 Marc Schumann.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
|
||||
package Bugzilla::Template::Parser;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Parser);
|
||||
|
||||
sub parse {
|
||||
my ($self, $text, @params) = @_;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::is_utf8($text) || utf8::decode($text);
|
||||
}
|
||||
return $self->SUPER::parse($text, @params);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Parser - Wrapper around the Template Toolkit
|
||||
C<Template::Parser> object
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This wrapper makes the Template Toolkit aware of UTF-8 templates.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<parse($options)>
|
||||
|
||||
Description: Parses template text using Template::Parser::parse(),
|
||||
converting the text to UTF-8 encoding before, if necessary.
|
||||
|
||||
Params: C<$text> - Text to pass to Template::Parser::parse().
|
||||
|
||||
Returns: Parsed text as returned by Template::Parser::parse().
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Template>
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::Bugzilla
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the persistent C<Bugzilla>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla>, L<Template::Plugin>
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Myk Melez <myk@mozilla.org>
|
||||
# Zach Lipton <zach@zachlipton.com>
|
||||
# Elliotte Martin <everythingsolved.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::Hook;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(include_languages);
|
||||
use Bugzilla::Template;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use File::Spec;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
sub load {
|
||||
my ($class, $context) = @_;
|
||||
return $class;
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
return bless { _CONTEXT => $context }, $class;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my ($self, $hook_name, $template) = @_;
|
||||
$template ||= $self->{_CONTEXT}->stash->{component}->{name};
|
||||
|
||||
my @hooks;
|
||||
|
||||
# sanity check:
|
||||
if (!$template =~ /[\w\.\/\-_\\]+/) {
|
||||
ThrowCodeError('template_invalid', { name => $template});
|
||||
}
|
||||
|
||||
# also get extension hook files that live in extensions/:
|
||||
# parse out the parts of the template name
|
||||
my ($vol, $subpath, $filename) = File::Spec->splitpath($template);
|
||||
$subpath = $subpath || '';
|
||||
$filename =~ m/(.*)\.(.*)\.tmpl/;
|
||||
my $templatename = $1;
|
||||
my $type = $2;
|
||||
# munge the filename to create the extension hook filename:
|
||||
my $extensiontemplate = $subpath.'/'.$templatename.'-'.$hook_name.'.'.$type.'.tmpl';
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
my @usedlanguages = include_languages({use_languages => Bugzilla->languages});
|
||||
foreach my $extension (@extensions) {
|
||||
next if -e "$extension/disabled";
|
||||
foreach my $language (@usedlanguages) {
|
||||
my $file = $extension.'/template/'.$language.'/'.$extensiontemplate;
|
||||
if (-e $file) {
|
||||
# tt is stubborn and won't take a template file not in its
|
||||
# include path, so we open a filehandle and give it to process()
|
||||
# so the hook gets invoked:
|
||||
open (my $fh, $file);
|
||||
push(@hooks, $fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
|
||||
|
||||
# we keep this too since you can still put hook templates in
|
||||
# template/en/custom/hook
|
||||
foreach my $path (@$paths) {
|
||||
my @files = glob("$path/hook/$template/$hook_name/*.tmpl");
|
||||
|
||||
# Have to remove the templates path (INCLUDE_PATH) from the
|
||||
# file path since the template processor auto-adds it back.
|
||||
@files = map($_ =~ /^$path\/(.*)$/ ? $1 : {}, @files);
|
||||
|
||||
# Add found files to the list of hooks, but removing duplicates,
|
||||
# which can happen when there are identical hooks or duplicate
|
||||
# directories in the INCLUDE_PATH (the latter probably being a TT bug).
|
||||
foreach my $file (@files) {
|
||||
push(@hooks, $file) unless grep($file eq $_, @hooks);
|
||||
}
|
||||
}
|
||||
|
||||
my $output;
|
||||
foreach my $hook (@hooks) {
|
||||
$output .= $self->{_CONTEXT}->process($hook);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::Hook
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to process hooks added into templates by extensions.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item B<process>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Processes hooks added into templates by extensions.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<hook_name>
|
||||
|
||||
The unique name of the template hook.
|
||||
|
||||
=item C<template> (optional)
|
||||
|
||||
The path of the calling template.
|
||||
This is used as a work around to a bug which causes the path to the hook
|
||||
to be incorrect when the hook is called from inside a block.
|
||||
|
||||
Example: If the hook C<lastrow> is added to the template
|
||||
F<show-multiple.html.tmpl> and it is desired to force the correct template
|
||||
path, the template hook would be:
|
||||
|
||||
[% Hook.process("lastrow", "bug/show-multiple.html.tmpl") %]
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
Output from processing template extension.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Template::Plugin>
|
||||
|
||||
L<http://www.bugzilla.org/docs/tip/html/customization.html>
|
||||
|
||||
L<http://bugzilla.mozilla.org/show_bug.cgi?id=229658>
|
||||
|
||||
L<http://bugzilla.mozilla.org/show_bug.cgi?id=298341>
|
||||
@@ -1,65 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joel Peshkin <bugreport@peshkin.net>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::User;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla::User;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla::User->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::User
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the C<User>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::User>, L<Template::Plugin>
|
||||
|
||||
@@ -1,563 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Myk Melez <myk@mozilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
################################################################################
|
||||
# Module Initialization
|
||||
################################################################################
|
||||
|
||||
# Make it harder for us to do dangerous things in Perl.
|
||||
use strict;
|
||||
|
||||
# Bundle the functions in this file together into the "Bugzilla::Token" package.
|
||||
package Bugzilla::Token;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Mailer;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
use Date::Format;
|
||||
use Date::Parse;
|
||||
use File::Basename;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token);
|
||||
|
||||
################################################################################
|
||||
# Public Functions
|
||||
################################################################################
|
||||
|
||||
# Creates and sends a token to create a new user account.
|
||||
# It assumes that the login has the correct format and is not already in use.
|
||||
sub issue_new_user_account_token {
|
||||
my $login_name = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $template = Bugzilla->template;
|
||||
my $vars = {};
|
||||
|
||||
# Is there already a pending request for this login name? If yes, do not throw
|
||||
# an error because the user may have lost his email with the token inside.
|
||||
# But to prevent using this way to mailbomb an email address, make sure
|
||||
# the last request is at least 10 minutes old before sending a new email.
|
||||
|
||||
my $pending_requests =
|
||||
$dbh->selectrow_array('SELECT COUNT(*)
|
||||
FROM tokens
|
||||
WHERE tokentype = ?
|
||||
AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
|
||||
AND issuedate > NOW() - ' . $dbh->sql_interval(10, 'MINUTE'),
|
||||
undef, ('account', $login_name));
|
||||
|
||||
ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests;
|
||||
|
||||
my ($token, $token_ts) = _create_token(undef, 'account', $login_name);
|
||||
|
||||
$vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
|
||||
$vars->{'token_ts'} = $token_ts;
|
||||
$vars->{'token'} = $token;
|
||||
|
||||
my $message;
|
||||
$template->process('account/email/request-new.txt.tmpl', $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
# In 99% of cases, the user getting the confirmation email is the same one
|
||||
# who made the request, and so it is reasonable to send the email in the same
|
||||
# language used to view the "Create a New Account" page (we cannot use his
|
||||
# user prefs as the user has no account yet!).
|
||||
MessageToMTA($message);
|
||||
}
|
||||
|
||||
sub IssueEmailChangeToken {
|
||||
my ($user, $old_email, $new_email) = @_;
|
||||
my $email_suffix = Bugzilla->params->{'emailsuffix'};
|
||||
|
||||
my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email);
|
||||
|
||||
my $newtoken = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email);
|
||||
|
||||
# Mail the user the token along with instructions for using it.
|
||||
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
my $vars = {};
|
||||
|
||||
$vars->{'oldemailaddress'} = $old_email . $email_suffix;
|
||||
$vars->{'newemailaddress'} = $new_email . $email_suffix;
|
||||
|
||||
$vars->{'max_token_age'} = MAX_TOKEN_AGE;
|
||||
$vars->{'token_ts'} = $token_ts;
|
||||
|
||||
$vars->{'token'} = $token;
|
||||
$vars->{'emailaddress'} = $old_email . $email_suffix;
|
||||
|
||||
my $message;
|
||||
$template->process("account/email/change-old.txt.tmpl", $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
MessageToMTA($message);
|
||||
|
||||
$vars->{'token'} = $newtoken;
|
||||
$vars->{'emailaddress'} = $new_email . $email_suffix;
|
||||
|
||||
$message = "";
|
||||
$template->process("account/email/change-new.txt.tmpl", $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
Bugzilla->template_inner("");
|
||||
MessageToMTA($message);
|
||||
}
|
||||
|
||||
# Generates a random token, adds it to the tokens table, and sends it
|
||||
# to the user with instructions for using it to change their password.
|
||||
sub IssuePasswordToken {
|
||||
my $user = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $too_soon =
|
||||
$dbh->selectrow_array('SELECT 1 FROM tokens
|
||||
WHERE userid = ?
|
||||
AND tokentype = ?
|
||||
AND issuedate > NOW() - ' .
|
||||
$dbh->sql_interval(10, 'MINUTE'),
|
||||
undef, ($user->id, 'password'));
|
||||
|
||||
ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
|
||||
|
||||
my ($token, $token_ts) = _create_token($user->id, 'password', $::ENV{'REMOTE_ADDR'});
|
||||
|
||||
# Mail the user the token along with instructions for using it.
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
my $vars = {};
|
||||
|
||||
$vars->{'token'} = $token;
|
||||
$vars->{'emailaddress'} = $user->email;
|
||||
$vars->{'max_token_age'} = MAX_TOKEN_AGE;
|
||||
$vars->{'token_ts'} = $token_ts;
|
||||
|
||||
my $message = "";
|
||||
$template->process("account/password/forgotten-password.txt.tmpl",
|
||||
$vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
Bugzilla->template_inner("");
|
||||
MessageToMTA($message);
|
||||
}
|
||||
|
||||
sub issue_session_token {
|
||||
# Generates a random token, adds it to the tokens table, and returns
|
||||
# the token to the caller.
|
||||
|
||||
my $data = shift;
|
||||
return _create_token(Bugzilla->user->id, 'session', $data);
|
||||
}
|
||||
|
||||
sub CleanTokenTable {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do('DELETE FROM tokens
|
||||
WHERE ' . $dbh->sql_to_days('NOW()') . ' - ' .
|
||||
$dbh->sql_to_days('issuedate') . ' >= ?',
|
||||
undef, MAX_TOKEN_AGE);
|
||||
}
|
||||
|
||||
sub GenerateUniqueToken {
|
||||
# Generates a unique random token. Uses generate_random_password
|
||||
# for the tokens themselves and checks uniqueness by searching for
|
||||
# the token in the "tokens" table. Gives up if it can't come up
|
||||
# with a token after about one hundred tries.
|
||||
my ($table, $column) = @_;
|
||||
|
||||
my $token;
|
||||
my $duplicate = 1;
|
||||
my $tries = 0;
|
||||
$table ||= "tokens";
|
||||
$column ||= "token";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare("SELECT userid FROM $table WHERE $column = ?");
|
||||
|
||||
while ($duplicate) {
|
||||
++$tries;
|
||||
if ($tries > 100) {
|
||||
ThrowCodeError("token_generation_error");
|
||||
}
|
||||
$token = generate_random_password();
|
||||
$sth->execute($token);
|
||||
$duplicate = $sth->fetchrow_array;
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
# Cancels a previously issued token and notifies the user.
|
||||
# This should only happen when the user accidentally makes a token request
|
||||
# or when a malicious hacker makes a token request on behalf of a user.
|
||||
sub Cancel {
|
||||
my ($token, $cancelaction, $vars) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$vars ||= {};
|
||||
|
||||
# Get information about the token being canceled.
|
||||
trick_taint($token);
|
||||
my ($issuedate, $tokentype, $eventdata, $userid) =
|
||||
$dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
|
||||
tokentype, eventdata, userid
|
||||
FROM tokens
|
||||
WHERE token = ?',
|
||||
undef, $token);
|
||||
|
||||
# If we are canceling the creation of a new user account, then there
|
||||
# is no entry in the 'profiles' table.
|
||||
my $user = new Bugzilla::User($userid);
|
||||
|
||||
$vars->{'emailaddress'} = $userid ? $user->email : $eventdata;
|
||||
$vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
|
||||
$vars->{'token'} = $token;
|
||||
$vars->{'tokentype'} = $tokentype;
|
||||
$vars->{'issuedate'} = $issuedate;
|
||||
$vars->{'eventdata'} = $eventdata;
|
||||
$vars->{'cancelaction'} = $cancelaction;
|
||||
|
||||
# Notify the user via email about the cancellation.
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
|
||||
my $message;
|
||||
$template->process("account/cancel-token.txt.tmpl", $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
Bugzilla->template_inner("");
|
||||
MessageToMTA($message);
|
||||
|
||||
# Delete the token from the database.
|
||||
delete_token($token);
|
||||
}
|
||||
|
||||
sub DeletePasswordTokens {
|
||||
my ($userid, $reason) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($userid);
|
||||
my $tokens = $dbh->selectcol_arrayref('SELECT token FROM tokens
|
||||
WHERE userid = ? AND tokentype = ?',
|
||||
undef, ($userid, 'password'));
|
||||
|
||||
foreach my $token (@$tokens) {
|
||||
Bugzilla::Token::Cancel($token, $reason);
|
||||
}
|
||||
}
|
||||
|
||||
# Returns an email change token if the user has one.
|
||||
sub HasEmailChangeToken {
|
||||
my $userid = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $token = $dbh->selectrow_array('SELECT token FROM tokens
|
||||
WHERE userid = ?
|
||||
AND (tokentype = ? OR tokentype = ?) ' .
|
||||
$dbh->sql_limit(1),
|
||||
undef, ($userid, 'emailnew', 'emailold'));
|
||||
return $token;
|
||||
}
|
||||
|
||||
# Returns the userid, issuedate and eventdata for the specified token
|
||||
sub GetTokenData {
|
||||
my ($token) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless defined $token;
|
||||
$token = clean_text($token);
|
||||
trick_taint($token);
|
||||
|
||||
return $dbh->selectrow_array(
|
||||
"SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
|
||||
FROM tokens
|
||||
WHERE token = ?", undef, $token);
|
||||
}
|
||||
|
||||
# Deletes specified token
|
||||
sub delete_token {
|
||||
my ($token) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless defined $token;
|
||||
trick_taint($token);
|
||||
|
||||
$dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token);
|
||||
}
|
||||
|
||||
# Given a token, makes sure it comes from the currently logged in user
|
||||
# and match the expected event. Returns 1 on success, else displays a warning.
|
||||
# Note: this routine must not be called while tables are locked as it will try
|
||||
# to lock some tables itself, see CleanTokenTable().
|
||||
sub check_token_data {
|
||||
my ($token, $expected_action) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
my $template = Bugzilla->template;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my ($creator_id, $date, $token_action) = GetTokenData($token);
|
||||
unless ($creator_id
|
||||
&& $creator_id == $user->id
|
||||
&& $token_action eq $expected_action)
|
||||
{
|
||||
# Something is going wrong. Ask confirmation before processing.
|
||||
# It is possible that someone tried to trick an administrator.
|
||||
# In this case, we want to know his name!
|
||||
require Bugzilla::User;
|
||||
|
||||
my $vars = {};
|
||||
$vars->{'abuser'} = Bugzilla::User->new($creator_id)->identity;
|
||||
$vars->{'token_action'} = $token_action;
|
||||
$vars->{'expected_action'} = $expected_action;
|
||||
$vars->{'script_name'} = basename($0);
|
||||
|
||||
# Now is a good time to remove old tokens from the DB.
|
||||
CleanTokenTable();
|
||||
|
||||
# If no token was found, create a valid token for the given action.
|
||||
unless ($creator_id) {
|
||||
$token = issue_session_token($expected_action);
|
||||
$cgi->param('token', $token);
|
||||
}
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
$template->process('admin/confirm-action.html.tmpl', $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Internal Functions
|
||||
################################################################################
|
||||
|
||||
# Generates a unique token and inserts it into the database
|
||||
# Returns the token and the token timestamp
|
||||
sub _create_token {
|
||||
my ($userid, $tokentype, $eventdata) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($userid) if defined $userid;
|
||||
trick_taint($tokentype);
|
||||
trick_taint($eventdata);
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $token = GenerateUniqueToken();
|
||||
|
||||
$dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
|
||||
VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata));
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
if (wantarray) {
|
||||
my (undef, $token_ts, undef) = GetTokenData($token);
|
||||
$token_ts = str2time($token_ts);
|
||||
return ($token, $token_ts);
|
||||
} else {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Token - Provides different routines to manage tokens.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Token;
|
||||
|
||||
Bugzilla::Token::issue_new_user_account_token($login_name);
|
||||
Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
|
||||
Bugzilla::Token::IssuePasswordToken($user);
|
||||
Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
|
||||
Bugzilla::Token::Cancel($token, $cancelaction, $vars);
|
||||
|
||||
Bugzilla::Token::CleanTokenTable();
|
||||
|
||||
my $token = issue_session_token($event);
|
||||
check_token_data($token, $event)
|
||||
delete_token($token);
|
||||
|
||||
my $token = Bugzilla::Token::GenerateUniqueToken($table, $column);
|
||||
my $token = Bugzilla::Token::HasEmailChangeToken($user_id);
|
||||
my ($token, $date, $data) = Bugzilla::Token::GetTokenData($token);
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<issue_new_user_account_token($login_name)>
|
||||
|
||||
Description: Creates and sends a token per email to the email address
|
||||
requesting a new user account. It doesn't check whether
|
||||
the user account already exists. The user will have to
|
||||
use this token to confirm the creation of his user account.
|
||||
|
||||
Params: $login_name - The new login name requested by the user.
|
||||
|
||||
Returns: Nothing. It throws an error if the same user made the same
|
||||
request in the last few minutes.
|
||||
|
||||
=item C<sub IssueEmailChangeToken($user, $old_email, $new_email)>
|
||||
|
||||
Description: Sends two distinct tokens per email to the old and new email
|
||||
addresses to confirm the email address change for the given
|
||||
user. These tokens remain valid for the next MAX_TOKEN_AGE days.
|
||||
|
||||
Params: $user - User object of the user requesting a new
|
||||
email address.
|
||||
$old_email - The current (old) email address of the user.
|
||||
$new_email - The new email address of the user.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<IssuePasswordToken($user)>
|
||||
|
||||
Description: Sends a token per email to the given user. This token
|
||||
can be used to change the password (e.g. in case the user
|
||||
cannot remember his password and wishes to enter a new one).
|
||||
|
||||
Params: $user - User object of the user requesting a new password.
|
||||
|
||||
Returns: Nothing. It throws an error if the same user made the same
|
||||
request in the last few minutes.
|
||||
|
||||
=item C<CleanTokenTable()>
|
||||
|
||||
Description: Removes all tokens older than MAX_TOKEN_AGE days from the DB.
|
||||
This means that these tokens will now be considered as invalid.
|
||||
|
||||
Params: None.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<GenerateUniqueToken($table, $column)>
|
||||
|
||||
Description: Generates and returns a unique token. This token is unique
|
||||
in the $column of the $table. This token is NOT stored in the DB.
|
||||
|
||||
Params: $table (optional): The table to look at (default: tokens).
|
||||
$column (optional): The column to look at for uniqueness (default: token).
|
||||
|
||||
Returns: A token which is unique in $column.
|
||||
|
||||
=item C<Cancel($token, $cancelaction, $vars)>
|
||||
|
||||
Description: Invalidates an existing token, generally when the token is used
|
||||
for an action which is not the one expected. An email is sent
|
||||
to the user who originally requested this token to inform him
|
||||
that this token has been invalidated (e.g. because an hacker
|
||||
tried to use this token for some malicious action).
|
||||
|
||||
Params: $token: The token to invalidate.
|
||||
$cancelaction: The reason why this token is invalidated.
|
||||
$vars: Some additional information about this action.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<DeletePasswordTokens($user_id, $reason)>
|
||||
|
||||
Description: Cancels all password tokens for the given user. Emails are sent
|
||||
to the user to inform him about this action.
|
||||
|
||||
Params: $user_id: The user ID of the user account whose password tokens
|
||||
are canceled.
|
||||
$reason: The reason why these tokens are canceled.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<HasEmailChangeToken($user_id)>
|
||||
|
||||
Description: Returns any existing token currently used for an email change
|
||||
for the given user.
|
||||
|
||||
Params: $user_id - A user ID.
|
||||
|
||||
Returns: A token if it exists, else undef.
|
||||
|
||||
=item C<GetTokenData($token)>
|
||||
|
||||
Description: Returns all stored data for the given token.
|
||||
|
||||
Params: $token - A valid token.
|
||||
|
||||
Returns: The user ID, the date and time when the token was created and
|
||||
the (event)data stored with that token.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Security related routines
|
||||
|
||||
The following routines have been written to be used together as described below,
|
||||
although they can be used separately.
|
||||
|
||||
=over
|
||||
|
||||
=item C<issue_session_token($event)>
|
||||
|
||||
Description: Creates and returns a token used internally.
|
||||
|
||||
Params: $event - The event which needs to be stored in the DB for future
|
||||
reference/checks.
|
||||
|
||||
Returns: A unique token.
|
||||
|
||||
=item C<check_token_data($token, $event)>
|
||||
|
||||
Description: Makes sure the $token has been created by the currently logged in
|
||||
user and to be used for the given $event. If this token is used for
|
||||
an unexpected action (i.e. $event doesn't match the information stored
|
||||
with the token), a warning is displayed asking whether the user really
|
||||
wants to continue. On success, it returns 1.
|
||||
This is the routine to use for security checks, combined with
|
||||
issue_session_token() and delete_token() as follows:
|
||||
|
||||
1. First, create a token for some coming action.
|
||||
my $token = issue_session_token($action);
|
||||
2. Some time later, it's time to make sure that the expected action
|
||||
is going to be executed, and by the expected user.
|
||||
check_token_data($token, $action);
|
||||
3. The check has been done and we no longer need this token.
|
||||
delete_token($token);
|
||||
|
||||
Params: $token - The token used for security checks.
|
||||
$event - The expected event to be run.
|
||||
|
||||
Returns: 1 on success, else a warning is thrown.
|
||||
|
||||
=item C<delete_token($token)>
|
||||
|
||||
Description: Deletes the specified token. No notification is sent.
|
||||
|
||||
Params: $token - The token to delete.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,221 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Update;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
|
||||
use constant LOCAL_FILE => "/bugzilla-update.xml"; # Relative to datadir.
|
||||
use constant TIME_INTERVAL => 86400; # Default is one day, in seconds.
|
||||
use constant TIMEOUT => 5; # Number of seconds before timeout.
|
||||
|
||||
# Look for new releases and notify logged in administrators about them.
|
||||
sub get_notifications {
|
||||
return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled');
|
||||
|
||||
# If the XML::Twig module is missing, we won't be able to parse
|
||||
# the XML file. So there is no need to go further.
|
||||
eval("require XML::Twig");
|
||||
return if $@;
|
||||
|
||||
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
|
||||
# Update the local XML file if this one doesn't exist or if
|
||||
# the last modification time (stat[9]) is older than TIME_INTERVAL.
|
||||
if (!-e $local_file || (time() - (stat($local_file))[9] > TIME_INTERVAL)) {
|
||||
# Are we sure we didn't try to refresh this file already
|
||||
# but we failed because we cannot modify its timestamp?
|
||||
my $can_alter = (-e $local_file) ? utime(undef, undef, $local_file) : 1;
|
||||
if ($can_alter) {
|
||||
unlink $local_file; # Make sure the old copy is away.
|
||||
my $error = _synchronize_data();
|
||||
# If an error is returned, leave now.
|
||||
return $error if $error;
|
||||
}
|
||||
else {
|
||||
return {'error' => 'no_update', 'xml_file' => $local_file};
|
||||
}
|
||||
}
|
||||
|
||||
# If we cannot access the local XML file, ignore it.
|
||||
return {'error' => 'no_access', 'xml_file' => $local_file} unless (-r $local_file);
|
||||
|
||||
my $twig = XML::Twig->new();
|
||||
$twig->safe_parsefile($local_file);
|
||||
# If the XML file is invalid, return.
|
||||
return {'error' => 'corrupted', 'xml_file' => $local_file} if $@;
|
||||
my $root = $twig->root;
|
||||
|
||||
my @releases;
|
||||
foreach my $branch ($root->children('branch')) {
|
||||
my $release = {
|
||||
'branch_ver' => $branch->{'att'}->{'id'},
|
||||
'latest_ver' => $branch->{'att'}->{'vid'},
|
||||
'status' => $branch->{'att'}->{'status'},
|
||||
'url' => $branch->{'att'}->{'url'},
|
||||
'date' => $branch->{'att'}->{'date'}
|
||||
};
|
||||
push(@releases, $release);
|
||||
}
|
||||
|
||||
# On which branch is the current installation running?
|
||||
my @current_version =
|
||||
(BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
|
||||
my @release;
|
||||
if (Bugzilla->params->{'upgrade_notification'} eq 'development_snapshot') {
|
||||
@release = grep {$_->{'status'} eq 'development'} @releases;
|
||||
# If there is no development snapshot available, then we are in the
|
||||
# process of releasing a release candidate. That's the release we want.
|
||||
unless (scalar(@release)) {
|
||||
@release = grep {$_->{'status'} eq 'release-candidate'} @releases;
|
||||
}
|
||||
}
|
||||
elsif (Bugzilla->params->{'upgrade_notification'} eq 'latest_stable_release') {
|
||||
@release = grep {$_->{'status'} eq 'stable'} @releases;
|
||||
}
|
||||
elsif (Bugzilla->params->{'upgrade_notification'} eq 'stable_branch_release') {
|
||||
# We want the latest stable version for the current branch.
|
||||
# If we are running a development snapshot, we won't match anything.
|
||||
my $branch_version = $current_version[0] . '.' . $current_version[1];
|
||||
|
||||
# We do a string comparison instead of a numerical one, because
|
||||
# e.g. 2.2 == 2.20, but 2.2 ne 2.20 (and 2.2 is indeed much older).
|
||||
@release = grep {$_->{'branch_ver'} eq $branch_version} @releases;
|
||||
|
||||
# If the branch is now closed, we should strongly suggest
|
||||
# to upgrade to the latest stable release available.
|
||||
if (scalar(@release) && $release[0]->{'status'} eq 'closed') {
|
||||
@release = grep {$_->{'status'} eq 'stable'} @releases;
|
||||
return {'data' => $release[0], 'deprecated' => $branch_version};
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Unknown parameter.
|
||||
return {'error' => 'unknown_parameter'};
|
||||
}
|
||||
|
||||
# Return if no new release is available.
|
||||
return unless scalar(@release);
|
||||
|
||||
# Only notify the administrator if the latest version available
|
||||
# is newer than the current one.
|
||||
my @new_version =
|
||||
($release[0]->{'latest_ver'} =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
|
||||
# We convert release candidates 'rc' to integers (rc ? 0 : 1) in order
|
||||
# to compare versions easily.
|
||||
$current_version[2] = ($current_version[2] && $current_version[2] eq 'rc') ? 0 : 1;
|
||||
$new_version[2] = ($new_version[2] && $new_version[2] eq 'rc') ? 0 : 1;
|
||||
|
||||
my $is_newer = _compare_versions(\@current_version, \@new_version);
|
||||
return ($is_newer == 1) ? {'data' => $release[0]} : undef;
|
||||
}
|
||||
|
||||
sub _synchronize_data {
|
||||
eval("require LWP::UserAgent");
|
||||
return {'error' => 'missing_package', 'package' => 'LWP::UserAgent'} if $@;
|
||||
|
||||
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
|
||||
|
||||
my $ua = LWP::UserAgent->new();
|
||||
$ua->timeout(TIMEOUT);
|
||||
$ua->protocols_allowed(['http', 'https']);
|
||||
# If the URL of the proxy is given, use it, else get this information
|
||||
# from the environment variable.
|
||||
my $proxy_url = Bugzilla->params->{'proxy_url'};
|
||||
if ($proxy_url) {
|
||||
$ua->proxy(['http', 'https'], $proxy_url);
|
||||
}
|
||||
else {
|
||||
$ua->env_proxy;
|
||||
}
|
||||
$ua->mirror(REMOTE_FILE, $local_file);
|
||||
|
||||
# $ua->mirror() forces the modification time of the local XML file
|
||||
# to match the modification time of the remote one.
|
||||
# So we have to update it manually to reflect that a newer version
|
||||
# of the file has effectively been requested. This will avoid
|
||||
# any new download for the next TIME_INTERVAL.
|
||||
if (-e $local_file) {
|
||||
# Try to alter its last modification time.
|
||||
my $can_alter = utime(undef, undef, $local_file);
|
||||
# This error should never happen.
|
||||
$can_alter || return {'error' => 'no_update', 'xml_file' => $local_file};
|
||||
}
|
||||
else {
|
||||
# We have been unable to download the file.
|
||||
return {'error' => 'cannot_download', 'xml_file' => $local_file};
|
||||
}
|
||||
|
||||
# Everything went well.
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _compare_versions {
|
||||
my ($old_ver, $new_ver) = @_;
|
||||
while (scalar(@$old_ver) && scalar(@$new_ver)) {
|
||||
my $old = shift(@$old_ver) || 0;
|
||||
my $new = shift(@$new_ver) || 0;
|
||||
return $new <=> $old if ($new <=> $old);
|
||||
}
|
||||
return scalar(@$new_ver) <=> scalar(@$old_ver);
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Update - Update routines for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Update;
|
||||
|
||||
# Get information about new releases
|
||||
my $new_release = Bugzilla::Update::get_notifications();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains all required routines to notify you
|
||||
about new releases. It downloads an XML file from bugzilla.org
|
||||
and parses it, in order to display information based on your
|
||||
preferences. Absolutely no information about the Bugzilla version
|
||||
you are running is sent to bugzilla.org.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_notifications()>
|
||||
|
||||
Description: This function informs you about new releases, if any.
|
||||
|
||||
Params: None.
|
||||
|
||||
Returns: On success, a reference to a hash with data about
|
||||
new releases, if any.
|
||||
On failure, a reference to a hash with the reason
|
||||
of the failure and the name of the unusable XML file.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,433 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
|
||||
package Bugzilla::User::Setting;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
|
||||
# Module stuff
|
||||
@Bugzilla::User::Setting::EXPORT = qw(get_all_settings get_defaults
|
||||
add_setting);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util qw(trick_taint get_text);
|
||||
|
||||
###############################
|
||||
### Module Initialization ###
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $setting_name = shift;
|
||||
my $user_id = shift;
|
||||
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $subclass = '';
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Confirm that the $setting_name is properly formed;
|
||||
# if not, throw a code error.
|
||||
#
|
||||
# NOTE: due to the way that setting names are used in templates,
|
||||
# they must conform to to the limitations set for HTML NAMEs and IDs.
|
||||
#
|
||||
if ( !($setting_name =~ /^[a-zA-Z][-.:\w]*$/) ) {
|
||||
ThrowCodeError("setting_name_invalid", { name => $setting_name });
|
||||
}
|
||||
|
||||
# If there were only two parameters passed in, then we need
|
||||
# to retrieve the information for this setting ourselves.
|
||||
if (scalar @_ == 0) {
|
||||
|
||||
my ($default, $is_enabled, $value);
|
||||
($default, $is_enabled, $value, $subclass) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled, setting_value, subclass
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE name = ?
|
||||
AND profile_setting.user_id = ?},
|
||||
undef,
|
||||
$setting_name, $user_id);
|
||||
|
||||
# if not defined, then grab the default value
|
||||
if (! defined $value) {
|
||||
($default, $is_enabled, $subclass) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled, subclass
|
||||
FROM setting
|
||||
WHERE name = ?},
|
||||
undef,
|
||||
$setting_name);
|
||||
}
|
||||
|
||||
$self->{'is_enabled'} = $is_enabled;
|
||||
$self->{'default_value'} = $default;
|
||||
|
||||
# IF the setting is enabled, AND the user has chosen a setting
|
||||
# THEN return that value
|
||||
# ELSE return the site default, and note that it is the default.
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$self->{'value'} = $value;
|
||||
} else {
|
||||
$self->{'value'} = $default;
|
||||
$self->{'isdefault'} = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# If the values were passed in, simply assign them and return.
|
||||
$self->{'is_enabled'} = shift;
|
||||
$self->{'default_value'} = shift;
|
||||
$self->{'value'} = shift;
|
||||
$self->{'is_default'} = shift;
|
||||
$subclass = shift;
|
||||
}
|
||||
if ($subclass) {
|
||||
eval('require ' . $class . '::' . $subclass);
|
||||
$@ && ThrowCodeError('setting_subclass_invalid',
|
||||
{'subclass' => $subclass});
|
||||
$class = $class . '::' . $subclass;
|
||||
}
|
||||
bless($self, $class);
|
||||
|
||||
$self->{'_setting_name'} = $setting_name;
|
||||
$self->{'_user_id'} = $user_id;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Subroutine Definitions ###
|
||||
###############################
|
||||
|
||||
sub add_setting {
|
||||
my ($name, $values, $default_value, $subclass, $force_check) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $exists = _setting_exists($name);
|
||||
return if ($exists && !$force_check);
|
||||
|
||||
($name && $default_value)
|
||||
|| ThrowCodeError("setting_info_invalid");
|
||||
|
||||
if ($exists) {
|
||||
# If this setting exists, we delete it and regenerate it.
|
||||
$dbh->do('DELETE FROM setting_value WHERE name = ?', undef, $name);
|
||||
$dbh->do('DELETE FROM setting WHERE name = ?', undef, $name);
|
||||
# Remove obsolete user preferences for this setting.
|
||||
if (defined $values && scalar(@$values)) {
|
||||
my $list = join(', ', map {$dbh->quote($_)} @$values);
|
||||
$dbh->do("DELETE FROM profile_setting
|
||||
WHERE setting_name = ? AND setting_value NOT IN ($list)",
|
||||
undef, $name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
print get_text('install_setting_new', { name => $name }) . "\n";
|
||||
}
|
||||
$dbh->do(q{INSERT INTO setting (name, default_value, is_enabled, subclass)
|
||||
VALUES (?, ?, 1, ?)},
|
||||
undef, ($name, $default_value, $subclass));
|
||||
|
||||
my $sth = $dbh->prepare(q{INSERT INTO setting_value (name, value, sortindex)
|
||||
VALUES (?, ?, ?)});
|
||||
|
||||
my $sortindex = 5;
|
||||
foreach my $key (@$values){
|
||||
$sth->execute($name, $key, $sortindex);
|
||||
$sortindex += 5;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_all_settings {
|
||||
my ($user_id) = @_;
|
||||
my $settings = get_defaults($user_id); # first get the defaults
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(
|
||||
q{SELECT name, default_value, is_enabled, setting_value, subclass
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE profile_setting.user_id = ?
|
||||
ORDER BY name});
|
||||
|
||||
$sth->execute($user_id);
|
||||
while (my ($name, $default_value, $is_enabled, $value, $subclass)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
|
||||
my $is_default;
|
||||
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$is_default = 0;
|
||||
} else {
|
||||
$value = $default_value;
|
||||
$is_default = 1;
|
||||
}
|
||||
|
||||
$settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default, $subclass);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
sub get_defaults {
|
||||
my ($user_id) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $default_settings = {};
|
||||
|
||||
$user_id ||= 0;
|
||||
|
||||
my $sth = $dbh->prepare(q{SELECT name, default_value, is_enabled, subclass
|
||||
FROM setting
|
||||
ORDER BY name});
|
||||
$sth->execute();
|
||||
while (my ($name, $default_value, $is_enabled, $subclass)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
|
||||
$default_settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled, $default_value, $default_value, 1,
|
||||
$subclass);
|
||||
}
|
||||
|
||||
return $default_settings;
|
||||
}
|
||||
|
||||
sub set_default {
|
||||
my ($setting_name, $default_value, $is_enabled) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(q{UPDATE setting
|
||||
SET default_value = ?, is_enabled = ?
|
||||
WHERE name = ?});
|
||||
$sth->execute($default_value, $is_enabled, $setting_name);
|
||||
}
|
||||
|
||||
sub _setting_exists {
|
||||
my ($setting_name) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
return $dbh->selectrow_arrayref(
|
||||
"SELECT 1 FROM setting WHERE name = ?", undef, $setting_name) || 0;
|
||||
}
|
||||
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$self->{'legal_values'} = $dbh->selectcol_arrayref(
|
||||
q{SELECT value
|
||||
FROM setting_value
|
||||
WHERE name = ?
|
||||
ORDER BY sortindex},
|
||||
undef, $self->{'_setting_name'});
|
||||
|
||||
return $self->{'legal_values'};
|
||||
}
|
||||
|
||||
sub validate_value {
|
||||
my $self = shift;
|
||||
|
||||
if (grep(/^$_[0]$/, @{$self->legal_values()})) {
|
||||
trick_taint($_[0]);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('setting_value_invalid',
|
||||
{'name' => $self->{'_setting_name'},
|
||||
'value' => $_[0]});
|
||||
}
|
||||
}
|
||||
|
||||
sub reset_to_default {
|
||||
my ($self) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->do(q{ DELETE
|
||||
FROM profile_setting
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?},
|
||||
undef, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
$self->{'value'} = $self->{'default_value'};
|
||||
$self->{'is_default'} = 1;
|
||||
}
|
||||
|
||||
sub set {
|
||||
my ($self, $value) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $query;
|
||||
|
||||
if ($self->{'is_default'}) {
|
||||
$query = q{INSERT INTO profile_setting
|
||||
(setting_value, setting_name, user_id)
|
||||
VALUES (?,?,?)};
|
||||
} else {
|
||||
$query = q{UPDATE profile_setting
|
||||
SET setting_value = ?
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?};
|
||||
}
|
||||
$dbh->do($query, undef, $value, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
|
||||
$self->{'value'} = $value;
|
||||
$self->{'is_default'} = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting - Object for a user preference setting
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Setting.pm creates a setting object, which is a hash containing the user
|
||||
preference information for a single preference for a single user. These
|
||||
are usually accessed through the "settings" object of a user, and not
|
||||
directly.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
use Bugzilla::User::Setting;
|
||||
my $settings;
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id);
|
||||
|
||||
OR
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default);
|
||||
|
||||
=head1 CLASS FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<add_setting($name, \@values, $default_value, $subclass, $force_check)>
|
||||
|
||||
Description: Checks for the existence of a setting, and adds it
|
||||
to the database if it does not yet exist.
|
||||
|
||||
Params: C<$name> - string - the name of the new setting
|
||||
C<$values> - arrayref - contains the new choices
|
||||
for the new Setting.
|
||||
C<$default_value> - string - the site default
|
||||
C<$subclass> - string - name of the module returning
|
||||
the list of valid values. This means legal values are
|
||||
not stored in the DB.
|
||||
C<$force_check> - boolean - when true, the existing setting
|
||||
and all its values are deleted and replaced by new data.
|
||||
|
||||
Returns: a pointer to a hash of settings
|
||||
|
||||
|
||||
=item C<get_all_settings($user_id)>
|
||||
|
||||
Description: Provides the user's choices for each setting in the
|
||||
system; if the user has made no choice, uses the site
|
||||
default instead.
|
||||
Params: C<$user_id> - integer - the user id.
|
||||
Returns: a pointer to a hash of settings
|
||||
|
||||
=item C<get_defaults($user_id)>
|
||||
|
||||
Description: When a user is not logged in, they must use the site
|
||||
defaults for every settings; this subroutine provides them.
|
||||
Params: C<$user_id> (optional) - integer - the user id. Note that
|
||||
this optional parameter is mainly for internal use only.
|
||||
Returns: A pointer to a hash of settings. If $user_id was passed, set
|
||||
the user_id value for each setting.
|
||||
|
||||
=item C<set_default($setting_name, $default_value, $is_enabled)>
|
||||
|
||||
Description: Sets the global default for a given setting. Also sets
|
||||
whether users are allowed to choose their own value for
|
||||
this setting, or if they must use the global default.
|
||||
Params: C<$setting_name> - string - the name of the setting
|
||||
C<$default_value> - string - the new default value for this setting
|
||||
C<$is_enabled> - boolean - if false, all users must use the global default
|
||||
Returns: nothing
|
||||
|
||||
=begin private
|
||||
|
||||
=item C<_setting_exists>
|
||||
|
||||
Description: Determines if a given setting exists in the database.
|
||||
Params: C<$setting_name> - string - the setting name
|
||||
Returns: boolean - true if the setting already exists in the DB.
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<legal_values($setting_name)>
|
||||
|
||||
Description: Returns all legal values for this setting
|
||||
Params: none
|
||||
Returns: A reference to an array containing all legal values
|
||||
|
||||
=item C<validate_value>
|
||||
|
||||
Description: Determines whether a value is valid for the setting
|
||||
by checking against the list of legal values.
|
||||
Untaints the parameter if the value is indeed valid,
|
||||
and throws a setting_value_invalid code error if not.
|
||||
Params: An lvalue containing a candidate for a setting value
|
||||
Returns: nothing
|
||||
|
||||
=item C<reset_to_default>
|
||||
|
||||
Description: If a user chooses to use the global default for a given
|
||||
setting, their saved entry is removed from the database via
|
||||
this subroutine.
|
||||
Params: none
|
||||
Returns: nothing
|
||||
|
||||
=item C<set($value)>
|
||||
|
||||
Description: If a user chooses to use their own value rather than the
|
||||
global value for a given setting, OR changes their value for
|
||||
a given setting, this subroutine is called to insert or
|
||||
update the database as appropriate.
|
||||
Params: C<$value> - string - the new value for this setting for this user.
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- 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 Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::User::Setting::Lang;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::User::Setting);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
return $self->{'legal_values'} = Bugzilla->languages;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting::Lang - Object for a user preference setting for preferred language
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Lang.pm extends Bugzilla::User::Setting and implements a class specialized for
|
||||
setting the preferred language.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values()>
|
||||
|
||||
Description: Returns all legal languages
|
||||
Params: none
|
||||
Returns: A reference to an array containing the names of all legal languages
|
||||
|
||||
=back
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
|
||||
package Bugzilla::User::Setting::Skin;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::User::Setting);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use File::Spec::Functions;
|
||||
use File::Basename;
|
||||
|
||||
use constant BUILTIN_SKIN_NAMES => ['standard'];
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my $dirbase = bz_locations()->{'skinsdir'} . '/contrib';
|
||||
# Avoid modification of the list BUILTIN_SKIN_NAMES points to by copying the
|
||||
# list over instead of simply writing $legal_values = BUILTIN_SKIN_NAMES.
|
||||
my @legal_values = @{(BUILTIN_SKIN_NAMES)};
|
||||
|
||||
foreach my $direntry (glob(catdir($dirbase, '*'))) {
|
||||
if (-d $direntry) {
|
||||
# Stylesheet set
|
||||
next if basename($direntry) =~ /^cvs$/i;
|
||||
push(@legal_values, basename($direntry));
|
||||
}
|
||||
elsif ($direntry =~ /\.css$/) {
|
||||
# Single-file stylesheet
|
||||
push(@legal_values, basename($direntry));
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{'legal_values'} = \@legal_values;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting::Skin - Object for a user preference setting for skins
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Skin.pm extends Bugzilla::User::Setting and implements a class specialized for
|
||||
skins settings.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values()>
|
||||
|
||||
Description: Returns all legal skins
|
||||
Params: none
|
||||
Returns: A reference to an array containing the names of all legal skins
|
||||
|
||||
=back
|
||||
@@ -1,953 +0,0 @@
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Util;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
|
||||
detaint_signed
|
||||
html_quote url_quote xml_quote
|
||||
css_class_quote html_light_quote url_decode
|
||||
i_am_cgi get_netaddr correct_urlbase
|
||||
lsearch ssl_require_redirect
|
||||
diff_arrays diff_strings
|
||||
trim wrap_hard wrap_comment find_wrap_point
|
||||
format_time format_time_decimal validate_date
|
||||
validate_time
|
||||
file_mod_time is_7bit_clean
|
||||
bz_crypt generate_random_password
|
||||
validate_email_syntax clean_text
|
||||
get_text disable_utf8);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
use Date::Parse;
|
||||
use Date::Format;
|
||||
use Text::Wrap;
|
||||
|
||||
# This is from the perlsec page, slightly modified to remove a warning
|
||||
# From that page:
|
||||
# This function makes use of the fact that the presence of
|
||||
# tainted data anywhere within an expression renders the
|
||||
# entire expression tainted.
|
||||
# Don't ask me how it works...
|
||||
sub is_tainted {
|
||||
return not eval { my $foo = join('',@_), kill 0; 1; };
|
||||
}
|
||||
|
||||
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 detaint_natural {
|
||||
my $match = $_[0] =~ /^(\d+)$/;
|
||||
$_[0] = $match ? $1 : undef;
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub detaint_signed {
|
||||
my $match = $_[0] =~ /^([-+]?\d+)$/;
|
||||
$_[0] = $match ? $1 : undef;
|
||||
# Remove any leading plus sign.
|
||||
if (defined($_[0]) && $_[0] =~ /^\+(\d+)$/) {
|
||||
$_[0] = $1;
|
||||
}
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub html_quote {
|
||||
my ($var) = (@_);
|
||||
$var =~ s/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
return $var;
|
||||
}
|
||||
|
||||
sub html_light_quote {
|
||||
my ($text) = @_;
|
||||
|
||||
# List of allowed HTML elements having no attributes.
|
||||
my @allow = qw(b strong em i u p br abbr acronym ins del cite code var
|
||||
dfn samp kbd big small sub sup tt dd dt dl ul li ol
|
||||
fieldset legend);
|
||||
|
||||
# Are HTML::Scrubber and HTML::Parser installed?
|
||||
eval { require HTML::Scrubber;
|
||||
require HTML::Parser;
|
||||
};
|
||||
|
||||
if ($@) { # Package(s) not installed.
|
||||
my $safe = join('|', @allow);
|
||||
my $chr = chr(1);
|
||||
|
||||
# First, escape safe elements.
|
||||
$text =~ s#<($safe)>#$chr$1$chr#go;
|
||||
$text =~ s#</($safe)>#$chr/$1$chr#go;
|
||||
# Now filter < and >.
|
||||
$text =~ s#<#<#g;
|
||||
$text =~ s#>#>#g;
|
||||
# Restore safe elements.
|
||||
$text =~ s#$chr/($safe)$chr#</$1>#go;
|
||||
$text =~ s#$chr($safe)$chr#<$1>#go;
|
||||
return $text;
|
||||
}
|
||||
else { # Packages installed.
|
||||
# We can be less restrictive. We can accept elements with attributes.
|
||||
push(@allow, qw(a blockquote q span));
|
||||
|
||||
# Allowed protocols.
|
||||
my $safe_protocols = join('|', SAFE_PROTOCOLS);
|
||||
my $protocol_regexp = qr{(^(?:$safe_protocols):|^[^:]+$)}i;
|
||||
|
||||
# Deny all elements and attributes unless explicitly authorized.
|
||||
my @default = (0 => {
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
}
|
||||
);
|
||||
|
||||
# Specific rules for allowed elements. If no specific rule is set
|
||||
# for a given element, then the default is used.
|
||||
my @rules = (a => {
|
||||
href => $protocol_regexp,
|
||||
title => 1,
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
},
|
||||
blockquote => {
|
||||
cite => $protocol_regexp,
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
},
|
||||
'q' => {
|
||||
cite => $protocol_regexp,
|
||||
id => 1,
|
||||
name => 1,
|
||||
class => 1,
|
||||
'*' => 0, # Reject all other attributes.
|
||||
},
|
||||
);
|
||||
|
||||
my $scrubber = HTML::Scrubber->new(default => \@default,
|
||||
allow => \@allow,
|
||||
rules => \@rules,
|
||||
comment => 0,
|
||||
process => 0);
|
||||
|
||||
return $scrubber->scrub($text);
|
||||
}
|
||||
}
|
||||
|
||||
# This originally came from CGI.pm, by Lincoln D. Stein
|
||||
sub url_quote {
|
||||
my ($toencode) = (@_);
|
||||
utf8::encode($toencode) # The below regex works only on bytes
|
||||
if Bugzilla->params->{'utf8'} && utf8::is_utf8($toencode);
|
||||
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
|
||||
return $toencode;
|
||||
}
|
||||
|
||||
sub css_class_quote {
|
||||
my ($toencode) = (@_);
|
||||
$toencode =~ s/ /_/g;
|
||||
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("&#x%x;",ord($1))/eg;
|
||||
return $toencode;
|
||||
}
|
||||
|
||||
sub xml_quote {
|
||||
my ($var) = (@_);
|
||||
$var =~ s/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
$var =~ s/\'/\'/g;
|
||||
return $var;
|
||||
}
|
||||
|
||||
# This function must not be relied upon to return a valid string to pass to
|
||||
# the DB or the user in UTF-8 situations. The only thing you can rely upon
|
||||
# it for is that if you url_decode a string, it will url_encode back to the
|
||||
# exact same thing.
|
||||
sub url_decode {
|
||||
my ($todecode) = (@_);
|
||||
$todecode =~ tr/+/ /; # pluses become spaces
|
||||
$todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
|
||||
return $todecode;
|
||||
}
|
||||
|
||||
sub i_am_cgi {
|
||||
# I use SERVER_SOFTWARE because it's required to be
|
||||
# defined for all requests in the CGI spec.
|
||||
return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
|
||||
}
|
||||
|
||||
sub ssl_require_redirect {
|
||||
my $method = shift;
|
||||
|
||||
# If currently not in a protected SSL
|
||||
# connection, determine if a redirection is
|
||||
# needed based on value in Bugzilla->params->{ssl}.
|
||||
# If we are already in a protected connection or
|
||||
# sslbase is not set then no action is required.
|
||||
if (uc($ENV{'HTTPS'}) ne 'ON'
|
||||
&& $ENV{'SERVER_PORT'} != 443
|
||||
&& Bugzilla->params->{'sslbase'} ne '')
|
||||
{
|
||||
# System is configured to never require SSL
|
||||
# so no redirection is needed.
|
||||
return 0
|
||||
if Bugzilla->params->{'ssl'} eq 'never';
|
||||
|
||||
# System is configured to always require a SSL
|
||||
# connection so we need to redirect.
|
||||
return 1
|
||||
if Bugzilla->params->{'ssl'} eq 'always';
|
||||
|
||||
# System is configured such that if we are inside
|
||||
# of an authenticated session, then we need to make
|
||||
# sure that all of the connections are over SSL. Non
|
||||
# authenticated sessions SSL is not mandatory.
|
||||
# For XMLRPC requests, if the method is User.login
|
||||
# then we always want the connection to be over SSL
|
||||
# if the system is configured for authenticated
|
||||
# sessions since the user's username and password
|
||||
# will be passed before the user is logged in.
|
||||
return 1
|
||||
if Bugzilla->params->{'ssl'} eq 'authenticated sessions'
|
||||
&& (Bugzilla->user->id
|
||||
|| (defined $method && $method eq 'User.login'));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub correct_urlbase {
|
||||
my $ssl = Bugzilla->params->{'ssl'};
|
||||
return Bugzilla->params->{'urlbase'} if $ssl eq 'never';
|
||||
|
||||
my $sslbase = Bugzilla->params->{'sslbase'};
|
||||
if ($sslbase) {
|
||||
return $sslbase if $ssl eq 'always';
|
||||
# Authenticated Sessions
|
||||
return $sslbase if Bugzilla->user->id;
|
||||
}
|
||||
|
||||
# Set to "authenticated sessions" but nobody's logged in, or
|
||||
# sslbase isn't set.
|
||||
return Bugzilla->params->{'urlbase'};
|
||||
}
|
||||
|
||||
sub lsearch {
|
||||
my ($list,$item) = (@_);
|
||||
my $count = 0;
|
||||
foreach my $i (@$list) {
|
||||
if ($i eq $item) {
|
||||
return $count;
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
sub diff_arrays {
|
||||
my ($old_ref, $new_ref) = @_;
|
||||
|
||||
my @old = @$old_ref;
|
||||
my @new = @$new_ref;
|
||||
|
||||
# For each pair of (old, new) entries:
|
||||
# If they're equal, set them to empty. When done, @old contains entries
|
||||
# that were removed; @new contains ones that got added.
|
||||
foreach my $oldv (@old) {
|
||||
foreach my $newv (@new) {
|
||||
next if ($newv eq '');
|
||||
if ($oldv eq $newv) {
|
||||
$newv = $oldv = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @removed = grep { $_ ne '' } @old;
|
||||
my @added = grep { $_ ne '' } @new;
|
||||
return (\@removed, \@added);
|
||||
}
|
||||
|
||||
sub trim {
|
||||
my ($str) = @_;
|
||||
if ($str) {
|
||||
$str =~ s/^\s+//g;
|
||||
$str =~ s/\s+$//g;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
sub diff_strings {
|
||||
my ($oldstr, $newstr) = @_;
|
||||
|
||||
# Split the old and new strings into arrays containing their values.
|
||||
$oldstr =~ s/[\s,]+/ /g;
|
||||
$newstr =~ s/[\s,]+/ /g;
|
||||
my @old = split(" ", $oldstr);
|
||||
my @new = split(" ", $newstr);
|
||||
|
||||
my ($rem, $add) = diff_arrays(\@old, \@new);
|
||||
|
||||
my $removed = join (", ", @$rem);
|
||||
my $added = join (", ", @$add);
|
||||
|
||||
return ($removed, $added);
|
||||
}
|
||||
|
||||
sub wrap_comment {
|
||||
my ($comment, $cols) = @_;
|
||||
my $wrappedcomment = "";
|
||||
|
||||
# Use 'local', as recommended by Text::Wrap's perldoc.
|
||||
local $Text::Wrap::columns = $cols || COMMENT_COLS;
|
||||
# Make words that are longer than COMMENT_COLS not wrap.
|
||||
local $Text::Wrap::huge = 'overflow';
|
||||
# Don't mess with tabs.
|
||||
local $Text::Wrap::unexpand = 0;
|
||||
|
||||
# If the line starts with ">", don't wrap it. Otherwise, wrap.
|
||||
foreach my $line (split(/\r\n|\r|\n/, $comment)) {
|
||||
if ($line =~ qr/^>/) {
|
||||
$wrappedcomment .= ($line . "\n");
|
||||
}
|
||||
else {
|
||||
# Due to a segfault in Text::Tabs::expand() when processing tabs with
|
||||
# Unicode (see http://rt.perl.org/rt3/Public/Bug/Display.html?id=52104),
|
||||
# we have to remove tabs before processing the comment. This restriction
|
||||
# can go away when we require Perl 5.8.9 or newer.
|
||||
$line =~ s/\t/ /g;
|
||||
$wrappedcomment .= (wrap('', '', $line) . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
chomp($wrappedcomment); # Text::Wrap adds an extra newline at the end.
|
||||
return $wrappedcomment;
|
||||
}
|
||||
|
||||
sub find_wrap_point {
|
||||
my ($string, $maxpos) = @_;
|
||||
if (!$string) { return 0 }
|
||||
if (length($string) < $maxpos) { return length($string) }
|
||||
my $wrappoint = rindex($string, ",", $maxpos); # look for comma
|
||||
if ($wrappoint < 0) { # can't find comma
|
||||
$wrappoint = rindex($string, " ", $maxpos); # look for space
|
||||
if ($wrappoint < 0) { # can't find space
|
||||
$wrappoint = rindex($string, "-", $maxpos); # look for hyphen
|
||||
if ($wrappoint < 0) { # can't find hyphen
|
||||
$wrappoint = $maxpos; # just truncate it
|
||||
} else {
|
||||
$wrappoint++; # leave hyphen on the left side
|
||||
}
|
||||
}
|
||||
}
|
||||
return $wrappoint;
|
||||
}
|
||||
|
||||
sub wrap_hard {
|
||||
my ($string, $columns) = @_;
|
||||
local $Text::Wrap::columns = $columns;
|
||||
local $Text::Wrap::unexpand = 0;
|
||||
local $Text::Wrap::huge = 'wrap';
|
||||
|
||||
my $wrapped = wrap('', '', $string);
|
||||
chomp($wrapped);
|
||||
return $wrapped;
|
||||
}
|
||||
|
||||
sub format_time {
|
||||
my ($date, $format) = @_;
|
||||
|
||||
# If $format is undefined, try to guess the correct date format.
|
||||
my $show_timezone;
|
||||
if (!defined($format)) {
|
||||
if ($date =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) {
|
||||
my $sec = $7;
|
||||
if (defined $sec) {
|
||||
$format = "%Y-%m-%d %T";
|
||||
} else {
|
||||
$format = "%Y-%m-%d %R";
|
||||
}
|
||||
} else {
|
||||
# Default date format. See Date::Format for other formats available.
|
||||
$format = "%Y-%m-%d %R";
|
||||
}
|
||||
# By default, we want the timezone to be displayed.
|
||||
$show_timezone = 1;
|
||||
}
|
||||
else {
|
||||
# Search for %Z or %z, meaning we want the timezone to be displayed.
|
||||
# Till bug 182238 gets fixed, we assume Bugzilla->params->{'timezone'}
|
||||
# is used.
|
||||
$show_timezone = ($format =~ s/\s?%Z$//i);
|
||||
}
|
||||
|
||||
# str2time($date) is undefined if $date has an invalid date format.
|
||||
my $time = str2time($date);
|
||||
|
||||
if (defined $time) {
|
||||
$date = time2str($format, $time);
|
||||
$date .= " " . Bugzilla->params->{'timezone'} if $show_timezone;
|
||||
}
|
||||
else {
|
||||
# Don't let invalid (time) strings to be passed to templates!
|
||||
$date = '';
|
||||
}
|
||||
return trim($date);
|
||||
}
|
||||
|
||||
sub format_time_decimal {
|
||||
my ($time) = (@_);
|
||||
|
||||
my $newtime = sprintf("%.2f", $time);
|
||||
|
||||
if ($newtime =~ /0\Z/) {
|
||||
$newtime = sprintf("%.1f", $time);
|
||||
}
|
||||
|
||||
return $newtime;
|
||||
}
|
||||
|
||||
sub file_mod_time {
|
||||
my ($filename) = (@_);
|
||||
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
|
||||
$atime,$mtime,$ctime,$blksize,$blocks)
|
||||
= stat($filename);
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
sub bz_crypt {
|
||||
my ($password) = @_;
|
||||
|
||||
# The list of characters that can appear in a salt. Salts and hashes
|
||||
# are both encoded as a sequence of characters from a set containing
|
||||
# 64 characters, each one of which represents 6 bits of the salt/hash.
|
||||
# The encoding is similar to BASE64, the difference being that the
|
||||
# BASE64 plus sign (+) is replaced with a forward slash (/).
|
||||
my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
|
||||
|
||||
# Generate the salt. We use an 8 character (48 bit) salt for maximum
|
||||
# security on systems whose crypt uses MD5. Systems with older
|
||||
# versions of crypt will just use the first two characters of the salt.
|
||||
my $salt = '';
|
||||
for ( my $i=0 ; $i < 8 ; ++$i ) {
|
||||
$salt .= $saltchars[rand(64)];
|
||||
}
|
||||
|
||||
# Wide characters cause crypt to die
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::encode($password) if utf8::is_utf8($password);
|
||||
}
|
||||
|
||||
# Crypt the password.
|
||||
my $cryptedpassword = crypt($password, $salt);
|
||||
|
||||
# Return the crypted password.
|
||||
return $cryptedpassword;
|
||||
}
|
||||
|
||||
sub generate_random_password {
|
||||
my $size = shift || 10; # default to 10 chars if nothing specified
|
||||
return join("", map{ ('0'..'9','a'..'z','A'..'Z')[rand 62] } (1..$size));
|
||||
}
|
||||
|
||||
sub validate_email_syntax {
|
||||
my ($addr) = @_;
|
||||
my $match = Bugzilla->params->{'emailregexp'};
|
||||
my $ret = ($addr =~ /$match/ && $addr !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/);
|
||||
if ($ret) {
|
||||
# We assume these checks to suffice to consider the address untainted.
|
||||
trick_taint($_[0]);
|
||||
}
|
||||
return $ret ? 1 : 0;
|
||||
}
|
||||
|
||||
sub validate_date {
|
||||
my ($date) = @_;
|
||||
my $date2;
|
||||
|
||||
# $ts is undefined if the parser fails.
|
||||
my $ts = str2time($date);
|
||||
if ($ts) {
|
||||
$date2 = time2str("%Y-%m-%d", $ts);
|
||||
|
||||
$date =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
|
||||
$date2 =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
|
||||
}
|
||||
my $ret = ($ts && $date eq $date2);
|
||||
return $ret ? 1 : 0;
|
||||
}
|
||||
|
||||
sub validate_time {
|
||||
my ($time) = @_;
|
||||
my $time2;
|
||||
|
||||
# $ts is undefined if the parser fails.
|
||||
my $ts = str2time($time);
|
||||
if ($ts) {
|
||||
$time2 = time2str("%H:%M:%S", $ts);
|
||||
if ($time =~ /^(\d{1,2}):(\d\d)(?::(\d\d))?$/) {
|
||||
$time = sprintf("%02d:%02d:%02d", $1, $2, $3 || 0);
|
||||
}
|
||||
}
|
||||
my $ret = ($ts && $time eq $time2);
|
||||
return $ret ? 1 : 0;
|
||||
}
|
||||
|
||||
sub is_7bit_clean {
|
||||
return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/;
|
||||
}
|
||||
|
||||
sub clean_text {
|
||||
my ($dtext) = shift;
|
||||
$dtext =~ s/[\x00-\x1F\x7F]+/ /g; # change control characters into a space
|
||||
return trim($dtext);
|
||||
}
|
||||
|
||||
sub get_text {
|
||||
my ($name, $vars) = @_;
|
||||
my $template = Bugzilla->template_inner;
|
||||
$vars ||= {};
|
||||
$vars->{'message'} = $name;
|
||||
my $message;
|
||||
$template->process('global/message.txt.tmpl', $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
# Remove the indenting that exists in messages.html.tmpl.
|
||||
$message =~ s/^ //gm;
|
||||
return $message;
|
||||
}
|
||||
|
||||
|
||||
sub get_netaddr {
|
||||
my $ipaddr = shift;
|
||||
|
||||
# Check for a valid IPv4 addr which we know how to parse
|
||||
if (!$ipaddr || $ipaddr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
|
||||
return undef;
|
||||
}
|
||||
|
||||
my $addr = unpack("N", pack("CCCC", split(/\./, $ipaddr)));
|
||||
|
||||
my $maskbits = Bugzilla->params->{'loginnetmask'};
|
||||
|
||||
# Make Bugzilla ignore the IP address if loginnetmask is set to 0
|
||||
return "0.0.0.0" if ($maskbits == 0);
|
||||
|
||||
$addr >>= (32-$maskbits);
|
||||
|
||||
$addr <<= (32-$maskbits);
|
||||
return join(".", unpack("CCCC", pack("N", $addr)));
|
||||
}
|
||||
|
||||
sub disable_utf8 {
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
binmode STDOUT, ':raw'; # Turn off UTF8 encoding.
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Util - Generic utility functions for bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Util;
|
||||
|
||||
# Functions for dealing with variable tainting
|
||||
$rv = is_tainted($var);
|
||||
trick_taint($var);
|
||||
detaint_natural($var);
|
||||
detaint_signed($var);
|
||||
|
||||
# Functions for quoting
|
||||
html_quote($var);
|
||||
url_quote($var);
|
||||
xml_quote($var);
|
||||
|
||||
# Functions for decoding
|
||||
$rv = url_decode($var);
|
||||
|
||||
# Functions that tell you about your environment
|
||||
my $is_cgi = i_am_cgi();
|
||||
my $net_addr = get_netaddr($ip_addr);
|
||||
my $urlbase = correct_urlbase();
|
||||
|
||||
# Functions for searching
|
||||
$loc = lsearch(\@arr, $val);
|
||||
|
||||
# Data manipulation
|
||||
($removed, $added) = diff_arrays(\@old, \@new);
|
||||
|
||||
# Functions for manipulating strings
|
||||
$val = trim(" abc ");
|
||||
($removed, $added) = diff_strings($old, $new);
|
||||
$wrapped = wrap_comment($comment);
|
||||
|
||||
# Functions for formatting time
|
||||
format_time($time);
|
||||
|
||||
# Functions for dealing with files
|
||||
$time = file_mod_time($filename);
|
||||
|
||||
# Cryptographic Functions
|
||||
$crypted_password = bz_crypt($password);
|
||||
$new_password = generate_random_password($password_length);
|
||||
|
||||
# Validation Functions
|
||||
validate_email_syntax($email);
|
||||
validate_date($date);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package contains various utility functions which do not belong anywhere
|
||||
else.
|
||||
|
||||
B<It is not intended as a general dumping group for something which
|
||||
people feel might be useful somewhere, someday>. Do not add methods to this
|
||||
package unless it is intended to be used for a significant number of files,
|
||||
and it does not belong anywhere else.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
This package provides several types of routines:
|
||||
|
||||
=head2 Tainting
|
||||
|
||||
Several functions are available to deal with tainted variables. B<Use these
|
||||
with care> to avoid security holes.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<is_tainted>
|
||||
|
||||
Determines whether a particular variable is tainted
|
||||
|
||||
=item C<trick_taint($val)>
|
||||
|
||||
Tricks perl into untainting a particular variable.
|
||||
|
||||
Use trick_taint() when you know that there is no way that the data
|
||||
in a scalar can be tainted, but taint mode still bails on it.
|
||||
|
||||
B<WARNING!! Using this routine on data that really could be tainted defeats
|
||||
the purpose of taint mode. It should only be used on variables that have been
|
||||
sanity checked in some way and have been determined to be OK.>
|
||||
|
||||
=item C<detaint_natural($num)>
|
||||
|
||||
This routine detaints a natural number. It returns a true value if the
|
||||
value passed in was a valid natural number, else it returns false. You
|
||||
B<MUST> check the result of this routine to avoid security holes.
|
||||
|
||||
=item C<detaint_signed($num)>
|
||||
|
||||
This routine detaints a signed integer. It returns a true value if the
|
||||
value passed in was a valid signed integer, else it returns false. You
|
||||
B<MUST> check the result of this routine to avoid security holes.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Quoting
|
||||
|
||||
Some values may need to be quoted from perl. However, this should in general
|
||||
be done in the template where possible.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<html_quote($val)>
|
||||
|
||||
Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, and E<34> being
|
||||
replaced with their appropriate HTML entities.
|
||||
|
||||
=item C<html_light_quote($val)>
|
||||
|
||||
Returns a string where only explicitly allowed HTML elements and attributes
|
||||
are kept. All HTML elements and attributes not being in the whitelist are either
|
||||
escaped (if HTML::Scrubber is not installed) or removed.
|
||||
|
||||
=item C<url_quote($val)>
|
||||
|
||||
Quotes characters so that they may be included as part of a url.
|
||||
|
||||
=item C<css_class_quote($val)>
|
||||
|
||||
Quotes characters so that they may be used as CSS class names. Spaces
|
||||
are replaced by underscores.
|
||||
|
||||
=item C<xml_quote($val)>
|
||||
|
||||
This is similar to C<html_quote>, except that ' is escaped to '. This
|
||||
is kept separate from html_quote partly for compatibility with previous code
|
||||
(for ') and partly for future handling of non-ASCII characters.
|
||||
|
||||
=item C<url_decode($val)>
|
||||
|
||||
Converts the %xx encoding from the given URL back to its original form.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Environment and Location
|
||||
|
||||
Functions returning information about your environment or location.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<i_am_cgi()>
|
||||
|
||||
Tells you whether or not you are being run as a CGI script in a web
|
||||
server. For example, it would return false if the caller is running
|
||||
in a command-line script.
|
||||
|
||||
=item C<get_netaddr($ipaddr)>
|
||||
|
||||
Given an IP address, this returns the associated network address, using
|
||||
C<Bugzilla->params->{'loginnetmask'}> as the netmask. This can be used
|
||||
to obtain data in order to restrict weak authentication methods (such as
|
||||
cookies) to only some addresses.
|
||||
|
||||
=item C<correct_urlbase()>
|
||||
|
||||
Returns either the C<sslbase> or C<urlbase> parameter, depending on the
|
||||
current setting for the C<ssl> parameter.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Searching
|
||||
|
||||
Functions for searching within a set of values.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<lsearch($list, $item)>
|
||||
|
||||
Returns the position of C<$item> in C<$list>. C<$list> must be a list
|
||||
reference.
|
||||
|
||||
If the item is not in the list, returns -1.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Data Manipulation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<diff_arrays(\@old, \@new)>
|
||||
|
||||
Description: Takes two arrayrefs, and will tell you what it takes to
|
||||
get from @old to @new.
|
||||
Params: @old = array that you are changing from
|
||||
@new = array that you are changing to
|
||||
Returns: A list of two arrayrefs. The first is a reference to an
|
||||
array containing items that were removed from @old. The
|
||||
second is a reference to an array containing items
|
||||
that were added to @old. If both returned arrays are
|
||||
empty, @old and @new contain the same values.
|
||||
|
||||
=back
|
||||
|
||||
=head2 String Manipulation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<trim($str)>
|
||||
|
||||
Removes any leading or trailing whitespace from a string. This routine does not
|
||||
modify the existing string.
|
||||
|
||||
=item C<diff_strings($oldstr, $newstr)>
|
||||
|
||||
Takes two strings containing a list of comma- or space-separated items
|
||||
and returns what items were removed from or added to the new one,
|
||||
compared to the old one. Returns a list, where the first entry is a scalar
|
||||
containing removed items, and the second entry is a scalar containing added
|
||||
items.
|
||||
|
||||
=item C<wrap_hard($string, $size)>
|
||||
|
||||
Wraps a string, so that a line is I<never> longer than C<$size>.
|
||||
Returns the string, wrapped.
|
||||
|
||||
=item C<wrap_comment($comment)>
|
||||
|
||||
Takes a bug comment, and wraps it to the appropriate length. The length is
|
||||
currently specified in C<Bugzilla::Constants::COMMENT_COLS>. Lines beginning
|
||||
with ">" are assumed to be quotes, and they will not be wrapped.
|
||||
|
||||
The intended use of this function is to wrap comments that are about to be
|
||||
displayed or emailed. Generally, wrapped text should not be stored in the
|
||||
database.
|
||||
|
||||
=item C<find_wrap_point($string, $maxpos)>
|
||||
|
||||
Search for a comma, a whitespace or a hyphen to split $string, within the first
|
||||
$maxpos characters. If none of them is found, just split $string at $maxpos.
|
||||
The search starts at $maxpos and goes back to the beginning of the string.
|
||||
|
||||
=item C<is_7bit_clean($str)>
|
||||
|
||||
Returns true is the string contains only 7-bit characters (ASCII 32 through 126,
|
||||
ASCII 10 (LineFeed) and ASCII 13 (Carrage Return).
|
||||
|
||||
=item C<disable_utf8()>
|
||||
|
||||
Disable utf8 on STDOUT (and display raw data instead).
|
||||
|
||||
=item C<clean_text($str)>
|
||||
Returns the parameter "cleaned" by exchanging non-printable characters with spaces.
|
||||
Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).
|
||||
|
||||
=item C<get_text>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This is a method of getting localized strings within Bugzilla code.
|
||||
Use this when you don't want to display a whole template, you just
|
||||
want a particular string.
|
||||
|
||||
It uses the F<global/message.txt.tmpl> template to return a string.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$message> - The identifier for the message.
|
||||
|
||||
=item C<$vars> - A hashref. Any variables you want to pass to the template.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A string.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Formatting Time
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<format_time($time)>
|
||||
|
||||
Takes a time, converts it to the desired format and appends the timezone
|
||||
as defined in editparams.cgi, if desired. This routine will be expanded
|
||||
in the future to adjust for user preferences regarding what timezone to
|
||||
display times in.
|
||||
|
||||
This routine is mainly called from templates to filter dates, see
|
||||
"FILTER time" in Templates.pm. In this case, $format is undefined and
|
||||
the routine has to "guess" the date format that was passed to $dbh->sql_date_format().
|
||||
|
||||
|
||||
=item C<format_time_decimal($time)>
|
||||
|
||||
Returns a number with 2 digit precision, unless the last digit is a 0. Then it
|
||||
returns only 1 digit precision.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Files
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<file_mod_time($filename)>
|
||||
|
||||
Takes a filename and returns the modification time. It returns it in the format
|
||||
of the "mtime" parameter of the perl "stat" function.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Cryptography
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<bz_crypt($password)>
|
||||
|
||||
Takes a string and returns a C<crypt>ed value for it, using a random salt.
|
||||
|
||||
Please always use this function instead of the built-in perl "crypt"
|
||||
when initially encrypting a password.
|
||||
|
||||
=begin undocumented
|
||||
|
||||
Random salts are generated because the alternative is usually
|
||||
to use the first two characters of the password itself, and since
|
||||
the salt appears in plaintext at the beginning of the encrypted
|
||||
password string this has the effect of revealing the first two
|
||||
characters of the password to anyone who views the encrypted version.
|
||||
|
||||
=end undocumented
|
||||
|
||||
=item C<generate_random_password($password_length)>
|
||||
|
||||
Returns an alphanumeric string with the specified length
|
||||
(10 characters by default). Use this function to generate passwords
|
||||
and tokens.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Validation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<validate_email_syntax($email)>
|
||||
|
||||
Do a syntax checking for a legal email address and returns 1 if
|
||||
the check is successful, else returns 0.
|
||||
Untaints C<$email> if successful.
|
||||
|
||||
=item C<validate_date($date)>
|
||||
|
||||
Make sure the date has the correct format and returns 1 if
|
||||
the check is successful, else returns 0.
|
||||
|
||||
=back
|
||||
@@ -1,263 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Version;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Install::Util qw(vers_cmp);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DEFAULT_VERSION => 'unspecified';
|
||||
|
||||
use constant DB_TABLE => 'versions';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
product_id
|
||||
);
|
||||
|
||||
use constant NAME_FIELD => 'value';
|
||||
# This is "id" because it has to be filled in and id is probably the fastest.
|
||||
# We do a custom sort in new_from_list below.
|
||||
use constant LIST_ORDER => 'id';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND value = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
return $class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
sub new_from_list {
|
||||
my $self = shift;
|
||||
my $list = $self->SUPER::new_from_list(@_);
|
||||
return [sort { vers_cmp(lc($a->name), lc($b->name)) } @$list];
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(qq{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE product_id = ? AND version = ?}, undef,
|
||||
($self->product_id, $self->name)) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The version cannot be removed if there are bugs
|
||||
# associated with it.
|
||||
if ($self->bug_count) {
|
||||
ThrowUserError("version_has_bugs", { nb => $self->bug_count });
|
||||
}
|
||||
|
||||
$dbh->do(q{DELETE FROM versions WHERE product_id = ? AND value = ?},
|
||||
undef, ($self->product_id, $self->name));
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my ($name, $product) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$name || ThrowUserError('version_not_specified');
|
||||
|
||||
# Remove unprintable characters
|
||||
$name = clean_text($name);
|
||||
|
||||
return 0 if ($name eq $self->name);
|
||||
my $version = new Bugzilla::Version({ product => $product, name => $name });
|
||||
|
||||
if ($version) {
|
||||
ThrowUserError('version_already_exists',
|
||||
{'name' => $version->name,
|
||||
'product' => $product->name});
|
||||
}
|
||||
|
||||
trick_taint($name);
|
||||
$dbh->do("UPDATE bugs SET version = ?
|
||||
WHERE version = ? AND product_id = ?", undef,
|
||||
($name, $self->name, $self->product_id));
|
||||
|
||||
$dbh->do("UPDATE versions SET value = ?
|
||||
WHERE product_id = ? AND value = ?", undef,
|
||||
($name, $self->product_id, $self->name));
|
||||
|
||||
$self->{'value'} = $name;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
###############################
|
||||
##### Accessors ####
|
||||
###############################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
|
||||
###############################
|
||||
##### Subroutines ###
|
||||
###############################
|
||||
|
||||
sub create {
|
||||
my ($name, $product) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Cleanups and validity checks
|
||||
$name || ThrowUserError('version_blank_name');
|
||||
|
||||
# Remove unprintable characters
|
||||
$name = clean_text($name);
|
||||
|
||||
my $version = new Bugzilla::Version({ product => $product, name => $name });
|
||||
if ($version) {
|
||||
ThrowUserError('version_already_exists',
|
||||
{'name' => $version->name,
|
||||
'product' => $product->name});
|
||||
}
|
||||
|
||||
# Add the new version
|
||||
trick_taint($name);
|
||||
$dbh->do(q{INSERT INTO versions (value, product_id)
|
||||
VALUES (?, ?)}, undef, ($name, $product->id));
|
||||
|
||||
return new Bugzilla::Version($dbh->bz_last_key('versions', 'id'));
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Version - Bugzilla product version class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Version;
|
||||
|
||||
my $version = new Bugzilla::Version(1, 'version_value');
|
||||
|
||||
my $product_id = $version->product_id;
|
||||
my $value = $version->value;
|
||||
|
||||
$version->remove_from_db;
|
||||
|
||||
my $updated = $version->update($version_name, $product);
|
||||
|
||||
my $version = $hash_ref->{'version_value'};
|
||||
|
||||
my $version = Bugzilla::Version::create($version_name, $product);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Version.pm represents a Product Version object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($product_id, $value)>
|
||||
|
||||
Description: The constructor is used to load an existing version
|
||||
by passing a product id and a version value.
|
||||
|
||||
Params: $product_id - Integer with a product id.
|
||||
$value - String with a version value.
|
||||
|
||||
Returns: A Bugzilla::Version object.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the version.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Removes the version from the database.
|
||||
|
||||
Params: none.
|
||||
|
||||
Retruns: none.
|
||||
|
||||
=item C<update($name, $product)>
|
||||
|
||||
Description: Update the value of the version.
|
||||
|
||||
Params: $name - String with the new version value.
|
||||
$product - Bugzilla::Product object the version belongs to.
|
||||
|
||||
Returns: An integer - 1 if the version has been updated, else 0.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<create($version_name, $product)>
|
||||
|
||||
Description: Create a new version for the given product.
|
||||
|
||||
Params: $version_name - String with a version value.
|
||||
$product - A Bugzilla::Product object.
|
||||
|
||||
Returns: A Bugzilla::Version object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,287 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::WebService;
|
||||
|
||||
use strict;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Date::Parse;
|
||||
|
||||
sub fail_unimplemented {
|
||||
my $this = shift;
|
||||
|
||||
die SOAP::Fault
|
||||
->faultcode(ERROR_UNIMPLEMENTED)
|
||||
->faultstring('Service Unimplemented');
|
||||
}
|
||||
|
||||
sub datetime_format {
|
||||
my ($self, $date_string) = @_;
|
||||
|
||||
my $time = str2time($date_string);
|
||||
my ($sec, $min, $hour, $mday, $mon, $year) = localtime $time;
|
||||
# This format string was stolen from SOAP::Utils->format_datetime,
|
||||
# which doesn't work but which has almost the right format string.
|
||||
my $iso_datetime = sprintf('%d%02d%02dT%02d:%02d:%02d',
|
||||
$year + 1900, $mon + 1, $mday, $hour, $min, $sec);
|
||||
return $iso_datetime;
|
||||
}
|
||||
|
||||
sub handle_login {
|
||||
my ($classes, $action, $uri, $method) = @_;
|
||||
|
||||
my $class = $classes->{$uri};
|
||||
eval "require $class";
|
||||
|
||||
return if $class->login_exempt($method);
|
||||
Bugzilla->login();
|
||||
|
||||
# Even though we check for the need to redirect in
|
||||
# Bugzilla->login() we check here again since Bugzilla->login()
|
||||
# does not know what the current XMLRPC method is. Therefore
|
||||
# ssl_require_redirect in Bugzilla->login() will have returned
|
||||
# false if system was configured to redirect for authenticated
|
||||
# sessions and the user was not yet logged in.
|
||||
# So here we pass in the method name to ssl_require_redirect so
|
||||
# it can then check for the extra case where the method equals
|
||||
# User.login, which we would then need to redirect if not
|
||||
# over a secure connection.
|
||||
my $full_method = $uri . "." . $method;
|
||||
Bugzilla->cgi->require_https(Bugzilla->params->{'sslbase'})
|
||||
if ssl_require_redirect($full_method);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# For some methods, we shouldn't call Bugzilla->login before we call them
|
||||
use constant LOGIN_EXEMPT => { };
|
||||
|
||||
sub login_exempt {
|
||||
my ($class, $method) = @_;
|
||||
|
||||
return $class->LOGIN_EXEMPT->{$method};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
package Bugzilla::WebService::XMLRPC::Transport::HTTP::CGI;
|
||||
use strict;
|
||||
eval { require XMLRPC::Transport::HTTP; };
|
||||
our @ISA = qw(XMLRPC::Transport::HTTP::CGI);
|
||||
|
||||
sub initialize {
|
||||
my $self = shift;
|
||||
my %retval = $self->SUPER::initialize(@_);
|
||||
$retval{'serializer'} = Bugzilla::WebService::XMLRPC::Serializer->new;
|
||||
return %retval;
|
||||
}
|
||||
|
||||
sub make_response {
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::make_response(@_);
|
||||
|
||||
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
|
||||
# its cookies in Bugzilla::CGI, so we need to copy them over.
|
||||
foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
|
||||
$self->response->headers->push_header('Set-Cookie', $_);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# This package exists to fix a UTF-8 bug in SOAP::Lite.
|
||||
# See http://rt.cpan.org/Public/Bug/Display.html?id=32952.
|
||||
package Bugzilla::WebService::XMLRPC::Serializer;
|
||||
use strict;
|
||||
# We can't use "use base" because XMLRPC::Serializer doesn't return
|
||||
# a true value.
|
||||
eval { require XMLRPC::Lite; };
|
||||
our @ISA = qw(XMLRPC::Serializer);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
# This fixes UTF-8.
|
||||
$self->{'_typelookup'}->{'base64'} =
|
||||
[10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/},
|
||||
'as_base64'];
|
||||
# This makes arrays work right even though we're a subclass.
|
||||
# (See http://rt.cpan.org//Ticket/Display.html?id=34514)
|
||||
$self->{'_encodingStyle'} = '';
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
my ($value) = @_;
|
||||
# Something weird happens with XML::Parser when we have upper-ASCII
|
||||
# characters encoded as UTF-8, and this fixes it.
|
||||
utf8::encode($value) if utf8::is_utf8($value)
|
||||
&& $value =~ /^[\x00-\xff]+$/;
|
||||
return $self->SUPER::as_string($value);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::WebService - The Web Service interface to Bugzilla
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is the standard API for external programs that want to interact
|
||||
with Bugzilla. It provides various methods in various modules.
|
||||
|
||||
Currently the only method of accessing the API is via XML-RPC. The XML-RPC
|
||||
standard is described here: L<http://www.xmlrpc.com/spec>
|
||||
|
||||
The endpoint for Bugzilla WebServices is the C<xmlrpc.cgi> script in
|
||||
your Bugzilla installation. For example, if your Bugzilla is at
|
||||
C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
|
||||
API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
|
||||
|
||||
=head1 CALLING METHODS
|
||||
|
||||
Methods are called in the normal XML-RPC fashion. Bugzilla does not currently
|
||||
implement any extensions to the standard method of XML-RPC method calling.
|
||||
|
||||
Methods are grouped into "packages", like C<Bug> for
|
||||
L<Bugzilla::WebService::Bug>. So, for example,
|
||||
L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get> in XML-RPC.
|
||||
|
||||
=head1 PARAMETERS
|
||||
|
||||
In addition to the standard parameter types like C<int>, C<string>, etc.,
|
||||
XML-RPC has two data structures, a C<< <struct> >> and an C<< <array> >>.
|
||||
|
||||
=head2 Structs
|
||||
|
||||
In Perl, we call a C<< <struct> >> a "hash" or a "hashref". You may see
|
||||
us refer to it that way in the API documentation.
|
||||
|
||||
In example code, you will see the characters C<{> and C<}> used to represent
|
||||
the beginning and end of structs.
|
||||
|
||||
For example, here's a struct in XML-RPC:
|
||||
|
||||
<struct>
|
||||
<member>
|
||||
<name>fruit</name>
|
||||
<value><string>oranges</string></value>
|
||||
</member>
|
||||
<member>
|
||||
<name>vegetable</name>
|
||||
<value><string>lettuce</string></value>
|
||||
</member>
|
||||
</struct>
|
||||
|
||||
In our example code in these API docs, that would look like:
|
||||
|
||||
{ fruit => 'oranges', vegetable => 'lettuce' }
|
||||
|
||||
=head2 Arrays
|
||||
|
||||
In example code, you will see the characters C<[> and C<]> used to
|
||||
represent the beginning and end of arrays.
|
||||
|
||||
For example, here's an array in XML-RPC:
|
||||
|
||||
<array>
|
||||
<data>
|
||||
<value><i4>1</i4></value>
|
||||
<value><i4>2</i4></value>
|
||||
<value><i4>3</i4></value>
|
||||
</data>
|
||||
</array>
|
||||
|
||||
In our example code in these API docs, that would look like:
|
||||
|
||||
[1, 2, 3]
|
||||
|
||||
=head2 How Bugzilla WebService Methods Take Parameters
|
||||
|
||||
B<All> Bugzilla WebServices functions take their parameters in
|
||||
a C<< <struct> >>. Another way of saying this would be: All functions
|
||||
take a single argument, a C<< <struct> >> that contains all parameters.
|
||||
The names of the parameters listed in the API docs for each function are
|
||||
the C<name> element for the struct C<member>s.
|
||||
|
||||
=head1 LOGGING IN
|
||||
|
||||
You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
|
||||
user. This issues standard HTTP cookies that you must then use in future
|
||||
calls, so your XML-RPC client must be capable of receiving and transmitting
|
||||
cookies.
|
||||
|
||||
=head1 STABLE, EXPERIMENTAL, and UNSTABLE
|
||||
|
||||
Methods are marked B<STABLE> if you can expect their parameters and
|
||||
return values not to change between versions of Bugzilla. You are
|
||||
best off always using methods marked B<STABLE>. We may add parameters
|
||||
and additional items to the return values, but your old code will
|
||||
always continue to work with any new changes we make. If we ever break
|
||||
a B<STABLE> interface, we'll post a big notice in the Release Notes,
|
||||
and it will only happen during a major new release.
|
||||
|
||||
Methods (or parts of methods) are marked B<EXPERIMENTAL> if
|
||||
we I<believe> they will be stable, but there's a slight chance that
|
||||
small parts will change in the future.
|
||||
|
||||
Certain parts of a method's description may be marked as B<UNSTABLE>,
|
||||
in which case those parts are not guaranteed to stay the same between
|
||||
Bugzilla versions.
|
||||
|
||||
=head1 ERRORS
|
||||
|
||||
If a particular webservice call fails, it will throw a standard XML-RPC
|
||||
error. There will be a numeric error code, and then the description
|
||||
field will contain descriptive text of the error. Each error that Bugzilla
|
||||
can throw has a specific code that will not change between versions of
|
||||
Bugzilla.
|
||||
|
||||
The various errors that functions can throw are specified by the
|
||||
documentation of those functions.
|
||||
|
||||
If your code needs to know what error Bugzilla threw, use the numeric
|
||||
code. Don't try to parse the description, because that may change
|
||||
from version to version of Bugzilla.
|
||||
|
||||
Note that if you display the error to the user in an HTML program, make
|
||||
sure that you properly escape the error, as it will not be HTML-escaped.
|
||||
|
||||
=head2 Transient vs. Fatal Errors
|
||||
|
||||
If the error code is a number greater than 0, the error is considered
|
||||
"transient," which means that it was an error made by the user, not
|
||||
some problem with Bugzilla itself.
|
||||
|
||||
If the error code is a number less than 0, the error is "fatal," which
|
||||
means that it's some error in Bugzilla itself that probably requires
|
||||
administrative attention.
|
||||
|
||||
Negative numbers and positive numbers don't overlap. That is, if there's
|
||||
an error 302, there won't be an error -302.
|
||||
|
||||
=head2 Unknown Errors
|
||||
|
||||
Sometimes a function will throw an error that doesn't have a specific
|
||||
error code. In this case, the code will be C<-32000> if it's a "fatal"
|
||||
error, and C<32000> if it's a "transient" error.
|
||||
@@ -1,583 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
# Tsahi Asher <tsahi_75@yahoo.com>
|
||||
|
||||
package Bugzilla::WebService::Bug;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::BugMail;
|
||||
use Bugzilla::Util qw(trim);
|
||||
|
||||
#############
|
||||
# Constants #
|
||||
#############
|
||||
|
||||
# This maps the names of internal Bugzilla bug fields to things that would
|
||||
# make sense to somebody who's not intimately familiar with the inner workings
|
||||
# of Bugzilla. (These are the field names that the WebService uses.)
|
||||
use constant FIELD_MAP => {
|
||||
status => 'bug_status',
|
||||
severity => 'bug_severity',
|
||||
description => 'comment',
|
||||
summary => 'short_desc',
|
||||
platform => 'rep_platform',
|
||||
};
|
||||
|
||||
use constant GLOBAL_SELECT_FIELDS => qw(
|
||||
bug_severity
|
||||
bug_status
|
||||
op_sys
|
||||
priority
|
||||
rep_platform
|
||||
resolution
|
||||
);
|
||||
|
||||
use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
|
||||
|
||||
######################################################
|
||||
# Add aliases here for old method name compatibility #
|
||||
######################################################
|
||||
|
||||
BEGIN { *get_bugs = \&get }
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
sub get {
|
||||
my ($self, $params) = @_;
|
||||
my $ids = $params->{ids};
|
||||
defined $ids || ThrowCodeError('param_required', { param => 'ids' });
|
||||
|
||||
my @return;
|
||||
foreach my $bug_id (@$ids) {
|
||||
ValidateBugID($bug_id);
|
||||
my $bug = new Bugzilla::Bug($bug_id);
|
||||
|
||||
# Timetracking fields are deleted if the user doesn't belong to
|
||||
# the corresponding group.
|
||||
unless (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
|
||||
delete $bug->{'estimated_time'};
|
||||
delete $bug->{'remaining_time'};
|
||||
delete $bug->{'deadline'};
|
||||
}
|
||||
# This is done in this fashion in order to produce a stable API.
|
||||
# The internals of Bugzilla::Bug are not stable enough to just
|
||||
# return them directly.
|
||||
my $creation_ts = $self->datetime_format($bug->creation_ts);
|
||||
my $delta_ts = $self->datetime_format($bug->delta_ts);
|
||||
my %item;
|
||||
$item{'creation_time'} = type('dateTime')->value($creation_ts);
|
||||
$item{'last_change_time'} = type('dateTime')->value($delta_ts);
|
||||
$item{'internals'} = $bug;
|
||||
$item{'id'} = type('int')->value($bug->bug_id);
|
||||
$item{'summary'} = type('string')->value($bug->short_desc);
|
||||
|
||||
if (Bugzilla->params->{'usebugaliases'}) {
|
||||
$item{'alias'} = type('string')->value($bug->alias);
|
||||
}
|
||||
else {
|
||||
# For API reasons, we always want the value to appear, we just
|
||||
# don't want it to have a value if aliases are turned off.
|
||||
$item{'alias'} = undef;
|
||||
}
|
||||
|
||||
push(@return, \%item);
|
||||
}
|
||||
|
||||
return { bugs => \@return };
|
||||
}
|
||||
|
||||
|
||||
sub create {
|
||||
my ($self, $params) = @_;
|
||||
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
|
||||
my %field_values;
|
||||
foreach my $field (keys %$params) {
|
||||
my $field_name = FIELD_MAP->{$field} || $field;
|
||||
$field_values{$field_name} = $params->{$field};
|
||||
}
|
||||
|
||||
# WebService users can't set the creation date of a bug.
|
||||
delete $field_values{'creation_ts'};
|
||||
|
||||
my $bug = Bugzilla::Bug->create(\%field_values);
|
||||
|
||||
Bugzilla::BugMail::Send($bug->bug_id, { changer => $bug->reporter->login });
|
||||
|
||||
return { id => type('int')->value($bug->bug_id) };
|
||||
}
|
||||
|
||||
sub legal_values {
|
||||
my ($self, $params) = @_;
|
||||
my $field = FIELD_MAP->{$params->{field}} || $params->{field};
|
||||
|
||||
my @custom_select = Bugzilla->get_fields(
|
||||
{custom => 1, type => [FIELD_TYPE_SINGLE_SELECT, FIELD_TYPE_MULTI_SELECT]});
|
||||
# We only want field names.
|
||||
@custom_select = map {$_->name} @custom_select;
|
||||
|
||||
my $values;
|
||||
if (grep($_ eq $field, GLOBAL_SELECT_FIELDS, @custom_select)) {
|
||||
$values = get_legal_field_values($field);
|
||||
}
|
||||
elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
|
||||
my $id = $params->{product_id};
|
||||
defined $id || ThrowCodeError('param_required',
|
||||
{ function => 'Bug.legal_values', param => 'product_id' });
|
||||
grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
|
||||
|| ThrowUserError('product_access_denied', { product => $id });
|
||||
|
||||
my $product = new Bugzilla::Product($id);
|
||||
my @objects;
|
||||
if ($field eq 'version') {
|
||||
@objects = @{$product->versions};
|
||||
}
|
||||
elsif ($field eq 'target_milestone') {
|
||||
@objects = @{$product->milestones};
|
||||
}
|
||||
elsif ($field eq 'component') {
|
||||
@objects = @{$product->components};
|
||||
}
|
||||
|
||||
$values = [map { $_->name } @objects];
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('invalid_field_name', { field => $params->{field} });
|
||||
}
|
||||
|
||||
my @result;
|
||||
foreach my $val (@$values) {
|
||||
push(@result, type('string')->value($val));
|
||||
}
|
||||
|
||||
return { values => \@result };
|
||||
}
|
||||
|
||||
sub add_comment {
|
||||
my ($self, $params) = @_;
|
||||
|
||||
#The user must login in order add a comment
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
|
||||
# Check parameters
|
||||
defined $params->{id}
|
||||
|| ThrowCodeError('param_required', { param => 'id' });
|
||||
ValidateBugID($params->{id});
|
||||
|
||||
my $comment = $params->{comment};
|
||||
(defined $comment && trim($comment) ne '')
|
||||
|| ThrowCodeError('param_required', { param => 'comment' });
|
||||
|
||||
my $bug = new Bugzilla::Bug($params->{id});
|
||||
|
||||
Bugzilla->user->can_edit_product($bug->product_id)
|
||||
|| ThrowUserError("product_edit_denied", {product => $bug->product});
|
||||
|
||||
# Append comment
|
||||
$bug->add_comment($comment, { isprivate => $params->{private},
|
||||
work_time => $params->{work_time} });
|
||||
$bug->update();
|
||||
|
||||
# Send mail.
|
||||
Bugzilla::BugMail::Send($bug->bug_id, { changer => Bugzilla->user->login });
|
||||
return undef;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::Bug - The API for creating, changing, and getting the
|
||||
details of bugs.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to file a new bug in Bugzilla,
|
||||
or get information about bugs that have already been filed.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 Utility Functions
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Tells you what values are allowed for a particular field.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<field> - The name of the field you want information about.
|
||||
This should be the same as the name you would use in L</create>, below.
|
||||
|
||||
=item C<product_id> - If you're picking a product-specific field, you have
|
||||
to specify the id of the product you want the values for.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
C<values> - An array of strings: the legal values for this field.
|
||||
The values will be sorted as they normally would be in Bugzilla.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 106 (Invalid Product)
|
||||
|
||||
You were required to specify a product, and either you didn't, or you
|
||||
specified an invalid product (or a product that you can't access).
|
||||
|
||||
=item 108 (Invalid Field Name)
|
||||
|
||||
You specified a field that doesn't exist or isn't a drop-down field.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=back
|
||||
|
||||
=head2 Bug Information
|
||||
|
||||
=over
|
||||
|
||||
=item C<get>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets information about particular bugs in the database.
|
||||
|
||||
Note: Can also be called as "get_bugs" for compatibilty with Bugzilla 3.0 API.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<ids>
|
||||
|
||||
An array of numbers and strings.
|
||||
|
||||
If an element in the array is entirely numeric, it represents a bug_id
|
||||
from the Bugzilla database to fetch. If it contains any non-numeric
|
||||
characters, it is considered to be a bug alias instead, and the bug with
|
||||
that alias will be loaded.
|
||||
|
||||
Note that it's possible for aliases to be disabled in Bugzilla, in which
|
||||
case you will be told that you have specified an invalid bug_id if you
|
||||
try to specify an alias. (It will be error 100.)
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing a single element, C<bugs>. This is an array of hashes.
|
||||
Each hash contains the following items:
|
||||
|
||||
=over
|
||||
|
||||
=item id
|
||||
|
||||
C<int> The numeric bug_id of this bug.
|
||||
|
||||
=item alias
|
||||
|
||||
C<string> The alias of this bug. If there is no alias or aliases are
|
||||
disabled in this Bugzilla, this will be an empty string.
|
||||
|
||||
=item summary
|
||||
|
||||
C<string> The summary of this bug.
|
||||
|
||||
=item creation_time
|
||||
|
||||
C<dateTime> When the bug was created.
|
||||
|
||||
=item last_change_time
|
||||
|
||||
C<dateTime> When the bug was last changed.
|
||||
|
||||
=item internals B<UNSTABLE>
|
||||
|
||||
A hash. The internals of a L<Bugzilla::Bug> object. This is extremely
|
||||
unstable, and you should only rely on this if you absolutely have to. The
|
||||
structure of the hash may even change between point releases of Bugzilla.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 100 (Invalid Bug Alias)
|
||||
|
||||
If you specified an alias and either: (a) the Bugzilla you're querying
|
||||
doesn't support aliases or (b) there is no bug with that alias.
|
||||
|
||||
=item 101 (Invalid Bug ID)
|
||||
|
||||
The bug_id you specified doesn't exist in the database.
|
||||
|
||||
=item 102 (Access Denied)
|
||||
|
||||
You do not have access to the bug_id you specified.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Bug Creation and Modification
|
||||
|
||||
=over
|
||||
|
||||
=item C<create> B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This allows you to create a new bug in Bugzilla. If you specify any
|
||||
invalid fields, they will be ignored. If you specify any fields you
|
||||
are not allowed to set, they will just be set to their defaults or ignored.
|
||||
|
||||
You cannot currently set all the items here that you can set on enter_bug.cgi.
|
||||
|
||||
The WebService interface may allow you to set things other than those listed
|
||||
here, but realize that anything undocumented is B<UNSTABLE> and will very
|
||||
likely change in the future.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
Some params must be set, or an error will be thrown. These params are
|
||||
marked B<Required>.
|
||||
|
||||
Some parameters can have defaults set in Bugzilla, by the administrator.
|
||||
If these parameters have defaults set, you can omit them. These parameters
|
||||
are marked B<Defaulted>.
|
||||
|
||||
Clients that want to be able to interact uniformly with multiple
|
||||
Bugzillas should always set both the params marked B<Required> and those
|
||||
marked B<Defaulted>, because some Bugzillas may not have defaults set
|
||||
for B<Defaulted> parameters, and then this method will throw an error
|
||||
if you don't specify them.
|
||||
|
||||
The descriptions of the parameters below are what they mean when Bugzilla is
|
||||
being used to track software bugs. They may have other meanings in some
|
||||
installations.
|
||||
|
||||
=over
|
||||
|
||||
=item C<product> (string) B<Required> - The name of the product the bug
|
||||
is being filed against.
|
||||
|
||||
=item C<component> (string) B<Required> - The name of a component in the
|
||||
product above.
|
||||
|
||||
=item C<summary> (string) B<Required> - A brief description of the bug being
|
||||
filed.
|
||||
|
||||
=item C<version> (string) B<Required> - A version of the product above;
|
||||
the version the bug was found in.
|
||||
|
||||
=item C<description> (string) B<Defaulted> - The initial description for
|
||||
this bug. Some Bugzilla installations require this to not be blank.
|
||||
|
||||
=item C<op_sys> (string) B<Defaulted> - The operating system the bug was
|
||||
discovered on.
|
||||
|
||||
=item C<platform> (string) B<Defaulted> - What type of hardware the bug was
|
||||
experienced on.
|
||||
|
||||
=item C<priority> (string) B<Defaulted> - What order the bug will be fixed
|
||||
in by the developer, compared to the developer's other bugs.
|
||||
|
||||
=item C<severity> (string) B<Defaulted> - How severe the bug is.
|
||||
|
||||
=item C<alias> (string) - A brief alias for the bug that can be used
|
||||
instead of a bug number when accessing this bug. Must be unique in
|
||||
all of this Bugzilla.
|
||||
|
||||
=item C<assigned_to> (username) - A user to assign this bug to, if you
|
||||
don't want it to be assigned to the component owner.
|
||||
|
||||
=item C<cc> (array) - An array of usernames to CC on this bug.
|
||||
|
||||
=item C<qa_contact> (username) - If this installation has QA Contacts
|
||||
enabled, you can set the QA Contact here if you don't want to use
|
||||
the component's default QA Contact.
|
||||
|
||||
=item C<status> (string) - The status that this bug should start out as.
|
||||
Note that only certain statuses can be set on bug creation.
|
||||
|
||||
=item C<target_milestone> (string) - A valid target milestone for this
|
||||
product.
|
||||
|
||||
=back
|
||||
|
||||
In addition to the above parameters, if your installation has any custom
|
||||
fields, you can set them just by passing in the name of the field and
|
||||
its value as a string.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with one element, C<id>. This is the id of the newly-filed bug.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 51 (Invalid Object)
|
||||
|
||||
The component you specified is not valid for this Product.
|
||||
|
||||
=item 103 (Invalid Alias)
|
||||
|
||||
The alias you specified is invalid for some reason. See the error message
|
||||
for more details.
|
||||
|
||||
=item 104 (Invalid Field)
|
||||
|
||||
One of the drop-down fields has an invalid value, or a value entered in a
|
||||
text field is too long. The error message will have more detail.
|
||||
|
||||
=item 105 (Invalid Component)
|
||||
|
||||
You didn't specify a component.
|
||||
|
||||
=item 106 (Invalid Product)
|
||||
|
||||
Either you didn't specify a product, this product doesn't exist, or
|
||||
you don't have permission to enter bugs in this product.
|
||||
|
||||
=item 107 (Invalid Summary)
|
||||
|
||||
You didn't specify a summary for the bug.
|
||||
|
||||
=item 504 (Invalid User)
|
||||
|
||||
Either the QA Contact, Assignee, or CC lists have some invalid user
|
||||
in them. The error message will have more details.
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Before B<3.0.4>, parameters marked as B<Defaulted> were actually
|
||||
B<Required>, due to a bug in Bugzilla.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<add_comment>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This allows you to add a comment to a bug in Bugzilla.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<id> (int) B<Required> - The id or alias of the bug to append a
|
||||
comment to.
|
||||
|
||||
=item C<comment> (string) B<Required> - The comment to append to the bug.
|
||||
If this is empty or all whitespace, an error will be thrown saying that
|
||||
you did not set the C<comment> parameter.
|
||||
|
||||
=item C<private> (boolean) - If set to true, the comment is private, otherwise
|
||||
it is assumed to be public.
|
||||
|
||||
=item C<work_time> (double) - Adds this many hours to the "Hours Worked"
|
||||
on the bug. If you are not in the time tracking group, this value will
|
||||
be ignored.
|
||||
|
||||
|
||||
=back
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 100 (Invalid Bug Alias)
|
||||
|
||||
If you specified an alias and either: (a) the Bugzilla you're querying
|
||||
doesn't support aliases or (b) there is no bug with that alias.
|
||||
|
||||
=item 101 (Invalid Bug ID)
|
||||
|
||||
The id you specified doesn't exist in the database.
|
||||
|
||||
=item 108 (Bug Edit Denied)
|
||||
|
||||
You did not have the necessary rights to edit the bug.
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.2>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=back
|
||||
@@ -1,149 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::Bugzilla;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Hook;
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
use Time::Zone;
|
||||
|
||||
# Basic info that is needed before logins
|
||||
use constant LOGIN_EXEMPT => {
|
||||
timezone => 1,
|
||||
version => 1,
|
||||
};
|
||||
|
||||
sub version {
|
||||
return { version => type('string')->value(BUGZILLA_VERSION) };
|
||||
}
|
||||
|
||||
sub extensions {
|
||||
my $extensions = Bugzilla::Hook::enabled_plugins();
|
||||
foreach my $name (keys %$extensions) {
|
||||
my $info = $extensions->{$name};
|
||||
foreach my $data (keys %$info)
|
||||
{
|
||||
$extensions->{$name}->{$data} = type('string')->value($info->{$data});
|
||||
}
|
||||
}
|
||||
return { extensions => $extensions };
|
||||
}
|
||||
|
||||
sub timezone {
|
||||
my $offset = tz_offset();
|
||||
$offset = (($offset / 60) / 60) * 100;
|
||||
$offset = sprintf('%+05d', $offset);
|
||||
return { timezone => type('string')->value($offset) };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::WebService::Bugzilla - Global functions for the webservice interface.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This provides functions that tell you about Bugzilla in general.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=over
|
||||
|
||||
=item C<version>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns the current version of Bugzilla.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<version>, that is the version as a
|
||||
string.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<extensions>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets information about the extensions that are currently installed and enabled
|
||||
in this Bugzilla.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<extesions>. This points to a hash. I<That> hash
|
||||
contains the names of extensions as keys, and information about the extension
|
||||
as values. One of the values that must be returned is the 'version' of the
|
||||
extension
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.2>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<timezone>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns the timezone of the server Bugzilla is running on. This is
|
||||
important because all dates/times that the webservice interface
|
||||
returns will be in this timezone.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<timezone>, that is the timezone offset as a
|
||||
string in (+/-)XXXX (RFC 2822) format.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
@@ -1,110 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::WebService::Constants;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::WebService::Constants::EXPORT = qw(
|
||||
WS_ERROR_CODE
|
||||
ERROR_UNKNOWN_FATAL
|
||||
ERROR_UNKNOWN_TRANSIENT
|
||||
|
||||
ERROR_AUTH_NODATA
|
||||
ERROR_UNIMPLEMENTED
|
||||
);
|
||||
|
||||
# This maps the error names in global/*-error.html.tmpl to numbers.
|
||||
# Generally, transient errors should have a number above 0, and
|
||||
# fatal errors should have a number below 0.
|
||||
#
|
||||
# This hash should generally contain any error that could be thrown
|
||||
# by the WebService interface. If it's extremely unlikely that the
|
||||
# error could be thrown (like some CodeErrors), it doesn't have to
|
||||
# be listed here.
|
||||
#
|
||||
# "Transient" means "If you resubmit that request with different data,
|
||||
# it may work."
|
||||
#
|
||||
# "Fatal" means, "There's something wrong with Bugzilla, probably
|
||||
# something an administrator would have to fix."
|
||||
#
|
||||
# NOTE: Numbers must never be recycled. If you remove a number, leave a
|
||||
# comment that it was retired. Also, if an error changes its name, you'll
|
||||
# have to fix it here.
|
||||
use constant WS_ERROR_CODE => {
|
||||
# Generic Bugzilla::Object errors are 50-99.
|
||||
object_name_not_specified => 50,
|
||||
param_required => 50,
|
||||
object_does_not_exist => 51,
|
||||
# Bug errors usually occupy the 100-200 range.
|
||||
improper_bug_id_field_value => 100,
|
||||
bug_id_does_not_exist => 101,
|
||||
bug_access_denied => 102,
|
||||
bug_access_query => 102,
|
||||
# These all mean "invalid alias"
|
||||
alias_too_long => 103,
|
||||
alias_in_use => 103,
|
||||
alias_is_numeric => 103,
|
||||
alias_has_comma_or_space => 103,
|
||||
# Misc. bug field errors
|
||||
illegal_field => 104,
|
||||
freetext_too_long => 104,
|
||||
# Component errors
|
||||
require_component => 105,
|
||||
component_name_too_long => 105,
|
||||
# Invalid Product
|
||||
no_products => 106,
|
||||
entry_access_denied => 106,
|
||||
product_access_denied => 106,
|
||||
product_disabled => 106,
|
||||
# Invalid Summary
|
||||
require_summary => 107,
|
||||
# Invalid field name
|
||||
invalid_field_name => 108,
|
||||
# Not authorized to edit the bug
|
||||
product_edit_denied => 109,
|
||||
|
||||
# Authentication errors are usually 300-400.
|
||||
invalid_username_or_password => 300,
|
||||
account_disabled => 301,
|
||||
auth_invalid_email => 302,
|
||||
extern_id_conflict => -303,
|
||||
|
||||
# User errors are 500-600.
|
||||
account_exists => 500,
|
||||
illegal_email_address => 501,
|
||||
account_creation_disabled => 501,
|
||||
account_creation_restricted => 501,
|
||||
password_too_short => 502,
|
||||
password_too_long => 503,
|
||||
invalid_username => 504,
|
||||
# This is from strict_isolation, but it also basically means
|
||||
# "invalid user."
|
||||
invalid_user_group => 504,
|
||||
};
|
||||
|
||||
# These are the fallback defaults for errors not in ERROR_CODE.
|
||||
use constant ERROR_UNKNOWN_FATAL => -32000;
|
||||
use constant ERROR_UNKNOWN_TRANSIENT => 32000;
|
||||
|
||||
use constant ERROR_AUTH_NODATA => 410;
|
||||
use constant ERROR_UNIMPLEMENTED => 910;
|
||||
use constant ERROR_GENERAL => 999;
|
||||
|
||||
1;
|
||||
@@ -1,198 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::Product;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::User;
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
##################################################
|
||||
# Add aliases here for method name compatibility #
|
||||
##################################################
|
||||
|
||||
BEGIN { *get_products = \&get }
|
||||
|
||||
# Get the ids of the products the user can search
|
||||
sub get_selectable_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]};
|
||||
}
|
||||
|
||||
# Get the ids of the products the user can enter bugs against
|
||||
sub get_enterable_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_enterable_products}]};
|
||||
}
|
||||
|
||||
# Get the union of the products the user can search and enter bugs against.
|
||||
sub get_accessible_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
|
||||
}
|
||||
|
||||
# Get a list of actual products, based on list of ids
|
||||
sub get {
|
||||
my ($self, $params) = @_;
|
||||
|
||||
# Only products that are in the users accessible products,
|
||||
# can be allowed to be returned
|
||||
my $accessible_products = Bugzilla->user->get_accessible_products;
|
||||
|
||||
# Create a hash with the ids the user wants
|
||||
my %ids = map { $_ => 1 } @{$params->{ids}};
|
||||
|
||||
# Return the intersection of this, by grepping the ids from
|
||||
# accessible products.
|
||||
my @requested_accessible = grep { $ids{$_->id} } @$accessible_products;
|
||||
|
||||
# Now create a result entry for each.
|
||||
my @products =
|
||||
map {{
|
||||
internals => $_,
|
||||
id => type('int')->value($_->id),
|
||||
name => type('string')->value($_->name),
|
||||
description => type('string')->value($_->description),
|
||||
}
|
||||
} @requested_accessible;
|
||||
|
||||
return { products => \@products };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::Product - The Product API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to list the available Products and
|
||||
get information about them.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 List Products
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_selectable_products>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can search on.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get_enterable_products>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can enter bugs
|
||||
against.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get_accessible_products>
|
||||
|
||||
B<UNSTABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can search or enter
|
||||
bugs against.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of information about the products passed to it.
|
||||
|
||||
Note: Can also be called as "get_products" for compatibilty with Bugzilla 3.0 API.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
A hash containing one item, C<ids>, that is an array of product ids.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<products>, that is an array of
|
||||
hashes. Each hash describes a product, and has the following items:
|
||||
C<id>, C<name>, C<description>, and C<internals>. The C<id> item is
|
||||
the id of the product. The C<name> item is the name of the
|
||||
product. The C<description> is the description of the
|
||||
product. Finally, the C<internals> is an internal representation of
|
||||
the product.
|
||||
|
||||
Note, that if the user tries to access a product that is not in the
|
||||
list of accessible products for the user, or a product that does not
|
||||
exist, that is silently ignored, and no information about that product
|
||||
is returned.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
# -*- 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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::User;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
|
||||
import SOAP::Data qw(type);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util qw(trim);
|
||||
use Bugzilla::Token;
|
||||
|
||||
# Don't need auth to login
|
||||
use constant LOGIN_EXEMPT => {
|
||||
login => 1,
|
||||
offer_account_by_email => 1,
|
||||
};
|
||||
|
||||
##############
|
||||
# User Login #
|
||||
##############
|
||||
|
||||
sub login {
|
||||
my ($self, $params) = @_;
|
||||
my $remember = $params->{remember};
|
||||
|
||||
# Username and password params are required
|
||||
foreach my $param ("login", "password") {
|
||||
defined $params->{$param}
|
||||
|| ThrowCodeError('param_required', { param => $param });
|
||||
}
|
||||
|
||||
# Convert $remember from a boolean 0/1 value to a CGI-compatible one.
|
||||
if (defined($remember)) {
|
||||
$remember = $remember? 'on': '';
|
||||
}
|
||||
else {
|
||||
# Use Bugzilla's default if $remember is not supplied.
|
||||
$remember =
|
||||
Bugzilla->params->{'rememberlogin'} eq 'defaulton'? 'on': '';
|
||||
}
|
||||
|
||||
# Make sure the CGI user info class works if necessary.
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->param('Bugzilla_login', $params->{login});
|
||||
$cgi->param('Bugzilla_password', $params->{password});
|
||||
$cgi->param('Bugzilla_remember', $remember);
|
||||
|
||||
Bugzilla->login;
|
||||
return { id => type('int')->value(Bugzilla->user->id) };
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my $self = shift;
|
||||
Bugzilla->logout;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#################
|
||||
# User Creation #
|
||||
#################
|
||||
|
||||
sub offer_account_by_email {
|
||||
my $self = shift;
|
||||
my ($params) = @_;
|
||||
my $email = trim($params->{email})
|
||||
|| ThrowCodeError('param_required', { param => 'email' });
|
||||
|
||||
my $createexp = Bugzilla->params->{'createemailregexp'};
|
||||
if (!$createexp) {
|
||||
ThrowUserError("account_creation_disabled");
|
||||
}
|
||||
elsif ($email !~ /$createexp/) {
|
||||
ThrowUserError("account_creation_restricted");
|
||||
}
|
||||
|
||||
$email = Bugzilla::User->check_login_name_for_creation($email);
|
||||
|
||||
# Create and send a token for this new account.
|
||||
Bugzilla::Token::issue_new_user_account_token($email);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub create {
|
||||
my $self = shift;
|
||||
my ($params) = @_;
|
||||
|
||||
Bugzilla->user->in_group('editusers')
|
||||
|| ThrowUserError("auth_failure", { group => "editusers",
|
||||
action => "add",
|
||||
object => "users"});
|
||||
|
||||
my $email = trim($params->{email})
|
||||
|| ThrowCodeError('param_required', { param => 'email' });
|
||||
my $realname = trim($params->{full_name});
|
||||
my $password = trim($params->{password}) || '*';
|
||||
|
||||
my $user = Bugzilla::User->create({
|
||||
login_name => $email,
|
||||
realname => $realname,
|
||||
cryptpassword => $password
|
||||
});
|
||||
|
||||
return { id => type('int')->value($user->id) };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::User - The User Account and Login API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to create User Accounts and
|
||||
log in/out using an existing account.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 Logging In and Out
|
||||
|
||||
=over
|
||||
|
||||
=item C<login>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Logging in, with a username and password, is required for many
|
||||
Bugzilla installations, in order to search for bugs, post new bugs,
|
||||
etc. This method logs in an user.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<login> (string) - The user's login name.
|
||||
|
||||
=item C<password> (string) - The user's password.
|
||||
|
||||
=item C<remember> (bool) B<Optional> - if the cookies returned by the
|
||||
call to login should expire with the session or not. In order for
|
||||
this option to have effect the Bugzilla server must be configured to
|
||||
allow the user to set this option - the Bugzilla parameter
|
||||
I<rememberlogin> must be set to "defaulton" or
|
||||
"defaultoff". Addionally, the client application must implement
|
||||
management of cookies across sessions.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
On success, a hash containing one item, C<id>, the numeric id of the
|
||||
user that was logged in. A set of http cookies is also sent with the
|
||||
response. These cookies must be sent along with any future requests
|
||||
to the webservice, for the duration of the session.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 300 (Invalid Username or Password)
|
||||
|
||||
The username does not exist, or the password is wrong.
|
||||
|
||||
=item 301 (Account Disabled)
|
||||
|
||||
The account has been disabled. A reason may be specified with the
|
||||
error.
|
||||
|
||||
=item 50 (Param Required)
|
||||
|
||||
A login or password parameter was not provided.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<logout>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Log out the user. Does nothing if there is no user logged in.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Account Creation
|
||||
|
||||
=over
|
||||
|
||||
=item C<offer_account_by_email>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Sends an email to the user, offering to create an account. The user
|
||||
will have to click on a URL in the email, and choose their password
|
||||
and real name.
|
||||
|
||||
This is the recommended way to create a Bugzilla account.
|
||||
|
||||
=item B<Param>
|
||||
|
||||
=over
|
||||
|
||||
=item C<email> (string) - the email to send the offer to.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 500 (Illegal Email Address)
|
||||
|
||||
This Bugzilla does not allow you to create accounts with the format of
|
||||
email address you specified. Account creation may be entirely disabled.
|
||||
|
||||
=item 501 (Account Already Exists)
|
||||
|
||||
An account with that email address already exists in Bugzilla.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<create>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Creates a user account directly in Bugzilla, password and all.
|
||||
Instead of this, you should use L</offer_account_by_email> when
|
||||
possible, because that makes sure that the email address specified can
|
||||
actually receive an email. This function does not check that.
|
||||
|
||||
You must be logged in and have the C<editusers> privilege in order to
|
||||
call this function.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<email> (string) - The email address for the new user.
|
||||
|
||||
=item C<full_name> (string) B<Optional> - The user's full name. Will
|
||||
be set to empty if not specified.
|
||||
|
||||
=item C<password> (string) B<Optional> - The password for the new user
|
||||
account, in plain text. It will be stripped of leading and trailing
|
||||
whitespace. If blank or not specified, the newly created account will
|
||||
exist in Bugzilla, but will not be allowed to log in using DB
|
||||
authentication until a password is set either by the user (through
|
||||
resetting their password) or by the administrator.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<id>, the numeric id of the user that was
|
||||
created.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
The same as L</offer_account_by_email>. If a password is specified,
|
||||
the function may also throw:
|
||||
|
||||
=over
|
||||
|
||||
=item 502 (Password Too Short)
|
||||
|
||||
The password specified is too short. (Usually, this means the
|
||||
password is under three characters.)
|
||||
|
||||
=item 503 (Password Too Long)
|
||||
|
||||
The password specified is too long. (Usually, this means the
|
||||
password is over ten characters.)
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.4>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
@@ -1,84 +0,0 @@
|
||||
Bugzilla Quick Start Guide
|
||||
==========================
|
||||
(or, how to get Bugzilla up and running in 10 steps)
|
||||
Christian Reis <kiko@async.com.br>
|
||||
|
||||
This express installation guide is for "normal" Bugzilla installations,
|
||||
which means a Linux or Unix system on which Apache, Perl, MySQL or PostgreSQL
|
||||
and a Sendmail compatible MTA are available. For other configurations, please
|
||||
see Section 4 of the Bugzilla Guide in the docs/ directory.
|
||||
|
||||
1. Decide from which URL and directory under your webserver root you
|
||||
will be serving the Bugzilla webpages.
|
||||
|
||||
2. Unpack the distribution into the chosen directory (there is no copying or
|
||||
installation involved).
|
||||
|
||||
3. Run ./checksetup.pl, look for unsolved requirements, and install them.
|
||||
You can run checksetup as many times as necessary to check if
|
||||
everything required has been installed.
|
||||
|
||||
These will usually include assorted Perl modules, MySQL or PostgreSQL,
|
||||
and a MTA.
|
||||
|
||||
After a successful dependency check, checksetup should complain that
|
||||
localconfig needs to be edited.
|
||||
|
||||
4. Edit the localconfig file, in particular the $webservergroup and
|
||||
$db_* variables. In particular, $db_name and $db_user will define
|
||||
your database setup in step 5.
|
||||
|
||||
5. Using the name you provided as $db_name above, create a MySQL database
|
||||
for Bugzilla. You should also create a user permission for the name
|
||||
supplied as $db_user with read/write access to that database.
|
||||
|
||||
If you are not familiar with MySQL permissions, it's a good idea to
|
||||
use the mysql_setpermission script that is installed with the MySQL
|
||||
distribution, and be sure to read Bugzilla Security - MySQL section
|
||||
in the Bugzilla Guide or PostgreSQL documentation.
|
||||
|
||||
6. Run checksetup.pl once more; if all goes well, it should set up the
|
||||
Bugzilla database for you. If not, return to step 5.
|
||||
|
||||
checksetup.pl should ask you, this time, for the administrator's
|
||||
email address and password. These will be used for the initial
|
||||
Bugzilla administrator account.
|
||||
|
||||
7. Configure Apache (or install and configure, if you don't have it up
|
||||
yet) to point to the Bugzilla directory. You should enable and
|
||||
activate mod_cgi, and add the configuration entries
|
||||
|
||||
Options +ExecCGI
|
||||
AllowOverride Limit
|
||||
DirectoryIndex index.cgi
|
||||
|
||||
to your Bugzilla <Directory> block. You may also need
|
||||
|
||||
AddHandler cgi-script .cgi
|
||||
|
||||
if you don't have that in your Apache configuration file yet.
|
||||
|
||||
8. Visit the URL you chose for Bugzilla. Your browser should display the
|
||||
default Bugzilla home page. You should then log in as the
|
||||
administrator by following the "Log in" link and supplying the
|
||||
account information you provided in step 6.
|
||||
|
||||
9. Scroll to the bottom of the page after logging in, and select
|
||||
"Parameters". Set up the relevant parameters for your local setup.
|
||||
|
||||
See section 4.2 of the Bugzilla Guide for a in-depth description of
|
||||
some of the configuration parameters available.
|
||||
|
||||
10. That's it. If anything unexpected comes up:
|
||||
|
||||
- read the error message carefully,
|
||||
- backtrack through the steps above,
|
||||
- check the official installation guide, which is section 4 in the
|
||||
Bugzilla Guide, included in the docs/ directory in various
|
||||
formats.
|
||||
|
||||
Support and installation questions should be directed to the
|
||||
mozilla-webtools@mozilla.org mailing list -- don't write to the
|
||||
developer mailing list: your post *will* be ignored if you do.
|
||||
|
||||
Further support information is at http://www.bugzilla.org/support/
|
||||
@@ -1,19 +0,0 @@
|
||||
* This README is no longer used to house installation instructions. Instead,
|
||||
it contains pointers to where you may find the information you need.
|
||||
|
||||
* A quick installation guide is provided in the QUICKSTART file.
|
||||
|
||||
* Complete installation instructions are found in docs/, with a
|
||||
variety of document types available. Please refer to these documents
|
||||
when installing, configuring, and maintaining your Bugzilla
|
||||
installation. A helpful starting point is docs/txt/Bugzilla-Guide.txt,
|
||||
or with a web browser at docs/html/index.html.
|
||||
|
||||
* Release notes for people upgrading to a new version of Bugzilla are
|
||||
available at docs/rel_notes.txt.
|
||||
|
||||
* If you wish to contribute to the documentation, please read docs/README.docs.
|
||||
|
||||
* The Bugzilla web site is at "http://www.bugzilla.org/". This site will
|
||||
contain the latest Bugzilla information, including how to report bugs and how
|
||||
to get help with Bugzilla.
|
||||
@@ -1,3 +0,0 @@
|
||||
Please consult The Bugzilla Guide for instructions on how to upgrade
|
||||
Bugzilla from an older version. The Guide can be found with this
|
||||
distribution, in docs/html, docs/txt, and docs/sgml.
|
||||
@@ -1,412 +0,0 @@
|
||||
This file contains only important changes made to Bugzilla before release
|
||||
2.8. If you are upgrading from version older than 2.8, please read this file.
|
||||
If you are upgrading from 2.8 or newer, please read the Installation and
|
||||
Upgrade instructions in The Bugzilla Guide, found with this distribution in
|
||||
docs/html, docs/txt, and docs/sgml.
|
||||
|
||||
Please note that the period in our version numbers is a place separator, not
|
||||
a decimal point. The 14 in version 2.14 is newer than the 8 in 2.8, for
|
||||
example. You should only be using this file if you have a single digit
|
||||
after the period in the version 2.x Bugzilla you are upgrading from.
|
||||
|
||||
For a complete list of what changes, use Bonsai
|
||||
(http://cvs-mirror.mozilla.org/webtools/bonsai/cvsqueryform.cgi) to
|
||||
query the CVS tree. For example,
|
||||
|
||||
http://cvs-mirror.mozilla.org/webtools/bonsai/cvsquery.cgi?module=all&branch=HEAD&branchtype=match&dir=mozilla%2Fwebtools%2Fbugzilla&file=&filetype=match&who=&whotype=match&sortby=Date&hours=2&date=week&mindate=&maxdate=&cvsroot=%2Fcvsroot
|
||||
|
||||
will tell you what has been changed in the last week.
|
||||
|
||||
|
||||
10/12/99 The CHANGES file is now obsolete! There is a new file called
|
||||
checksetup.pl. You should get in the habit of running that file every time
|
||||
you update your installation of Bugzilla. That file will be constantly
|
||||
updated to automatically update your installation to match any code changes.
|
||||
If you're curious as to what is going on, changes are commented in that file,
|
||||
at the end.
|
||||
|
||||
Many thanks to Holger Schurig <holgerschurig@nikocity.de> for writing this
|
||||
script!
|
||||
|
||||
|
||||
|
||||
10/11/99 Restructured voting database to add a cached value in each
|
||||
bug recording how many total votes that bug has. While I'm at it, I
|
||||
removed the unused "area" field from the bugs database. It is
|
||||
distressing to realize that the bugs table has reached the maximum
|
||||
number of indices allowed by MySQL (16), which may make future
|
||||
enhancements awkward.
|
||||
|
||||
You must feed the following to MySQL:
|
||||
|
||||
alter table bugs drop column area;
|
||||
alter table bugs add column votes mediumint not null, add index (votes);
|
||||
|
||||
You then *must* delete the data/versioncache file when you make this
|
||||
change, as it contains references to the "area" field. Deleting it is safe,
|
||||
bugzilla will correctly regenerate it.
|
||||
|
||||
If you have been using the voting feature at all, then you will then
|
||||
need to update the voting cache. You can do this by visiting the
|
||||
sanitycheck.cgi page, and taking it up on its offer to rebuild the
|
||||
votes stuff.
|
||||
|
||||
|
||||
10/7/99 Added voting ability. You must run the new script
|
||||
"makevotestable.sh". You must also feed the following to mysql:
|
||||
|
||||
alter table products add column votesperuser smallint not null;
|
||||
|
||||
|
||||
|
||||
9/15/99 Apparently, newer alphas of MySQL won't allow you to have
|
||||
"when" as a column name. So, I have had to rename a column in the
|
||||
bugs_activity table. You must feed the below to mysql or you won't
|
||||
work at all.
|
||||
|
||||
alter table bugs_activity change column when bug_when datetime not null;
|
||||
|
||||
|
||||
8/16/99 Added "OpenVMS" to the list of OS's. Feed this to mysql:
|
||||
|
||||
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "OpenVMS", "other") not null;
|
||||
|
||||
6/22/99 Added an entry to the attachments table to record who the submitter
|
||||
was. Nothing uses this yet, but it still should be recorded.
|
||||
|
||||
alter table attachments add column submitter_id mediumint not null;
|
||||
|
||||
You should also run this script to populate the new field:
|
||||
|
||||
#!/usr/bin/perl -w
|
||||
use diagnostics;
|
||||
use strict;
|
||||
require "globals.pl";
|
||||
$|=1;
|
||||
ConnectToDatabase();
|
||||
SendSQL("select bug_id, attach_id from attachments order by bug_id");
|
||||
my @list;
|
||||
while (MoreSQLData()) {
|
||||
my @row = FetchSQLData();
|
||||
push(@list, \@row);
|
||||
}
|
||||
foreach my $ref (@list) {
|
||||
my ($bug, $attach) = (@$ref);
|
||||
SendSQL("select long_desc from bugs where bug_id = $bug");
|
||||
my $comment = FetchOneColumn() . "Created an attachment (id=$attach)";
|
||||
|
||||
if ($comment =~ m@-* Additional Comments From ([^ ]*)[- 0-9/:]*\nCreated an attachment \(id=$attach\)@) {
|
||||
print "Found $1\n";
|
||||
SendSQL("select userid from profiles where login_name=" .
|
||||
SqlQuote($1));
|
||||
my $userid = FetchOneColumn();
|
||||
if (defined $userid && $userid > 0) {
|
||||
SendSQL("update attachments set submitter_id=$userid where attach_id = $attach");
|
||||
}
|
||||
} else {
|
||||
print "Bug $bug can't find comment for attachment $attach\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
6/14/99 Added "BeOS" to the list of OS's. Feed this to mysql:
|
||||
|
||||
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "other") not null;
|
||||
|
||||
|
||||
5/27/99 Added support for dependency information. You must run the new
|
||||
"makedependenciestable.sh" script. You can turn off dependencies with the new
|
||||
"usedependencies" param, but it defaults to being on. Also, read very
|
||||
carefully the description for the new "webdotbase" param; you will almost
|
||||
certainly need to tweak it.
|
||||
|
||||
|
||||
5/24/99 Added "Mac System 8.6" and "Neutrino" to the list of OS's.
|
||||
Feed this to mysql:
|
||||
|
||||
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "other") not null;
|
||||
|
||||
|
||||
5/12/99 Added a pref to control how much email you get. This needs a new
|
||||
column in the profiles table, so feed the following to mysql:
|
||||
|
||||
alter table profiles add column emailnotification enum("ExcludeSelfChanges", "CConly", "All") not null default "ExcludeSelfChanges";
|
||||
|
||||
5/5/99 Added the ability to search by creation date. To make this perform
|
||||
well, you ought to do the following:
|
||||
|
||||
alter table bugs change column creation_ts creation_ts datetime not null, add index (creation_ts);
|
||||
|
||||
|
||||
4/30/99 Added a new severity, "blocker". To get this into your running
|
||||
Bugzilla, do the following:
|
||||
|
||||
alter table bugs change column bug_severity bug_severity enum("blocker", "critical", "major", "normal", "minor", "trivial", "enhancement") not null;
|
||||
|
||||
|
||||
4/22/99 There was a bug where the long descriptions of bugs had a variety of
|
||||
newline characters at the end, depending on the operating system of the browser
|
||||
that submitted the text. This bug has been fixed, so that no further changes
|
||||
like that will happen. But to fix problems that have already crept into your
|
||||
database, you can run the following perl script (which is slow and ugly, but
|
||||
does work:)
|
||||
#!/usr/bin/perl -w
|
||||
use diagnostics;
|
||||
use strict;
|
||||
require "globals.pl";
|
||||
$|=1;
|
||||
ConnectToDatabase();
|
||||
SendSQL("select bug_id from bugs order by bug_id");
|
||||
my @list;
|
||||
while (MoreSQLData()) {
|
||||
push(@list, FetchOneColumn());
|
||||
}
|
||||
foreach my $id (@list) {
|
||||
if ($id % 50 == 0) {
|
||||
print "\n$id ";
|
||||
}
|
||||
SendSQL("select long_desc from bugs where bug_id = $id");
|
||||
my $comment = FetchOneColumn();
|
||||
my $orig = $comment;
|
||||
$comment =~ s/\r\n/\n/g; # Get rid of windows-style line endings.
|
||||
$comment =~ s/\r/\n/g; # Get rid of mac-style line endings.
|
||||
if ($comment ne $orig) {
|
||||
SendSQL("update bugs set long_desc = " . SqlQuote($comment) .
|
||||
" where bug_id = $id");
|
||||
print ".";
|
||||
} else {
|
||||
print "-";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
4/8/99 Added ability to store patches with bugs. This requires a new table
|
||||
to store the data, so you will need to run the "makeattachmenttable.sh" script.
|
||||
|
||||
3/25/99 Unfortunately, the HTML::FromText CPAN module had too many bugs, and
|
||||
so I had to roll my own. We no longer use the HTML::FromText CPAN module.
|
||||
|
||||
3/24/99 (This entry has been removed. It used to say that we required the
|
||||
HTML::FromText CPAN module, but that's no longer true.)
|
||||
|
||||
3/22/99 Added the ability to query by fields which have changed within a date
|
||||
range. To make this perform a bit better, we need a new index:
|
||||
|
||||
alter table bugs_activity add index (field);
|
||||
|
||||
3/10/99 Added 'groups' stuff, where we have different group bits that we can
|
||||
put on a person or on a bug. Some of the group bits control access to bugzilla
|
||||
features. And a person can't access a bug unless he has every group bit set
|
||||
that is also set on the bug. See the comments in makegroupstable.sh for a bit
|
||||
more info.
|
||||
|
||||
The 'maintainer' param is now used only as an email address for people to send
|
||||
complaints to. The groups table is what is now used to determine permissions.
|
||||
|
||||
You will need to run the new script "makegroupstable.sh". And then you need to
|
||||
feed the following lines to MySQL (replace XXX with the login name of the
|
||||
maintainer, the person you wish to be all-powerful).
|
||||
|
||||
alter table bugs add column groupset bigint not null;
|
||||
alter table profiles add column groupset bigint not null;
|
||||
update profiles set groupset=0x7fffffffffffffff where login_name = XXX;
|
||||
|
||||
|
||||
|
||||
3/8/99 Added params to control how priorities are set in a new bug. You can
|
||||
now choose whether to let submitters of new bugs choose a priority, or whether
|
||||
they should just accept the default priority (which is now no longer hardcoded
|
||||
to "P2", but is instead a param.) The default value of the params will cause
|
||||
the same behavior as before.
|
||||
|
||||
3/3/99 Added a "disallownew" field to the products table. If non-zero, then
|
||||
don't let people file new bugs against this product. (This is for when a
|
||||
product is retired, but you want to keep the bug reports around for posterity.)
|
||||
Feed this to MySQL:
|
||||
|
||||
alter table products add column disallownew tinyint not null;
|
||||
|
||||
|
||||
2/8/99 Added FreeBSD to the list of OS's. Feed this to MySQL:
|
||||
|
||||
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
|
||||
|
||||
|
||||
2/4/99 Added a new column "description" to the components table, and added
|
||||
links to a new page which will use this to describe the components of a
|
||||
given product. Feed this to MySQL:
|
||||
|
||||
alter table components add column description mediumtext not null;
|
||||
|
||||
|
||||
2/3/99 Added a new column "initialqacontact" to the components table that gives
|
||||
an initial QA contact field. It may be empty if you wish the initial qa
|
||||
contact to be empty. If you're not using the QA contact field, you don't need
|
||||
to add this column, but you might as well be safe and add it anyway:
|
||||
|
||||
alter table components add column initialqacontact tinytext not null;
|
||||
|
||||
|
||||
2/2/99 Added a new column "milestoneurl" to the products table that gives a URL
|
||||
which is to describe the currently defined milestones for a product. If you
|
||||
don't use target milestone, you might be able to get away without adding this
|
||||
column, but you might as well be safe and add it anyway:
|
||||
|
||||
alter table products add column milestoneurl tinytext not null;
|
||||
|
||||
|
||||
1/29/99 Whoops; had a misspelled op_sys. It was "Mac System 7.1.6"; it should
|
||||
be "Mac System 7.6.1". It turns out I had no bugs with this value set, so I
|
||||
could just do the below simple command. If you have bugs with this value, you
|
||||
may need to do something more complicated.
|
||||
|
||||
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
|
||||
|
||||
|
||||
|
||||
1/20/99 Added new fields: Target Milestone, QA Contact, and Status Whiteboard.
|
||||
These fields are all optional in the UI; there are parameters to turn them on.
|
||||
However, whether or not you use them, the fields need to be in the DB. There
|
||||
is some code that needs them, even if you don't.
|
||||
|
||||
To update your DB to have these fields, send the following to MySQL:
|
||||
|
||||
alter table bugs add column target_milestone varchar(20) not null,
|
||||
add column qa_contact mediumint not null,
|
||||
add column status_whiteboard mediumtext not null,
|
||||
add index (target_milestone), add index (qa_contact);
|
||||
|
||||
|
||||
|
||||
1/18/99 You can now query by CC. To make this perform reasonably, the CC table
|
||||
needs some indices. The following MySQL does the necessary stuff:
|
||||
|
||||
alter table cc add index (bug_id), add index (who);
|
||||
|
||||
|
||||
1/15/99 The op_sys field can now be queried by (and more easily tweaked).
|
||||
To make this perform reasonably, it needs an index. The following MySQL
|
||||
command will create the necessary index:
|
||||
|
||||
alter table bugs add index (op_sys);
|
||||
|
||||
|
||||
12/2/98 The op_sys and rep_platform fields have been tweaked. op_sys
|
||||
is now an enum, rather than having the legal values all hard-coded in
|
||||
perl. rep_platform now no longer allows a value of "X-Windows".
|
||||
|
||||
Here's how I ported to the new world. This ought to work for you too.
|
||||
Actually, it's probably overkill. I had a lot of illegal values for op_sys
|
||||
in my tables, from importing bugs from strange places. If you haven't done
|
||||
anything funky, then much of the below will be a no-op.
|
||||
|
||||
First, send the following commands to MySQL to make sure all your values for
|
||||
rep_platform and op_sys are legal in the new world..
|
||||
|
||||
update bugs set rep_platform="Sun" where rep_platform="X-Windows" and op_sys like "Solaris%";
|
||||
update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "IRIX";
|
||||
update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "HP-UX";
|
||||
update bugs set rep_platform="DEC" where rep_platform="X-Windows" and op_sys = "OSF/1";
|
||||
update bugs set rep_platform="PC" where rep_platform="X-Windows" and op_sys = "Linux";
|
||||
update bugs set rep_platform="other" where rep_platform="X-Windows";
|
||||
update bugs set rep_platform="other" where rep_platform="";
|
||||
update bugs set op_sys="Mac System 7" where op_sys="System 7";
|
||||
update bugs set op_sys="Mac System 7.5" where op_sys="System 7.5";
|
||||
update bugs set op_sys="Mac System 8.0" where op_sys="8.0";
|
||||
update bugs set op_sys="OSF/1" where op_sys="Digital Unix 4.0";
|
||||
update bugs set op_sys="IRIX" where op_sys like "IRIX %";
|
||||
update bugs set op_sys="HP-UX" where op_sys like "HP-UX %";
|
||||
update bugs set op_sys="Windows NT" where op_sys like "NT %";
|
||||
update bugs set op_sys="OSF/1" where op_sys like "OSF/1 %";
|
||||
update bugs set op_sys="Solaris" where op_sys like "Solaris %";
|
||||
update bugs set op_sys="SunOS" where op_sys like "SunOS%";
|
||||
update bugs set op_sys="other" where op_sys = "Motif";
|
||||
update bugs set op_sys="other" where op_sys = "Other";
|
||||
|
||||
Next, send the following commands to make sure you now have only legal
|
||||
entries in your table. If either of the queries do not come up empty, then
|
||||
you have to do more stuff like the above.
|
||||
|
||||
select bug_id,op_sys,rep_platform from bugs where rep_platform not regexp "^(All|DEC|HP|Macintosh|PC|SGI|Sun|X-Windows|Other)$";
|
||||
select bug_id,op_sys,rep_platform from bugs where op_sys not regexp "^(All|Windows 3.1|Windows 95|Windows 98|Windows NT|Mac System 7|Mac System 7.5|Mac System 7.1.6|Mac System 8.0|AIX|BSDI|HP-UX|IRIX|Linux|OSF/1|Solaris|SunOS|other)$";
|
||||
|
||||
Finally, once that's all clear, alter the table to make enforce the new legal
|
||||
entries:
|
||||
|
||||
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.1.6", "Mac System 8.0", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "other") not null, change column rep_platform rep_platform enum("All", "DEC", "HP", "Macintosh", "PC", "SGI", "Sun", "Other");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
11/20/98 Added searching of CC field. To better support this, added
|
||||
some indexes to the CC table. You probably want to execute the following
|
||||
mysql commands:
|
||||
|
||||
alter table cc add index (bug_id);
|
||||
alter table cc add index (who);
|
||||
|
||||
|
||||
10/27/98 security check for legal products in place. bug charts are not
|
||||
available as an option if collectstats.pl has never been run. all products
|
||||
get daily stats collected now. README updated: Chart::Base is listed as
|
||||
a requirement, instructions for using collectstats.pl included as
|
||||
an optional step. also got silly and added optional quips to bug
|
||||
reports.
|
||||
|
||||
10/17/98 modified README installation instructions slightly.
|
||||
|
||||
10/7/98 Added a new table called "products". Right now, this is used
|
||||
only to have a description for each product, and that description is
|
||||
only used when initially adding a new bug. Anyway, you *must* create
|
||||
the new table (which you can do by running the new makeproducttable.sh
|
||||
script). If you just leave it empty, things will work much as they
|
||||
did before, or you can add descriptions for some or all of your
|
||||
products.
|
||||
|
||||
|
||||
9/15/98 Everything has been ported to Perl. NO MORE TCL. This
|
||||
transition should be relatively painless, except for the "params"
|
||||
file. This is the file that contains parameters you've set up on the
|
||||
editparams.cgi page. Before changing to Perl, this was a tcl-syntax
|
||||
file, stored in the same directory as the code; after the change to
|
||||
Perl, it becomes a perl-syntax file, stored in a subdirectory named
|
||||
"data". See the README file for more details on what version of Perl
|
||||
you need.
|
||||
|
||||
So, if updating from an older version of Bugzilla, you will need to
|
||||
edit data/param, change the email address listed for
|
||||
$::param{'maintainer'}, and then go revisit the editparams.cgi page
|
||||
and reset all the parameters to your taste. Fortunately, your old
|
||||
params file will still be around, and so you ought to be able to
|
||||
cut&paste important bits from there.
|
||||
|
||||
Also, note that the "whineatnews" script has changed name (it now has
|
||||
an extension of .pl instead of .tcl), so you'll need to change your
|
||||
cron job.
|
||||
|
||||
And the "comments" file has been moved to the data directory. Just do
|
||||
"cat comments >> data/comments" to restore any old comments that may
|
||||
have been lost.
|
||||
|
||||
|
||||
|
||||
9/2/98 Changed the way password validation works. We now keep a
|
||||
crypt'd version of the password in the database, and check against
|
||||
that. (This is silly, because we're also keeping the plaintext
|
||||
version there, but I have plans...) Stop passing the plaintext
|
||||
password around as a cookie; instead, we have a cookie that references
|
||||
a record in a new database table, logincookies.
|
||||
|
||||
IMPORTANT: if updating from an older version of Bugzilla, you must run
|
||||
the following commands to keep things working:
|
||||
|
||||
./makelogincookiestable.sh
|
||||
echo "alter table profiles add column cryptpassword varchar(64);" | mysql bugs
|
||||
echo "update profiles set cryptpassword = encrypt(password,substring(rand(),3, 4));" | mysql bugs
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/perl -wT
|
||||
# -*- 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 Frédéric Buclin.
|
||||
# Portions created by Frédéric Buclin are Copyright (C) 2007
|
||||
# Frédéric Buclin. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
use lib qw(. lib);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
my $user = Bugzilla->login(LOGIN_REQUIRED);
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
$user->in_group('admin')
|
||||
|| $user->in_group('tweakparams')
|
||||
|| $user->in_group('editusers')
|
||||
|| $user->can_bless
|
||||
|| (Bugzilla->params->{'useclassification'} && $user->in_group('editclassifications'))
|
||||
|| $user->in_group('editcomponents')
|
||||
|| scalar(@{$user->get_products_by_permission('editcomponents')})
|
||||
|| $user->in_group('creategroups')
|
||||
|| $user->in_group('editkeywords')
|
||||
|| $user->in_group('bz_canusewhines')
|
||||
|| ThrowUserError('auth_failure', {action => 'access', object => 'administrative_pages'});
|
||||
|
||||
$template->process('admin/admin.html.tmpl')
|
||||
|| ThrowTemplateError($template->error());
|
||||
@@ -1,716 +0,0 @@
|
||||
#!/usr/bin/perl -wT
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
# Daniel Raichle <draichle@gmx.net>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Alexander J. Vincent <ajvincent@juno.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Greg Hendricks <ghendricks@novell.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
################################################################################
|
||||
# Script Initialization
|
||||
################################################################################
|
||||
|
||||
# Make it harder for us to do dangerous things in Perl.
|
||||
use strict;
|
||||
|
||||
use lib qw(. lib);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Flag;
|
||||
use Bugzilla::FlagType;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Attachment;
|
||||
use Bugzilla::Attachment::PatchReader;
|
||||
use Bugzilla::Token;
|
||||
use Bugzilla::Keyword;
|
||||
|
||||
Bugzilla->login();
|
||||
|
||||
# For most scripts we don't make $cgi and $template global variables. But
|
||||
# when preparing Bugzilla for mod_perl, this script used these
|
||||
# variables in so many subroutines that it was easier to just
|
||||
# make them globals.
|
||||
local our $cgi = Bugzilla->cgi;
|
||||
local our $template = Bugzilla->template;
|
||||
local our $vars = {};
|
||||
|
||||
################################################################################
|
||||
# Main Body Execution
|
||||
################################################################################
|
||||
|
||||
# All calls to this script should contain an "action" variable whose
|
||||
# value determines what the user wants to do. The code below checks
|
||||
# the value of that variable and runs the appropriate code. If none is
|
||||
# supplied, we default to 'view'.
|
||||
|
||||
# Determine whether to use the action specified by the user or the default.
|
||||
my $action = $cgi->param('action') || 'view';
|
||||
|
||||
# Determine if PatchReader is installed
|
||||
eval {
|
||||
require PatchReader;
|
||||
$vars->{'patchviewerinstalled'} = 1;
|
||||
};
|
||||
|
||||
if ($action eq "view")
|
||||
{
|
||||
view();
|
||||
}
|
||||
elsif ($action eq "interdiff")
|
||||
{
|
||||
interdiff();
|
||||
}
|
||||
elsif ($action eq "diff")
|
||||
{
|
||||
diff();
|
||||
}
|
||||
elsif ($action eq "viewall")
|
||||
{
|
||||
viewall();
|
||||
}
|
||||
elsif ($action eq "enter")
|
||||
{
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
enter();
|
||||
}
|
||||
elsif ($action eq "insert")
|
||||
{
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
insert();
|
||||
}
|
||||
elsif ($action eq "edit")
|
||||
{
|
||||
edit();
|
||||
}
|
||||
elsif ($action eq "update")
|
||||
{
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
update();
|
||||
}
|
||||
elsif ($action eq "delete") {
|
||||
delete_attachment();
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowCodeError("unknown_action", { action => $action });
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
################################################################################
|
||||
# Data Validation / Security Authorization
|
||||
################################################################################
|
||||
|
||||
# Validates an attachment ID. Optionally takes a parameter of a form
|
||||
# variable name that contains the ID to be validated. If not specified,
|
||||
# uses 'id'.
|
||||
#
|
||||
# Will throw an error if 1) attachment ID is not a valid number,
|
||||
# 2) attachment does not exist, or 3) user isn't allowed to access the
|
||||
# attachment.
|
||||
#
|
||||
# Returns an attachment object.
|
||||
|
||||
sub validateID {
|
||||
my $param = @_ ? $_[0] : 'id';
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
# If we're not doing interdiffs, check if id wasn't specified and
|
||||
# prompt them with a page that allows them to choose an attachment.
|
||||
# Happens when calling plain attachment.cgi from the urlbar directly
|
||||
if ($param eq 'id' && !$cgi->param('id')) {
|
||||
print $cgi->header();
|
||||
$template->process("attachment/choose.html.tmpl", $vars) ||
|
||||
ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
|
||||
my $attach_id = $cgi->param($param);
|
||||
|
||||
# Validate the specified attachment id. detaint kills $attach_id if
|
||||
# non-natural, so use the original value from $cgi in our exception
|
||||
# message here.
|
||||
detaint_natural($attach_id)
|
||||
|| ThrowUserError("invalid_attach_id", { attach_id => $cgi->param($param) });
|
||||
|
||||
# Make sure the attachment exists in the database.
|
||||
my $attachment = Bugzilla::Attachment->get($attach_id)
|
||||
|| ThrowUserError("invalid_attach_id", { attach_id => $attach_id });
|
||||
|
||||
# Make sure the user is authorized to access this attachment's bug.
|
||||
ValidateBugID($attachment->bug_id);
|
||||
if ($attachment->isprivate && $user->id != $attachment->attacher->id && !$user->is_insider) {
|
||||
ThrowUserError('auth_failure', {action => 'access',
|
||||
object => 'attachment'});
|
||||
}
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
# Validates format of a diff/interdiff. Takes a list as an parameter, which
|
||||
# defines the valid format values. Will throw an error if the format is not
|
||||
# in the list. Returns either the user selected or default format.
|
||||
sub validateFormat
|
||||
{
|
||||
# receives a list of legal formats; first item is a default
|
||||
my $format = $cgi->param('format') || $_[0];
|
||||
if ( lsearch(\@_, $format) == -1)
|
||||
{
|
||||
ThrowUserError("invalid_format", { format => $format, formats => \@_ });
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
# Validates context of a diff/interdiff. Will throw an error if the context
|
||||
# is not number, "file" or "patch". Returns the validated, detainted context.
|
||||
sub validateContext
|
||||
{
|
||||
my $context = $cgi->param('context') || "patch";
|
||||
if ($context ne "file" && $context ne "patch") {
|
||||
detaint_natural($context)
|
||||
|| ThrowUserError("invalid_context", { context => $cgi->param('context') });
|
||||
}
|
||||
|
||||
return $context;
|
||||
}
|
||||
|
||||
sub validateCanChangeBug
|
||||
{
|
||||
my ($bugid) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my ($productid) = $dbh->selectrow_array(
|
||||
"SELECT product_id
|
||||
FROM bugs
|
||||
WHERE bug_id = ?", undef, $bugid);
|
||||
|
||||
Bugzilla->user->can_edit_product($productid)
|
||||
|| ThrowUserError("illegal_attachment_edit_bug",
|
||||
{ bug_id => $bugid });
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Functions
|
||||
################################################################################
|
||||
|
||||
# Display an attachment.
|
||||
sub view {
|
||||
# Retrieve and validate parameters
|
||||
my $attachment = validateID();
|
||||
my $contenttype = $attachment->contenttype;
|
||||
my $filename = $attachment->filename;
|
||||
|
||||
# Bug 111522: allow overriding content-type manually in the posted form
|
||||
# params.
|
||||
if (defined $cgi->param('content_type'))
|
||||
{
|
||||
$cgi->param('contenttypemethod', 'manual');
|
||||
$cgi->param('contenttypeentry', $cgi->param('content_type'));
|
||||
Bugzilla::Attachment->validate_content_type(THROW_ERROR);
|
||||
$contenttype = $cgi->param('content_type');
|
||||
}
|
||||
|
||||
# Return the appropriate HTTP response headers.
|
||||
$attachment->datasize || ThrowUserError("attachment_removed");
|
||||
|
||||
$filename =~ s/^.*[\/\\]//;
|
||||
# escape quotes and backslashes in the filename, per RFCs 2045/822
|
||||
$filename =~ s/\\/\\\\/g; # escape backslashes
|
||||
$filename =~ s/"/\\"/g; # escape quotes
|
||||
|
||||
print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
|
||||
-content_disposition=> "inline; filename=\"$filename\"",
|
||||
-content_length => $attachment->datasize);
|
||||
disable_utf8();
|
||||
print $attachment->data;
|
||||
}
|
||||
|
||||
sub interdiff {
|
||||
# Retrieve and validate parameters
|
||||
my $old_attachment = validateID('oldid');
|
||||
my $new_attachment = validateID('newid');
|
||||
my $format = validateFormat('html', 'raw');
|
||||
my $context = validateContext();
|
||||
|
||||
Bugzilla::Attachment::PatchReader::process_interdiff(
|
||||
$old_attachment, $new_attachment, $format, $context);
|
||||
}
|
||||
|
||||
sub diff {
|
||||
# Retrieve and validate parameters
|
||||
my $attachment = validateID();
|
||||
my $format = validateFormat('html', 'raw');
|
||||
my $context = validateContext();
|
||||
|
||||
# If it is not a patch, view normally.
|
||||
if (!$attachment->ispatch) {
|
||||
view();
|
||||
return;
|
||||
}
|
||||
|
||||
Bugzilla::Attachment::PatchReader::process_diff($attachment, $format, $context);
|
||||
}
|
||||
|
||||
# Display all attachments for a given bug in a series of IFRAMEs within one
|
||||
# HTML page.
|
||||
sub viewall {
|
||||
# Retrieve and validate parameters
|
||||
my $bugid = $cgi->param('bugid');
|
||||
ValidateBugID($bugid);
|
||||
my $bug = new Bugzilla::Bug($bugid);
|
||||
|
||||
my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bugid);
|
||||
|
||||
# Define the variables and functions that will be passed to the UI template.
|
||||
$vars->{'bug'} = $bug;
|
||||
$vars->{'attachments'} = $attachments;
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
# Generate and return the UI (HTML page) from the appropriate template.
|
||||
$template->process("attachment/show-multiple.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
# Display a form for entering a new attachment.
|
||||
sub enter {
|
||||
# Retrieve and validate parameters
|
||||
my $bugid = $cgi->param('bugid');
|
||||
ValidateBugID($bugid);
|
||||
validateCanChangeBug($bugid);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
my $bug = new Bugzilla::Bug($bugid, $user->id);
|
||||
# Retrieve the attachments the user can edit from the database and write
|
||||
# them into an array of hashes where each hash represents one attachment.
|
||||
my $canEdit = "";
|
||||
if (!$user->in_group('editbugs', $bug->product_id)) {
|
||||
$canEdit = "AND submitter_id = " . $user->id;
|
||||
}
|
||||
my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
|
||||
WHERE bug_id = ? AND isobsolete = 0 $canEdit
|
||||
ORDER BY attach_id", undef, $bugid);
|
||||
|
||||
# Define the variables and functions that will be passed to the UI template.
|
||||
$vars->{'bug'} = $bug;
|
||||
$vars->{'attachments'} = Bugzilla::Attachment->get_list($attach_ids);
|
||||
|
||||
my $flag_types = Bugzilla::FlagType::match({'target_type' => 'attachment',
|
||||
'product_id' => $bug->product_id,
|
||||
'component_id' => $bug->component_id});
|
||||
$vars->{'flag_types'} = $flag_types;
|
||||
$vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types);
|
||||
$vars->{'token'} = issue_session_token('createattachment:');
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
# Generate and return the UI (HTML page) from the appropriate template.
|
||||
$template->process("attachment/create.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
# Insert a new attachment into the database.
|
||||
sub insert {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
$dbh->bz_start_transaction;
|
||||
|
||||
# Retrieve and validate parameters
|
||||
my $bugid = $cgi->param('bugid');
|
||||
ValidateBugID($bugid);
|
||||
validateCanChangeBug($bugid);
|
||||
my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()");
|
||||
|
||||
# Detect if the user already used the same form to submit an attachment
|
||||
my $token = trim($cgi->param('token'));
|
||||
if ($token) {
|
||||
my ($creator_id, $date, $old_attach_id) = Bugzilla::Token::GetTokenData($token);
|
||||
unless ($creator_id
|
||||
&& ($creator_id == $user->id)
|
||||
&& ($old_attach_id =~ "^createattachment:"))
|
||||
{
|
||||
# The token is invalid.
|
||||
ThrowUserError('token_does_not_exist');
|
||||
}
|
||||
|
||||
$old_attach_id =~ s/^createattachment://;
|
||||
|
||||
if ($old_attach_id) {
|
||||
$vars->{'bugid'} = $bugid;
|
||||
$vars->{'attachid'} = $old_attach_id;
|
||||
print $cgi->header();
|
||||
$template->process("attachment/cancel-create-dupe.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
my $bug = new Bugzilla::Bug($bugid);
|
||||
my $attachment =
|
||||
Bugzilla::Attachment->insert_attachment_for_bug(THROW_ERROR, $bug, $user,
|
||||
$timestamp, $vars);
|
||||
|
||||
# Insert a comment about the new attachment into the database.
|
||||
my $comment = "Created an attachment (id=" . $attachment->id . ")\n" .
|
||||
$attachment->description . "\n";
|
||||
$comment .= ("\n" . $cgi->param('comment')) if defined $cgi->param('comment');
|
||||
|
||||
$bug->add_comment($comment, { isprivate => $attachment->isprivate });
|
||||
|
||||
# Assign the bug to the user, if they are allowed to take it
|
||||
my $owner = "";
|
||||
if ($cgi->param('takebug') && $user->in_group('editbugs', $bug->product_id)) {
|
||||
# When taking a bug, we have to follow the workflow.
|
||||
my $bug_status = $cgi->param('bug_status') || '';
|
||||
($bug_status) = grep {$_->name eq $bug_status} @{$bug->status->can_change_to};
|
||||
|
||||
if ($bug_status && $bug_status->is_open
|
||||
&& ($bug_status->name ne 'UNCONFIRMED' || $bug->product_obj->votes_to_confirm))
|
||||
{
|
||||
$bug->set_status($bug_status->name);
|
||||
$bug->clear_resolution();
|
||||
}
|
||||
# Make sure the person we are taking the bug from gets mail.
|
||||
$owner = $bug->assigned_to->login;
|
||||
$bug->set_assigned_to($user);
|
||||
}
|
||||
$bug->update($timestamp);
|
||||
|
||||
if ($token) {
|
||||
trick_taint($token);
|
||||
$dbh->do('UPDATE tokens SET eventdata = ? WHERE token = ?', undef,
|
||||
("createattachment:" . $attachment->id, $token));
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction;
|
||||
|
||||
# Define the variables and functions that will be passed to the UI template.
|
||||
$vars->{'mailrecipients'} = { 'changer' => $user->login,
|
||||
'owner' => $owner };
|
||||
$vars->{'attachment'} = $attachment;
|
||||
# We cannot reuse the $bug object as delta_ts has eventually been updated
|
||||
# since the object was created.
|
||||
$vars->{'bugs'} = [new Bugzilla::Bug($bugid)];
|
||||
$vars->{'header_done'} = 1;
|
||||
$vars->{'contenttypemethod'} = $cgi->param('contenttypemethod');
|
||||
$vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
|
||||
|
||||
print $cgi->header();
|
||||
# Generate and return the UI (HTML page) from the appropriate template.
|
||||
$template->process("attachment/created.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
# Displays a form for editing attachment properties.
|
||||
# Any user is allowed to access this page, unless the attachment
|
||||
# is private and the user does not belong to the insider group.
|
||||
# Validations are done later when the user submits changes.
|
||||
sub edit {
|
||||
my $attachment = validateID();
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Retrieve a list of attachments for this bug as well as a summary of the bug
|
||||
# to use in a navigation bar across the top of the screen.
|
||||
my $bugattachments =
|
||||
Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id);
|
||||
# We only want attachment IDs.
|
||||
@$bugattachments = map { $_->id } @$bugattachments;
|
||||
|
||||
my ($bugsummary, $product_id, $component_id) =
|
||||
$dbh->selectrow_array('SELECT short_desc, product_id, component_id
|
||||
FROM bugs
|
||||
WHERE bug_id = ?', undef, $attachment->bug_id);
|
||||
|
||||
# Get a list of flag types that can be set for this attachment.
|
||||
my $flag_types = Bugzilla::FlagType::match({ 'target_type' => 'attachment' ,
|
||||
'product_id' => $product_id ,
|
||||
'component_id' => $component_id });
|
||||
foreach my $flag_type (@$flag_types) {
|
||||
$flag_type->{'flags'} = Bugzilla::Flag->match({ 'type_id' => $flag_type->id,
|
||||
'attach_id' => $attachment->id });
|
||||
}
|
||||
$vars->{'flag_types'} = $flag_types;
|
||||
$vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types);
|
||||
$vars->{'attachment'} = $attachment;
|
||||
$vars->{'bugsummary'} = $bugsummary;
|
||||
$vars->{'attachments'} = $bugattachments;
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
# Generate and return the UI (HTML page) from the appropriate template.
|
||||
$template->process("attachment/edit.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
# Updates an attachment record. Users with "editbugs" privileges, (or the
|
||||
# original attachment's submitter) can edit the attachment's description,
|
||||
# content type, ispatch and isobsolete flags, and statuses, and they can
|
||||
# also submit a comment that appears in the bug.
|
||||
# Users cannot edit the content of the attachment itself.
|
||||
sub update {
|
||||
my $user = Bugzilla->user;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Retrieve and validate parameters
|
||||
my $attachment = validateID();
|
||||
my $bug = new Bugzilla::Bug($attachment->bug_id);
|
||||
$attachment->validate_can_edit($bug->product_id);
|
||||
validateCanChangeBug($bug->id);
|
||||
Bugzilla::Attachment->validate_description(THROW_ERROR);
|
||||
Bugzilla::Attachment->validate_is_patch(THROW_ERROR);
|
||||
Bugzilla::Attachment->validate_content_type(THROW_ERROR) unless $cgi->param('ispatch');
|
||||
$cgi->param('isobsolete', $cgi->param('isobsolete') ? 1 : 0);
|
||||
$cgi->param('isprivate', $cgi->param('isprivate') ? 1 : 0);
|
||||
|
||||
# Now make sure the attachment has not been edited since we loaded the page.
|
||||
if (defined $cgi->param('delta_ts')
|
||||
&& $cgi->param('delta_ts') ne $attachment->modification_time)
|
||||
{
|
||||
($vars->{'operations'}) =
|
||||
Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts'));
|
||||
|
||||
# If the modification date changed but there is no entry in
|
||||
# the activity table, this means someone commented only.
|
||||
# In this case, there is no reason to midair.
|
||||
if (scalar(@{$vars->{'operations'}})) {
|
||||
$cgi->param('delta_ts', $attachment->modification_time);
|
||||
$vars->{'attachment'} = $attachment;
|
||||
|
||||
print $cgi->header();
|
||||
# Warn the user about the mid-air collision and ask them what to do.
|
||||
$template->process("attachment/midair.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
}
|
||||
# If the submitter of the attachment is not in the insidergroup,
|
||||
# be sure that he cannot overwrite the private bit.
|
||||
# This check must be done before calling Bugzilla::Flag*::validate(),
|
||||
# because they will look at the private bit when checking permissions.
|
||||
# XXX - This is a ugly hack. Ideally, we shouldn't have to look at the
|
||||
# old private bit twice (first here, and then below again), but this is
|
||||
# the less risky change.
|
||||
unless ($user->is_insider) {
|
||||
$cgi->param('isprivate', $attachment->isprivate);
|
||||
}
|
||||
|
||||
# If the user submitted a comment while editing the attachment,
|
||||
# add the comment to the bug. Do this after having validated isprivate!
|
||||
if ($cgi->param('comment')) {
|
||||
# Prepend a string to the comment to let users know that the comment came
|
||||
# from the "edit attachment" screen.
|
||||
my $comment = "(From update of attachment " . $attachment->id . ")\n" .
|
||||
$cgi->param('comment');
|
||||
|
||||
$bug->add_comment($comment, { isprivate => $cgi->param('isprivate') });
|
||||
}
|
||||
|
||||
# The order of these function calls is important, as Flag::validate
|
||||
# assumes User::match_field has ensured that the values in the
|
||||
# requestee fields are legitimate user email addresses.
|
||||
Bugzilla::User::match_field($cgi, {
|
||||
'^requestee(_type)?-(\d+)$' => { 'type' => 'multi' }
|
||||
});
|
||||
Bugzilla::Flag::validate($bug->id, $attachment->id);
|
||||
|
||||
# Start a transaction in preparation for updating the attachment.
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
# Quote the description and content type for use in the SQL UPDATE statement.
|
||||
my $description = $cgi->param('description');
|
||||
my $contenttype = $cgi->param('contenttype');
|
||||
my $filename = $cgi->param('filename');
|
||||
# we can detaint this way thanks to placeholders
|
||||
trick_taint($description);
|
||||
trick_taint($contenttype);
|
||||
trick_taint($filename);
|
||||
|
||||
# Figure out when the changes were made.
|
||||
my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
|
||||
|
||||
# Update flags. We have to do this before committing changes
|
||||
# to attachments so that we can delete pending requests if the user
|
||||
# is obsoleting this attachment without deleting any requests
|
||||
# the user submits at the same time.
|
||||
Bugzilla::Flag->process($bug, $attachment, $timestamp, $vars);
|
||||
|
||||
# Update the attachment record in the database.
|
||||
$dbh->do("UPDATE attachments
|
||||
SET description = ?,
|
||||
mimetype = ?,
|
||||
filename = ?,
|
||||
ispatch = ?,
|
||||
isobsolete = ?,
|
||||
isprivate = ?,
|
||||
modification_time = ?
|
||||
WHERE attach_id = ?",
|
||||
undef, ($description, $contenttype, $filename,
|
||||
$cgi->param('ispatch'), $cgi->param('isobsolete'),
|
||||
$cgi->param('isprivate'), $timestamp, $attachment->id));
|
||||
|
||||
my $updated_attachment = Bugzilla::Attachment->get($attachment->id);
|
||||
# Record changes in the activity table.
|
||||
my $sth = $dbh->prepare('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
|
||||
fieldid, removed, added)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)');
|
||||
|
||||
if ($attachment->description ne $updated_attachment->description) {
|
||||
my $fieldid = get_field_id('attachments.description');
|
||||
$sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
|
||||
$attachment->description, $updated_attachment->description);
|
||||
}
|
||||
if ($attachment->contenttype ne $updated_attachment->contenttype) {
|
||||
my $fieldid = get_field_id('attachments.mimetype');
|
||||
$sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
|
||||
$attachment->contenttype, $updated_attachment->contenttype);
|
||||
}
|
||||
if ($attachment->filename ne $updated_attachment->filename) {
|
||||
my $fieldid = get_field_id('attachments.filename');
|
||||
$sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
|
||||
$attachment->filename, $updated_attachment->filename);
|
||||
}
|
||||
if ($attachment->ispatch != $updated_attachment->ispatch) {
|
||||
my $fieldid = get_field_id('attachments.ispatch');
|
||||
$sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
|
||||
$attachment->ispatch, $updated_attachment->ispatch);
|
||||
}
|
||||
if ($attachment->isobsolete != $updated_attachment->isobsolete) {
|
||||
my $fieldid = get_field_id('attachments.isobsolete');
|
||||
$sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
|
||||
$attachment->isobsolete, $updated_attachment->isobsolete);
|
||||
}
|
||||
if ($attachment->isprivate != $updated_attachment->isprivate) {
|
||||
my $fieldid = get_field_id('attachments.isprivate');
|
||||
$sth->execute($bug->id, $attachment->id, $user->id, $timestamp, $fieldid,
|
||||
$attachment->isprivate, $updated_attachment->isprivate);
|
||||
}
|
||||
|
||||
# Commit the transaction now that we are finished updating the database.
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
# Commit the comment, if any.
|
||||
$bug->update();
|
||||
|
||||
# Define the variables and functions that will be passed to the UI template.
|
||||
$vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
|
||||
$vars->{'attachment'} = $attachment;
|
||||
# We cannot reuse the $bug object as delta_ts has eventually been updated
|
||||
# since the object was created.
|
||||
$vars->{'bugs'} = [new Bugzilla::Bug($bug->id)];
|
||||
$vars->{'header_done'} = 1;
|
||||
$vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
# Generate and return the UI (HTML page) from the appropriate template.
|
||||
$template->process("attachment/updated.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
# Only administrators can delete attachments.
|
||||
sub delete_attachment {
|
||||
my $user = Bugzilla->login(LOGIN_REQUIRED);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
$user->in_group('admin')
|
||||
|| ThrowUserError('auth_failure', {group => 'admin',
|
||||
action => 'delete',
|
||||
object => 'attachment'});
|
||||
|
||||
Bugzilla->params->{'allow_attachment_deletion'}
|
||||
|| ThrowUserError('attachment_deletion_disabled');
|
||||
|
||||
# Make sure the administrator is allowed to edit this attachment.
|
||||
my $attachment = validateID();
|
||||
validateCanChangeBug($attachment->bug_id);
|
||||
|
||||
$attachment->datasize || ThrowUserError('attachment_removed');
|
||||
|
||||
# We don't want to let a malicious URL accidentally delete an attachment.
|
||||
my $token = trim($cgi->param('token'));
|
||||
if ($token) {
|
||||
my ($creator_id, $date, $event) = Bugzilla::Token::GetTokenData($token);
|
||||
unless ($creator_id
|
||||
&& ($creator_id == $user->id)
|
||||
&& ($event eq 'attachment' . $attachment->id))
|
||||
{
|
||||
# The token is invalid.
|
||||
ThrowUserError('token_does_not_exist');
|
||||
}
|
||||
|
||||
my $bug = new Bugzilla::Bug($attachment->bug_id);
|
||||
|
||||
# The token is valid. Delete the content of the attachment.
|
||||
my $msg;
|
||||
$vars->{'attachment'} = $attachment;
|
||||
$vars->{'date'} = $date;
|
||||
$vars->{'reason'} = clean_text($cgi->param('reason') || '');
|
||||
$vars->{'mailrecipients'} = { 'changer' => $user->login };
|
||||
|
||||
$template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
# Paste the reason provided by the admin into a comment.
|
||||
$bug->add_comment($msg);
|
||||
|
||||
# If the attachment is stored locally, remove it.
|
||||
if (-e $attachment->_get_local_filename) {
|
||||
unlink $attachment->_get_local_filename;
|
||||
}
|
||||
$attachment->remove_from_db();
|
||||
|
||||
# Now delete the token.
|
||||
delete_token($token);
|
||||
|
||||
# Insert the comment.
|
||||
$bug->update();
|
||||
|
||||
# Required to display the bug the deleted attachment belongs to.
|
||||
$vars->{'bugs'} = [$bug];
|
||||
$vars->{'header_done'} = 1;
|
||||
$vars->{'use_keywords'} = 1 if Bugzilla::Keyword::keyword_count();
|
||||
|
||||
$template->process("attachment/updated.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
else {
|
||||
# Create a token.
|
||||
$token = issue_session_token('attachment' . $attachment->id);
|
||||
|
||||
$vars->{'a'} = $attachment;
|
||||
$vars->{'token'} = $token;
|
||||
|
||||
$template->process("attachment/confirm-delete.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,81 +0,0 @@
|
||||
<!ELEMENT bugzilla (bug+)>
|
||||
<!ATTLIST bugzilla
|
||||
version CDATA #REQUIRED
|
||||
urlbase CDATA #REQUIRED
|
||||
maintainer CDATA #REQUIRED
|
||||
exporter CDATA #IMPLIED
|
||||
>
|
||||
<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, dup_id?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, votes?, everconfirmed, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time, deadline)?, group*, flag*, long_desc*, attachment*)?)>
|
||||
<!ATTLIST bug
|
||||
error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
|
||||
>
|
||||
<!ELEMENT bug_id (#PCDATA)>
|
||||
<!ELEMENT alias (#PCDATA)>
|
||||
<!ELEMENT reporter_accessible (#PCDATA)>
|
||||
<!ELEMENT cclist_accessible (#PCDATA)>
|
||||
<!ELEMENT exporter (#PCDATA)>
|
||||
<!ELEMENT urlbase (#PCDATA)>
|
||||
<!ELEMENT bug_status (#PCDATA)>
|
||||
<!ELEMENT classification_id (#PCDATA)>
|
||||
<!ELEMENT classification (#PCDATA)>
|
||||
<!ELEMENT product (#PCDATA)>
|
||||
<!ELEMENT priority (#PCDATA)>
|
||||
<!ELEMENT version (#PCDATA)>
|
||||
<!ELEMENT rep_platform (#PCDATA)>
|
||||
<!ELEMENT assigned_to (#PCDATA)>
|
||||
<!ELEMENT delta_ts (#PCDATA)>
|
||||
<!ELEMENT component (#PCDATA)>
|
||||
<!ELEMENT reporter (#PCDATA)>
|
||||
<!ELEMENT target_milestone (#PCDATA)>
|
||||
<!ELEMENT bug_severity (#PCDATA)>
|
||||
<!ELEMENT creation_ts (#PCDATA)>
|
||||
<!ELEMENT qa_contact (#PCDATA)>
|
||||
<!ELEMENT status_whiteboard (#PCDATA)>
|
||||
<!ELEMENT op_sys (#PCDATA)>
|
||||
<!ELEMENT resolution (#PCDATA)>
|
||||
<!ELEMENT dup_id (#PCDATA)>
|
||||
<!ELEMENT bug_file_loc (#PCDATA)>
|
||||
<!ELEMENT short_desc (#PCDATA)>
|
||||
<!ELEMENT keywords (#PCDATA)>
|
||||
<!ELEMENT dependson (#PCDATA)>
|
||||
<!ELEMENT blocked (#PCDATA)>
|
||||
<!ELEMENT votes (#PCDATA)>
|
||||
<!ELEMENT everconfirmed (#PCDATA)>
|
||||
<!ELEMENT cc (#PCDATA)>
|
||||
<!ELEMENT group (#PCDATA)>
|
||||
<!ELEMENT estimated_time (#PCDATA)>
|
||||
<!ELEMENT remaining_time (#PCDATA)>
|
||||
<!ELEMENT actual_time (#PCDATA)>
|
||||
<!ELEMENT deadline (#PCDATA)>
|
||||
<!ELEMENT long_desc (who, bug_when, work_time?, thetext)>
|
||||
<!ATTLIST long_desc
|
||||
encoding (base64) #IMPLIED
|
||||
isprivate (0|1) #IMPLIED
|
||||
>
|
||||
<!ELEMENT who (#PCDATA)>
|
||||
<!ELEMENT bug_when (#PCDATA)>
|
||||
<!ELEMENT work_time (#PCDATA)>
|
||||
<!ELEMENT thetext (#PCDATA)>
|
||||
<!ELEMENT attachment (attachid, date, desc, filename?, type?, size?, data?, flag*)>
|
||||
<!ATTLIST attachment
|
||||
isobsolete (0|1) #IMPLIED
|
||||
ispatch (0|1) #IMPLIED
|
||||
isprivate (0|1) #IMPLIED
|
||||
>
|
||||
<!ELEMENT attachid (#PCDATA)>
|
||||
<!ELEMENT date (#PCDATA)>
|
||||
<!ELEMENT desc (#PCDATA)>
|
||||
<!ELEMENT filename (#PCDATA)>
|
||||
<!ELEMENT type (#PCDATA)>
|
||||
<!ELEMENT size (#PCDATA)>
|
||||
<!ELEMENT data (#PCDATA)>
|
||||
<!ATTLIST data
|
||||
encoding (base64) #IMPLIED
|
||||
>
|
||||
<!ELEMENT flag EMPTY>
|
||||
<!ATTLIST flag
|
||||
name CDATA #REQUIRED
|
||||
status CDATA #REQUIRED
|
||||
setter CDATA #IMPLIED
|
||||
requestee CDATA #IMPLIED
|
||||
>
|
||||
@@ -1,325 +0,0 @@
|
||||
#!/usr/bin/perl -wT
|
||||
# -*- 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 Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
# Glossary:
|
||||
# series: An individual, defined set of data plotted over time.
|
||||
# data set: What a series is called in the UI.
|
||||
# line: A set of one or more series, to be summed and drawn as a single
|
||||
# line when the series is plotted.
|
||||
# chart: A set of lines
|
||||
#
|
||||
# So when you select rows in the UI, you are selecting one or more lines, not
|
||||
# series.
|
||||
|
||||
# Generic Charting TODO:
|
||||
#
|
||||
# JS-less chart creation - hard.
|
||||
# Broken image on error or no data - need to do much better.
|
||||
# Centralise permission checking, so Bugzilla->user->in_group('editbugs')
|
||||
# not scattered everywhere.
|
||||
# User documentation :-)
|
||||
#
|
||||
# Bonus:
|
||||
# Offer subscription when you get a "series already exists" error?
|
||||
|
||||
use strict;
|
||||
use lib qw(. lib);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Chart;
|
||||
use Bugzilla::Series;
|
||||
use Bugzilla::User;
|
||||
|
||||
# For most scripts we don't make $cgi and $template global variables. But
|
||||
# when preparing Bugzilla for mod_perl, this script used these
|
||||
# variables in so many subroutines that it was easier to just
|
||||
# make them globals.
|
||||
local our $cgi = Bugzilla->cgi;
|
||||
local our $template = Bugzilla->template;
|
||||
local our $vars = {};
|
||||
|
||||
# Go back to query.cgi if we are adding a boolean chart parameter.
|
||||
if (grep(/^cmd-/, $cgi->param())) {
|
||||
my $params = $cgi->canonicalise_query("format", "ctype", "action");
|
||||
print "Location: query.cgi?format=" . $cgi->param('query_format') .
|
||||
($params ? "&$params" : "") . "\n\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
my $action = $cgi->param('action');
|
||||
my $series_id = $cgi->param('series_id');
|
||||
$vars->{'doc_section'} = 'reporting.html#charts';
|
||||
|
||||
# Because some actions are chosen by buttons, we can't encode them as the value
|
||||
# of the action param, because that value is localization-dependent. So, we
|
||||
# encode it in the name, as "action-<action>". Some params even contain the
|
||||
# series_id they apply to (e.g. subscribe, unsubscribe).
|
||||
my @actions = grep(/^action-/, $cgi->param());
|
||||
if ($actions[0] && $actions[0] =~ /^action-([^\d]+)(\d*)$/) {
|
||||
$action = $1;
|
||||
$series_id = $2 if $2;
|
||||
}
|
||||
|
||||
$action ||= "assemble";
|
||||
|
||||
# Go to buglist.cgi if we are doing a search.
|
||||
if ($action eq "search") {
|
||||
my $params = $cgi->canonicalise_query("format", "ctype", "action");
|
||||
print "Location: buglist.cgi" . ($params ? "?$params" : "") . "\n\n";
|
||||
exit;
|
||||
}
|
||||
|
||||
my $user = Bugzilla->login(LOGIN_REQUIRED);
|
||||
|
||||
Bugzilla->user->in_group(Bugzilla->params->{"chartgroup"})
|
||||
|| ThrowUserError("auth_failure", {group => Bugzilla->params->{"chartgroup"},
|
||||
action => "use",
|
||||
object => "charts"});
|
||||
|
||||
# Only admins may create public queries
|
||||
Bugzilla->user->in_group('admin') || $cgi->delete('public');
|
||||
|
||||
# All these actions relate to chart construction.
|
||||
if ($action =~ /^(assemble|add|remove|sum|subscribe|unsubscribe)$/) {
|
||||
# These two need to be done before the creation of the Chart object, so
|
||||
# that the changes they make will be reflected in it.
|
||||
if ($action =~ /^subscribe|unsubscribe$/) {
|
||||
detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
$series->$action($user->id);
|
||||
}
|
||||
|
||||
my $chart = new Bugzilla::Chart($cgi);
|
||||
|
||||
if ($action =~ /^remove|sum$/) {
|
||||
$chart->$action(getSelectedLines());
|
||||
}
|
||||
elsif ($action eq "add") {
|
||||
my @series_ids = getAndValidateSeriesIDs();
|
||||
$chart->add(@series_ids);
|
||||
}
|
||||
|
||||
view($chart);
|
||||
}
|
||||
elsif ($action eq "plot") {
|
||||
plot();
|
||||
}
|
||||
elsif ($action eq "wrap") {
|
||||
# For CSV "wrap", we go straight to "plot".
|
||||
if ($cgi->param('ctype') && $cgi->param('ctype') eq "csv") {
|
||||
plot();
|
||||
}
|
||||
else {
|
||||
wrap();
|
||||
}
|
||||
}
|
||||
elsif ($action eq "create") {
|
||||
assertCanCreate($cgi);
|
||||
|
||||
my $series = new Bugzilla::Series($cgi);
|
||||
|
||||
if (!$series->existsInDatabase()) {
|
||||
$series->writeToDatabase();
|
||||
$vars->{'message'} = "series_created";
|
||||
}
|
||||
else {
|
||||
ThrowUserError("series_already_exists", {'series' => $series});
|
||||
}
|
||||
|
||||
$vars->{'series'} = $series;
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("global/message.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
elsif ($action eq "edit") {
|
||||
detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
|
||||
assertCanEdit($series_id);
|
||||
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
|
||||
edit($series);
|
||||
}
|
||||
elsif ($action eq "alter") {
|
||||
# This is the "commit" action for editing a series
|
||||
detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
|
||||
assertCanEdit($series_id);
|
||||
|
||||
my $series = new Bugzilla::Series($cgi);
|
||||
|
||||
# We need to check if there is _another_ series in the database with
|
||||
# our (potentially new) name. So we call existsInDatabase() to see if
|
||||
# the return value is us or some other series we need to avoid stomping
|
||||
# on.
|
||||
my $id_of_series_in_db = $series->existsInDatabase();
|
||||
if (defined($id_of_series_in_db) &&
|
||||
$id_of_series_in_db != $series->{'series_id'})
|
||||
{
|
||||
ThrowUserError("series_already_exists", {'series' => $series});
|
||||
}
|
||||
|
||||
$series->writeToDatabase();
|
||||
$vars->{'changes_saved'} = 1;
|
||||
|
||||
edit($series);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError("unknown_action");
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
# Find any selected series and return either the first or all of them.
|
||||
sub getAndValidateSeriesIDs {
|
||||
my @series_ids = grep(/^\d+$/, $cgi->param("name"));
|
||||
|
||||
return wantarray ? @series_ids : $series_ids[0];
|
||||
}
|
||||
|
||||
# Return a list of IDs of all the lines selected in the UI.
|
||||
sub getSelectedLines {
|
||||
my @ids = map { /^select(\d+)$/ ? $1 : () } $cgi->param();
|
||||
|
||||
return @ids;
|
||||
}
|
||||
|
||||
# Check if the user is the owner of series_id or is an admin.
|
||||
sub assertCanEdit {
|
||||
my ($series_id) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
return if $user->in_group('admin');
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $iscreator = $dbh->selectrow_array("SELECT CASE WHEN creator = ? " .
|
||||
"THEN 1 ELSE 0 END FROM series " .
|
||||
"WHERE series_id = ?", undef,
|
||||
$user->id, $series_id);
|
||||
$iscreator || ThrowUserError("illegal_series_edit");
|
||||
}
|
||||
|
||||
# Check if the user is permitted to create this series with these parameters.
|
||||
sub assertCanCreate {
|
||||
my ($cgi) = shift;
|
||||
|
||||
Bugzilla->user->in_group("editbugs") || ThrowUserError("illegal_series_creation");
|
||||
|
||||
# Check permission for frequency
|
||||
my $min_freq = 7;
|
||||
if ($cgi->param('frequency') < $min_freq && !Bugzilla->user->in_group("admin")) {
|
||||
ThrowUserError("illegal_frequency", { 'minimum' => $min_freq });
|
||||
}
|
||||
}
|
||||
|
||||
sub validateWidthAndHeight {
|
||||
$vars->{'width'} = $cgi->param('width');
|
||||
$vars->{'height'} = $cgi->param('height');
|
||||
|
||||
if (defined($vars->{'width'})) {
|
||||
(detaint_natural($vars->{'width'}) && $vars->{'width'} > 0)
|
||||
|| ThrowCodeError("invalid_dimensions");
|
||||
}
|
||||
|
||||
if (defined($vars->{'height'})) {
|
||||
(detaint_natural($vars->{'height'}) && $vars->{'height'} > 0)
|
||||
|| ThrowCodeError("invalid_dimensions");
|
||||
}
|
||||
|
||||
# The equivalent of 2000 square seems like a very reasonable maximum size.
|
||||
# This is merely meant to prevent accidental or deliberate DOS, and should
|
||||
# have no effect in practice.
|
||||
if ($vars->{'width'} && $vars->{'height'}) {
|
||||
(($vars->{'width'} * $vars->{'height'}) <= 4000000)
|
||||
|| ThrowUserError("chart_too_large");
|
||||
}
|
||||
}
|
||||
|
||||
sub edit {
|
||||
my $series = shift;
|
||||
|
||||
$vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
|
||||
$vars->{'creator'} = new Bugzilla::User($series->{'creator'});
|
||||
$vars->{'default'} = $series;
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("reports/edit-series.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
sub plot {
|
||||
validateWidthAndHeight();
|
||||
$vars->{'chart'} = new Bugzilla::Chart($cgi);
|
||||
|
||||
my $format = $template->get_format("reports/chart", "", scalar($cgi->param('ctype')));
|
||||
|
||||
# Debugging PNGs is a pain; we need to be able to see the error messages
|
||||
if ($cgi->param('debug')) {
|
||||
print $cgi->header();
|
||||
$vars->{'chart'}->dump();
|
||||
}
|
||||
|
||||
print $cgi->header($format->{'ctype'});
|
||||
$template->process($format->{'template'}, $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
sub wrap {
|
||||
validateWidthAndHeight();
|
||||
|
||||
# We create a Chart object so we can validate the parameters
|
||||
my $chart = new Bugzilla::Chart($cgi);
|
||||
|
||||
$vars->{'time'} = time();
|
||||
|
||||
$vars->{'imagebase'} = $cgi->canonicalise_query(
|
||||
"action", "action-wrap", "ctype", "format", "width", "height");
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("reports/chart.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
|
||||
sub view {
|
||||
my $chart = shift;
|
||||
|
||||
# Set defaults
|
||||
foreach my $field ('category', 'subcategory', 'name', 'ctype') {
|
||||
$vars->{'default'}{$field} = $cgi->param($field) || 0;
|
||||
}
|
||||
|
||||
# Pass the state object to the display UI.
|
||||
$vars->{'chart'} = $chart;
|
||||
$vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
# If we have having problems with bad data, we can set debug=1 to dump
|
||||
# the data structure.
|
||||
$chart->dump() if $cgi->param('debug');
|
||||
|
||||
$template->process("reports/create-chart.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
@@ -1,511 +0,0 @@
|
||||
#!/usr/bin/perl -w
|
||||
# -*- 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 mozilla.org code.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Holger
|
||||
# Schurig. Portions created by Holger Schurig are
|
||||
# Copyright (C) 1999 Holger Schurig. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
|
||||
# Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Zach Lipton <zach@zachlipton.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Tobias Burnus <burnus@net-b.de>
|
||||
# Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Joel Peshkin <bugreport@peshkin.net>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
# A. Karl Kornel <karl@kornel.name>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
# This file has detailed POD docs, do "perldoc checksetup.pl" to see them.
|
||||
|
||||
######################################################################
|
||||
# Initialization
|
||||
######################################################################
|
||||
|
||||
use strict;
|
||||
use 5.008001;
|
||||
use File::Basename;
|
||||
use Getopt::Long qw(:config bundling);
|
||||
use Pod::Usage;
|
||||
use Safe;
|
||||
|
||||
BEGIN { chdir dirname($0); }
|
||||
use lib qw(. lib);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Requirements;
|
||||
use Bugzilla::Install::Util qw(install_string get_version_and_os get_console_locale);
|
||||
|
||||
######################################################################
|
||||
# Live Code
|
||||
######################################################################
|
||||
|
||||
# When we're running at the command line, we need to pick the right
|
||||
# language before ever displaying any string.
|
||||
$ENV{'HTTP_ACCEPT_LANGUAGE'} ||= get_console_locale();
|
||||
|
||||
my %switch;
|
||||
GetOptions(\%switch, 'help|h|?', 'check-modules', 'no-templates|t',
|
||||
'verbose|v|no-silent', 'make-admin=s',
|
||||
'reset-password=s', 'version|V');
|
||||
|
||||
# Print the help message if that switch was selected.
|
||||
pod2usage({-verbose => 1, -exitval => 1}) if $switch{'help'};
|
||||
|
||||
# Read in the "answers" file if it exists, for running in
|
||||
# non-interactive mode.
|
||||
my $answers_file = $ARGV[0];
|
||||
my $silent = $answers_file && !$switch{'verbose'};
|
||||
|
||||
print(install_string('header', get_version_and_os()) . "\n") unless $silent;
|
||||
exit if $switch{'version'};
|
||||
# Check required --MODULES--
|
||||
my $module_results = check_requirements(!$silent);
|
||||
Bugzilla::Install::Requirements::print_module_instructions(
|
||||
$module_results, !$silent);
|
||||
exit if !$module_results->{pass};
|
||||
# Break out if checking the modules is all we have been asked to do.
|
||||
exit if $switch{'check-modules'};
|
||||
|
||||
###########################################################################
|
||||
# Load Bugzilla Modules
|
||||
###########################################################################
|
||||
|
||||
# It's never safe to "use" a Bugzilla module in checksetup. If a module
|
||||
# prerequisite is missing, and you "use" a module that requires it,
|
||||
# then instead of our nice normal checksetup message, the user would
|
||||
# get a cryptic perl error about the missing module.
|
||||
|
||||
# We need $::ENV{'PATH'} to remain defined.
|
||||
my $env = $::ENV{'PATH'};
|
||||
require Bugzilla;
|
||||
$::ENV{'PATH'} = $env;
|
||||
|
||||
require Bugzilla::Config;
|
||||
import Bugzilla::Config qw(:admin);
|
||||
|
||||
require Bugzilla::Install::Localconfig;
|
||||
import Bugzilla::Install::Localconfig qw(update_localconfig);
|
||||
|
||||
require Bugzilla::Install::Filesystem;
|
||||
import Bugzilla::Install::Filesystem qw(update_filesystem create_htaccess
|
||||
fix_all_file_permissions);
|
||||
require Bugzilla::Install::DB;
|
||||
require Bugzilla::DB;
|
||||
require Bugzilla::Template;
|
||||
require Bugzilla::Field;
|
||||
require Bugzilla::Install;
|
||||
|
||||
Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
|
||||
Bugzilla->installation_mode(INSTALLATION_MODE_NON_INTERACTIVE) if $answers_file;
|
||||
Bugzilla->installation_answers($answers_file);
|
||||
|
||||
###########################################################################
|
||||
# Check and update --LOCAL-- configuration
|
||||
###########################################################################
|
||||
|
||||
print "Reading " . bz_locations()->{'localconfig'} . "...\n" unless $silent;
|
||||
update_localconfig({ output => !$silent });
|
||||
my $lc_hash = Bugzilla->localconfig;
|
||||
|
||||
###########################################################################
|
||||
# Check --DATABASE-- setup
|
||||
###########################################################################
|
||||
|
||||
# At this point, localconfig is defined and is readable. So we know
|
||||
# everything we need to create the DB. We have to create it early,
|
||||
# because some data required to populate data/params is stored in the DB.
|
||||
|
||||
Bugzilla::DB::bz_check_requirements(!$silent);
|
||||
Bugzilla::DB::bz_create_database() if $lc_hash->{'db_check'};
|
||||
|
||||
# now get a handle to the database:
|
||||
my $dbh = Bugzilla->dbh;
|
||||
# Create the tables, and do any database-specific schema changes.
|
||||
$dbh->bz_setup_database();
|
||||
# Populate the tables that hold the values for the <select> fields.
|
||||
$dbh->bz_populate_enum_tables();
|
||||
|
||||
###########################################################################
|
||||
# Check --DATA-- directory
|
||||
###########################################################################
|
||||
|
||||
update_filesystem({ index_html => $lc_hash->{'index_html'} });
|
||||
create_htaccess() if $lc_hash->{'create_htaccess'};
|
||||
|
||||
# Remove parameters from the params file that no longer exist in Bugzilla,
|
||||
# and set the defaults for new ones
|
||||
my %old_params = update_params();
|
||||
|
||||
###########################################################################
|
||||
# Pre-compile --TEMPLATE-- code
|
||||
###########################################################################
|
||||
|
||||
Bugzilla::Template::precompile_templates(!$silent)
|
||||
unless $switch{'no-templates'};
|
||||
|
||||
###########################################################################
|
||||
# Set proper rights (--CHMOD--)
|
||||
###########################################################################
|
||||
|
||||
fix_all_file_permissions(!$silent);
|
||||
|
||||
###########################################################################
|
||||
# Check GraphViz setup
|
||||
###########################################################################
|
||||
|
||||
# If we are using a local 'dot' binary, verify the specified binary exists
|
||||
# and that the generated images are accessible.
|
||||
check_graphviz(!$silent) if Bugzilla->params->{'webdotbase'};
|
||||
|
||||
###########################################################################
|
||||
# Changes to the fielddefs --TABLE--
|
||||
###########################################################################
|
||||
|
||||
# Using Bugzilla::Field's create() or update() depends on the
|
||||
# fielddefs table having a modern definition. So, we have to make
|
||||
# these particular schema changes before we make any other schema changes.
|
||||
Bugzilla::Install::DB::update_fielddefs_definition();
|
||||
|
||||
Bugzilla::Field::populate_field_definitions();
|
||||
|
||||
###########################################################################
|
||||
# Update the tables to the current definition --TABLE--
|
||||
###########################################################################
|
||||
|
||||
Bugzilla::Install::DB::update_table_definitions(\%old_params);
|
||||
|
||||
###########################################################################
|
||||
# Bugzilla uses --GROUPS-- to assign various rights to its users.
|
||||
###########################################################################
|
||||
|
||||
Bugzilla::Install::update_system_groups();
|
||||
|
||||
###########################################################################
|
||||
# Create --SETTINGS-- users can adjust
|
||||
###########################################################################
|
||||
|
||||
Bugzilla::Install::update_settings();
|
||||
|
||||
###########################################################################
|
||||
# Create Administrator --ADMIN--
|
||||
###########################################################################
|
||||
|
||||
Bugzilla::Install::make_admin($switch{'make-admin'}) if $switch{'make-admin'};
|
||||
Bugzilla::Install::create_admin();
|
||||
|
||||
Bugzilla::Install::reset_password($switch{'reset-password'})
|
||||
if $switch{'reset-password'};
|
||||
|
||||
###########################################################################
|
||||
# Create default Product and Classification
|
||||
###########################################################################
|
||||
|
||||
Bugzilla::Install::create_default_product();
|
||||
|
||||
Bugzilla::Hook::process('install-before_final_checks', {'silent' => $silent });
|
||||
|
||||
###########################################################################
|
||||
# Final checks
|
||||
###########################################################################
|
||||
|
||||
# Check if the default parameter for urlbase is still set, and if so, give
|
||||
# notification that they should go and visit editparams.cgi
|
||||
if (Bugzilla->params->{'urlbase'} eq '') {
|
||||
print "\n" . Bugzilla::Install::get_text('install_urlbase_default') . "\n"
|
||||
unless $silent;
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
checksetup.pl - A do-it-all upgrade and installation script for Bugzilla.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
./checksetup.pl [--help|--check-modules|--version]
|
||||
./checksetup.pl [SCRIPT [--verbose]] [--no-templates|-t]
|
||||
[--make-admin=user@domain.com]
|
||||
[--reset-password=user@domain.com]
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item F<SCRIPT>
|
||||
|
||||
Name of script to drive non-interactive mode. This script should
|
||||
define an C<%answer> hash whose keys are variable names and the
|
||||
values answers to all the questions checksetup.pl asks. For details
|
||||
on the format of this script, do C<perldoc checksetup.pl> and look for
|
||||
the L</"RUNNING CHECKSETUP NON-INTERACTIVELY"> section.
|
||||
|
||||
=item B<--help>
|
||||
|
||||
Display this help text
|
||||
|
||||
=item B<--check-modules>
|
||||
|
||||
Only check for correct module dependencies and quit afterward.
|
||||
|
||||
=item B<--make-admin>=username@domain.com
|
||||
|
||||
Makes the specified user into a Bugzilla administrator. This is
|
||||
in case you accidentally lock yourself out of the Bugzilla administrative
|
||||
interface.
|
||||
|
||||
=item B<--reset-password>=user@domain.com
|
||||
|
||||
Resets the specified user's password. checksetup.pl will prompt you to
|
||||
enter a new password for the user.
|
||||
|
||||
=item B<--no-templates> (B<-t>)
|
||||
|
||||
Don't compile the templates at all. Existing compiled templates will
|
||||
remain; missing compiled templates will not be created. (Used primarily
|
||||
by developers to speed up checksetup.) Use this switch at your own risk.
|
||||
|
||||
=item B<--verbose>
|
||||
|
||||
Output results of SCRIPT being processed.
|
||||
|
||||
=item B<--version>
|
||||
|
||||
Display the version of Bugzilla, Perl, and some info about the
|
||||
system that Bugzilla is being installed on, and then exit.
|
||||
|
||||
=back
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Hey, what's this?
|
||||
|
||||
F<checksetup.pl> is a script that is supposed to run during
|
||||
installation time and also after every upgrade.
|
||||
|
||||
The goal of this script is to make the installation even easier.
|
||||
It does this by doing things for you as well as testing for problems
|
||||
in advance.
|
||||
|
||||
You can run the script whenever you like. You MUST run it after
|
||||
you update Bugzilla, because it will then update your SQL table
|
||||
definitions to resync them with the code.
|
||||
|
||||
You can see all the details of what the script does at
|
||||
L</How Checksetup Works>.
|
||||
|
||||
=head1 MODIFYING CHECKSETUP
|
||||
|
||||
There should be no need for Bugzilla Administrators to modify
|
||||
this script; all user-configurable stuff has been moved
|
||||
into a local configuration file called F<localconfig>. When that file
|
||||
in changed and F<checksetup.pl> is run, then the user's changes
|
||||
will be reflected back into the database.
|
||||
|
||||
However, developers often need to modify the installation process.
|
||||
This section explains how F<checksetup.pl> works, so that you
|
||||
know the right part to modify.
|
||||
|
||||
=head2 How Checksetup Works
|
||||
|
||||
F<checksetup.pl> runs through several stages during installation:
|
||||
|
||||
=over
|
||||
|
||||
=item 1
|
||||
|
||||
Checks if the required and optional perl modules are installed,
|
||||
using L<Bugzilla::Install::Requirements/check_requirements>.
|
||||
|
||||
=item 2
|
||||
|
||||
Creates or updates the F<localconfig> file, using
|
||||
L<Bugzilla::Install::Localconfig/update_localconfig>.
|
||||
|
||||
=item 3
|
||||
|
||||
Checks the DBD and database version, using
|
||||
L<Bugzilla::DB/bz_check_requirements>.
|
||||
|
||||
=item 4
|
||||
|
||||
Creates the Bugzilla database if it doesn't exist, using
|
||||
L<Bugzilla::DB/bz_create_database>.
|
||||
|
||||
=item 5
|
||||
|
||||
Creates all of the tables in the Bugzilla database, using
|
||||
L<Bugzilla::DB/bz_setup_database>.
|
||||
|
||||
Note that all the table definitions are stored in
|
||||
L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>.
|
||||
|
||||
=item 6
|
||||
|
||||
Puts the values into the enum tables (like C<resolution>, C<bug_status>,
|
||||
etc.) using L<Bugzilla::DB/bz_populate_enum_tables>.
|
||||
|
||||
=item 7
|
||||
|
||||
Creates any files that Bugzilla needs but doesn't ship with, using
|
||||
L<Bugzilla::Install::Filesystem/update_filesystem>.
|
||||
|
||||
=item 8
|
||||
|
||||
Creates the F<.htaccess> files if you haven't specified not to
|
||||
in F<localconfig>. It does this with
|
||||
L<Bugzilla::Install::Filesystem/create_htaccess>.
|
||||
|
||||
=item 9
|
||||
|
||||
Updates the system parameters (stored in F<data/params>), using
|
||||
L<Bugzilla::Config/update_params>.
|
||||
|
||||
=item 10
|
||||
|
||||
Pre-compiles all templates, to improve the speed of Bugzilla.
|
||||
It uses L<Bugzilla::Template/precompile_templates> to do this.
|
||||
|
||||
=item 11
|
||||
|
||||
Fixes all file permissions to be secure. It does this differently depending
|
||||
on whether or not you've specified C<$webservergroup> in F<localconfig>.
|
||||
|
||||
The function that does this is
|
||||
L<Bugzilla::Install::Filesystem/fix_all_file_permissions>.
|
||||
|
||||
=item 12
|
||||
|
||||
Populates the C<fielddefs> table, using
|
||||
L<Bugzilla::Field/populate_field_definitions>.
|
||||
|
||||
=item 13
|
||||
|
||||
This is the major part of checksetup--updating the table definitions
|
||||
from one version of Bugzilla to another.
|
||||
|
||||
The code for this is in L<Bugzilla::Install::DB/update_table_definitions>.
|
||||
|
||||
=item 14
|
||||
|
||||
Creates the system groups--the ones like C<editbugs>, C<admin>, and so on.
|
||||
This is L<Bugzilla::Install/update_system_groups>.
|
||||
|
||||
=item 15
|
||||
|
||||
Creates all of the user-adjustable preferences that appear on the
|
||||
"General Preferences" screen. This is L<Bugzilla::Install/update_settings>.
|
||||
|
||||
=item 16
|
||||
|
||||
Creates an administrator, if one doesn't already exist, using
|
||||
L<Bugzilla::Install/create_admin>.
|
||||
|
||||
We also can make somebody an admin at this step, if the user specified
|
||||
the C<--make-admin> switch.
|
||||
|
||||
=item 17
|
||||
|
||||
Creates the default Classification, Product, and Component, using
|
||||
L<Bugzilla::Install/create_default_product>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Modifying the Database
|
||||
|
||||
Sometimes you'll want to modify the database. In fact, that's mostly
|
||||
what checksetup does, is upgrade old Bugzilla databases to the modern
|
||||
format.
|
||||
|
||||
If you'd like to know how to make changes to the datbase, see
|
||||
the information in the Bugzilla Developer's Guide, at:
|
||||
L<http://www.bugzilla.org/docs/developer.html#sql-schema>
|
||||
|
||||
Also see L<Bugzilla::DB/"Schema Modification Methods"> and
|
||||
L<Bugzilla::DB/"Schema Information Methods">.
|
||||
|
||||
=head1 RUNNING CHECKSETUP NON-INTERACTIVELY
|
||||
|
||||
To operate checksetup non-interactively, run it with a single argument
|
||||
specifying a filename that contains the information usually obtained by
|
||||
prompting the user or by editing localconfig.
|
||||
|
||||
The format of that file is as follows:
|
||||
|
||||
$answer{'db_host'} = 'localhost';
|
||||
$answer{'db_driver'} = 'mydbdriver';
|
||||
$answer{'db_port'} = 0;
|
||||
$answer{'db_name'} = 'mydbname';
|
||||
$answer{'db_user'} = 'mydbuser';
|
||||
$answer{'db_pass'} = 'mydbpass';
|
||||
|
||||
$answer{'urlbase'} = 'http://bugzilla.mydomain.com/';
|
||||
|
||||
(Any localconfig variable or parameter can be specified as above.)
|
||||
|
||||
$answer{'ADMIN_EMAIL'} = 'myadmin@mydomain.net';
|
||||
$answer{'ADMIN_PASSWORD'} = 'fooey';
|
||||
$answer{'ADMIN_REALNAME'} = 'Joel Peshkin';
|
||||
|
||||
$answer{'SMTP_SERVER'} = 'mail.mydomain.net';
|
||||
|
||||
$answer{'NO_PAUSE'} = 1
|
||||
|
||||
C<NO_PAUSE> means "never stop and prompt the user to hit Enter to continue,
|
||||
just go ahead and do things, even if they are potentially dangerous."
|
||||
Don't set this to 1 unless you know what you are doing.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over
|
||||
|
||||
=item *
|
||||
|
||||
L<Bugzilla::Install::Requirements>
|
||||
|
||||
=item *
|
||||
|
||||
L<Bugzilla::Install::Localconfig>
|
||||
|
||||
=item *
|
||||
|
||||
L<Bugzilla::Install::Filesystem>
|
||||
|
||||
=item *
|
||||
|
||||
L<Bugzilla::Install::DB>
|
||||
|
||||
=item *
|
||||
|
||||
L<Bugzilla::Install>
|
||||
|
||||
=item *
|
||||
|
||||
L<Bugzilla::Config/update_params>
|
||||
|
||||
=item *
|
||||
|
||||
L<Bugzilla::DB/CONNECTION>
|
||||
|
||||
=back
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user