Compare commits

..

1 Commits

Author SHA1 Message Date
(no author)
258dc9fead This commit was manufactured by cvs2svn to create branch 'src'.
git-svn-id: svn://10.0.0.236/branches/src@33658 18797224-902f-48f8-a5cc-f745e15eee43
1999-06-03 23:10:01 +00:00
413 changed files with 18627 additions and 77150 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 B

View File

@@ -1,338 +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;
use strict;
use Bugzilla::Auth;
use Bugzilla::CGI;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::DB;
use Bugzilla::Template;
use Bugzilla::User;
my $_template;
sub template {
my $class = shift;
$_template ||= Bugzilla::Template->create();
return $_template;
}
my $_cgi;
sub cgi {
my $class = shift;
$_cgi ||= new Bugzilla::CGI();
return $_cgi;
}
my $_user;
sub user {
my $class = shift;
return $_user;
}
sub login {
my ($class, $type) = @_;
# Avoid double-logins, which may confuse the auth code
# (double cookies, odd compat code settings, etc)
# This is particularly important given the munging for
# $::COOKIE{'Bugzilla_login'} from a userid to a loginname
# (for backwards compat)
if (defined $_user) {
return $_user;
}
$type = LOGIN_NORMAL unless defined $type;
# For now, we can only log in from a cgi
# One day, we'll be able to log in via apache auth, an email message's
# PGP signature, and so on
use Bugzilla::Auth::CGI;
my $userid = Bugzilla::Auth::CGI->login($type);
if ($userid) {
$_user = new Bugzilla::User($userid);
# Compat stuff
$::userid = $userid;
# Evil compat hack. The cookie stores the id now, not the name, but
# old code still looks at this to get the current user's email
# so it needs to be set.
$::COOKIE{'Bugzilla_login'} = $_user->login;
} else {
logout_request();
}
return $_user;
}
sub logout {
my ($class, $option) = @_;
if (! $_user) {
# If we're not logged in, go away
return;
}
$option = LOGOUT_CURRENT unless defined $option;
use Bugzilla::Auth::CGI;
Bugzilla::Auth::CGI->logout($_user, $option);
if ($option != LOGOUT_KEEP_CURRENT) {
Bugzilla::Auth::CGI->clear_browser_cookies();
logout_request();
}
}
sub logout_user {
my ($class, $user) = @_;
# When we're logging out another user we leave cookies alone, and
# therefore avoid calling logout() directly.
use Bugzilla::Auth::CGI;
Bugzilla::Auth::CGI->logout($user, LOGOUT_ALL);
}
# 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 {
undef $_user;
$::userid = 0;
# XXX clean these up eventually
delete $::COOKIE{"Bugzilla_login"};
# NB - Can't delete from $cgi->cookie, so the logincookie data will
# remain there; it's only used in Bugzilla::Auth::CGI->logout anyway
# People shouldn't rely on the cookie param for the username
# - use Bugzilla->user instead!
}
my $_dbh;
my $_dbh_main;
my $_dbh_shadow;
sub dbh {
my $class = shift;
# If we're not connected, then we must want the main db
if (!$_dbh) {
$_dbh = $_dbh_main = Bugzilla::DB::connect_main();
}
return $_dbh;
}
sub dbwritesallowed {
my $class = shift;
# We can write if we are connected to the main database.
# Note that if we don't have a shadowdb, then we claim that its ok
# to write even if we're nominally connected to the shadowdb.
# This is OK because this method is only used to test if misc
# updates can be done, rather than anything complicated.
return $class->dbh == $_dbh_main;
}
sub switch_to_shadow_db {
my $class = shift;
if (!$_dbh_shadow) {
if (Param('shadowdb')) {
$_dbh_shadow = Bugzilla::DB::connect_shadow();
} else {
$_dbh_shadow = $_dbh_main;
}
}
$_dbh = $_dbh_shadow;
}
sub switch_to_main_db {
my $class = shift;
$_dbh = $_dbh_main;
}
# Private methods
# Per process cleanup
sub _cleanup {
undef $_cgi;
undef $_user;
# See bug 192531. If we don't clear the possibly active statement handles,
# then when this is called from the END block, it happens _before_ the
# destructors in Bugzilla::DB have happened.
# See http://rt.perl.org/rt2/Ticket/Display.html?id=17450#38810
# Without disconnecting explicitly here, noone notices, because DBI::END
# ends up calling DBD::mysql's $drh->disconnect_all, which is a noop.
# This code is evil, but it needs to be done, at least until SendSQL and
# friends can be removed
@Bugzilla::DB::SQLStateStack = ();
undef $Bugzilla::DB::_current_sth;
# When we support transactions, need to ->rollback here
$_dbh_main->disconnect if $_dbh_main;
$_dbh_shadow->disconnect if $_dbh_shadow and Param("shadowdb");
undef $_dbh_main;
undef $_dbh_shadow;
undef $_dbh;
}
sub END {
_cleanup();
}
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 arround
with mod_perl
=item *
Everything is in one central place, so its 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> functionailty is method based; use C<Bugzilla->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<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>
The current C<Bugzilla::User>. C<undef> if there is no currently logged in user
or if the login code has not yet been run.
=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->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<dbh>
The current database handle. See L<DBI>.
=item C<dbwritesallowed>
Determines if writes to the database are permitted. This is usually used to
determine if some general cleanup needs to occur (such as clearing the token
table)
=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.
=back

View File

@@ -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>
# Myk Melez <myk@mozilla.org>
############################################################################
# Module Initialization
############################################################################
use strict;
package Bugzilla::Attachment;
# This module requires that its caller have said "require CGI.pl" to import
# relevant functions from that script and its companion globals.pl.
# Use the Flag module to handle flags.
use Bugzilla::Flag;
############################################################################
# Functions
############################################################################
sub new {
# Returns a hash of information about the attachment with the given ID.
my ($invocant, $id) = @_;
return undef if !$id;
my $self = { 'id' => $id };
my $class = ref($invocant) || $invocant;
bless($self, $class);
&::PushGlobalSQLState();
&::SendSQL("SELECT 1, description, bug_id, isprivate FROM attachments " .
"WHERE attach_id = $id");
($self->{'exists'},
$self->{'summary'},
$self->{'bug_id'},
$self->{'isprivate'}) = &::FetchSQLData();
&::PopGlobalSQLState();
return $self;
}
sub query
{
# Retrieves and returns an array of attachment records for a given bug.
# This data should be given to attachment/list.atml in an
# "attachments" variable.
my ($bugid) = @_;
my $in_editbugs = &::UserInGroup("editbugs");
&::SendSQL("SELECT product_id
FROM bugs
WHERE bug_id = $bugid");
my $productid = &::FetchOneColumn();
my $caneditproduct = &::CanEditProductId($productid);
# Retrieve a list of attachments for this bug and write them into an array
# of hashes in which each hash represents a single attachment.
&::SendSQL("
SELECT attach_id, DATE_FORMAT(creation_ts, '%Y.%m.%d %H:%i'),
mimetype, description, ispatch, isobsolete, isprivate,
submitter_id, LENGTH(thedata)
FROM attachments WHERE bug_id = $bugid ORDER BY attach_id
");
my @attachments = ();
while (&::MoreSQLData()) {
my %a;
my $submitter_id;
($a{'attachid'}, $a{'date'}, $a{'contenttype'}, $a{'description'},
$a{'ispatch'}, $a{'isobsolete'}, $a{'isprivate'}, $submitter_id,
$a{'datasize'}) = &::FetchSQLData();
# Retrieve a list of flags for this attachment.
$a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'},
'is_active' => 1 });
# We will display the edit link if the user can edit the attachment;
# ie the are the submitter, or they have canedit.
# Also show the link if the user is not logged in - in that cae,
# They'll be prompted later
$a{'canedit'} = ($::userid == 0 || (($submitter_id == $::userid ||
$in_editbugs) && $caneditproduct));
push @attachments, \%a;
}
return \@attachments;
}
1;

View File

@@ -1,254 +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>
package Bugzilla::Auth;
use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
# 'inherit' from the main loginmethod
BEGIN {
my $loginmethod = Param("loginmethod");
if ($loginmethod =~ /^([A-Za-z0-9_\.\-]+)$/) {
$loginmethod = $1;
}
else {
die "Badly-named loginmethod '$loginmethod'";
}
require "Bugzilla/Auth/" . $loginmethod . ".pm";
our @ISA;
push (@ISA, "Bugzilla::Auth::" . $loginmethod);
}
# PRIVATE
# Returns the network address for a given ip
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 = Param('loginnetmask');
$addr >>= (32-$maskbits);
$addr <<= (32-$maskbits);
return join(".", unpack("CCCC", pack("N", $addr)));
}
1;
__END__
=head1 NAME
Bugzilla::Auth - Authentication handling for Bugzilla users
=head1 DESCRIPTION
Handles authentication for Bugzilla users.
Authentication from Bugzilla involves two sets of modules. One set is
used to obtain the data (from CGI, email, etc), and the other set uses
this data to authenticate against the datasource (the Bugzilla DB, LDAP,
cookies, etc).
The handlers for the various types of authentication
(DB/LDAP/cookies/etc) provide the actual code for each specific method
of authentication.
The source modules (currently, only
L<Bugzilla::Auth::CGI|Bugzilla::Auth::CGI>) then use those methods to do
the authentication.
I<Bugzilla::Auth> itself inherits from the default authentication handler,
identified by the I<loginmethod> param.
=head1 METHODS
C<Bugzilla::Auth> contains several helper methods to be used by
authentication or login modules.
=over 4
=item C<Bugzilla::Auth::get_netaddr($ipaddr)>
Given an ip address, this returns the associated network address, using
C<Param('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.
=back
=head1 AUTHENTICATION
Authentication modules check a user's credentials (username, password,
etc) to verify who the user is.
=head2 METHODS
=over 4
=item C<authenticate($username, $pass)>
This method is passed a username and a password, and returns a list
containing up to four return values, depending on the results of the
authentication.
The first return value is one of the status codes defined in
L<Bugzilla::Constants|Bugzilla::Constants> and described below. The
rest of the return values are status code-specific and are explained in
the status code descriptions.
=over 4
=item C<AUTH_OK>
Authentication succeeded. The second variable is the userid of the new
user.
=item 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.
=item C<AUTH_ERROR>
An error occurred when trying to use the login mechanism. The second return
value may contain the Bugzilla userid, but will probably be C<undef>,
signifiying that the userid is unknown. The third value is a tag describing
the error used by the authentication error templates to print a description
to the user. The optional fourth argument is a hashref of values used as part
of the tag's error descriptions.
This error template must have a name/location of
I<account/auth/C<lc(authentication-type)>-error.html.tmpl>.
=item C<AUTH_LOGINFAILED>
An incorrect username or password was given. Note that for security reasons,
both cases return the same error code. However, in the case of a valid
username, the second argument may be the userid. The authentication
mechanism may not always be able to discover the userid if the password is
not known, so whether or not this argument is present is implementation
specific. For security reasons, the presence or lack of a userid value should
not be communicated to the user.
The third argument is an optional tag from the authentication server
describing the error. The tag can be used by a template to inform the user
about the error. Similar to C<AUTH_ERROR>, an optional hashref may be
present as a fourth argument, to be used by the tag to give more detailed
information.
=item C<AUTH_DISABLED>
The user successfully logged in, but their account has been disabled.
The second argument in the returned array is the userid, and the third
is some text explaining why the account was disabled. This text would
typically come from the C<disabledtext> field in the C<profiles> table.
Note that this argument is a string, not a tag.
=back
=item C<can_edit>
This determines if the user's account details can be modified. If this
method returns a C<true> value, then accounts can be created and
modified through the Bugzilla user interface. Forgotten passwords can
also be retrieved through the L<Token interface|Bugzilla::Token>.
=back
=head1 LOGINS
A login module can be used to try to log in a Bugzilla user in a
particular way. For example, L<Bugzilla::Auth::CGI|Bugzilla::Auth::CGI>
logs in users from CGI scripts, first by using form variables, and then
by trying cookies as a fallback.
The login interface consists of the following methods:
=item C<login>, which takes a C<$type> argument, using constants found in
C<Bugzilla::Constants>.
The login method may use various authentication modules (described
above) to try to authenticate a user, and should return the userid on
success, or C<undef> on failure.
When a login is required, but data is not present, it is the job of the
login method to prompt the user for this data.
The constants accepted by C<login> include the following:
=over 4
=item 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.
=item C<LOGIN_NORMAL>
A login may or may not be required, depending on the setting of the
I<requirelogin> parameter.
=item C<LOGIN_REQUIRED>
A login is always required to access this data.
=back
=item C<logout>, which takes a C<Bugzilla::User> argument for the user
being logged out, and an C<$option> argument. Possible values for
C<$option> include:
=over 4
=item C<LOGOUT_CURRENT>
Log out the user and invalidate his currently registered session.
=item C<LOGOUT_ALL>
Log out the user, and invalidate all sessions the user has registered in
Bugzilla.
=item C<LOGOUT_KEEP_CURRENT>
Invalidate all sessions the user has registered excluding his current
session; this option should leave the user logged in. This is useful for
user-performed password changes.
=back
=head1 SEE ALSO
L<Bugzilla::Auth::CGI>, L<Bugzilla::Auth::Cookie>, L<Bugzilla::Auth::DB>

View File

@@ -1,249 +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>
package Bugzilla::Auth::CGI;
use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
sub login {
my ($class, $type) = @_;
# 'NORMAL' logins depend on the 'requirelogin' param
if ($type == LOGIN_NORMAL) {
$type = Param('requirelogin') ? LOGIN_REQUIRED : LOGIN_OPTIONAL;
}
my $cgi = Bugzilla->cgi;
# First, try the actual login method against form variables
my $username = $cgi->param("Bugzilla_login");
my $passwd = $cgi->param("Bugzilla_password");
my $authmethod = Param("loginmethod");
my ($authres, $userid, $extra, $info) =
Bugzilla::Auth->authenticate($username, $passwd);
if ($authres == AUTH_OK) {
# Login via username/password was correct and valid, so create
# and send out the login cookies
my $ipaddr = $cgi->remote_addr;
unless ($cgi->param('Bugzilla_restrictlogin') ||
Param('loginnetmask') == 32) {
$ipaddr = Bugzilla::Auth::get_netaddr($ipaddr);
}
# The IP address is valid, at least for comparing with itself in a
# subsequent login
trick_taint($ipaddr);
my $dbh = Bugzilla->dbh;
$dbh->do("INSERT INTO logincookies (userid, ipaddr) VALUES (?, ?)",
undef,
$userid, $ipaddr);
my $logincookie = $dbh->selectrow_array("SELECT LAST_INSERT_ID()");
# Remember cookie only if admin has told so
# or admin didn't forbid it and user told to remember.
if ((Param('rememberlogin') eq 'on') ||
((Param('rememberlogin') ne 'off') &&
($cgi->param('Bugzilla_remember') eq 'on'))) {
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $userid,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $logincookie,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
}
else {
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $userid);
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $logincookie);
}
}
elsif ($authres == AUTH_NODATA) {
# No data from the form, so try to login via cookies
$username = $cgi->cookie("Bugzilla_login");
$passwd = $cgi->cookie("Bugzilla_logincookie");
require Bugzilla::Auth::Cookie;
my $authmethod = "Cookie";
($authres, $userid, $extra) =
Bugzilla::Auth::Cookie->authenticate($username, $passwd);
# If the data for the cookie was incorrect, then treat that as
# NODATA. This could occur if the user's IP changed, for example.
# Give them un-loggedin access if allowed (checked below)
$authres = AUTH_NODATA if $authres == AUTH_LOGINFAILED;
}
# Now check the result
# An error may have occurred with the login mechanism
if ($authres == AUTH_ERROR) {
ThrowCodeError("auth_err",
{ authmethod => lc($authmethod),
userid => $userid,
auth_err_tag => $extra,
info => $info
});
}
# We can load the page if the login was ok, or there was no data
# but a login wasn't required
if ($authres == AUTH_OK ||
($authres == AUTH_NODATA && $type == LOGIN_OPTIONAL)) {
# login succeded, so we're done
return $userid;
}
# No login details were given, but we require a login if the
# page does
if ($authres == AUTH_NODATA && $type == LOGIN_REQUIRED) {
# Throw up the login page
print Bugzilla->cgi->header();
my $template = Bugzilla->template;
$template->process("account/auth/login.html.tmpl",
{ 'target' => $cgi->url(-relative=>1),
'form' => \%::FORM,
'mform' => \%::MFORM,
'caneditaccount' => Bugzilla::Auth->can_edit,
}
)
|| ThrowTemplateError($template->error());
# 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.
Bugzilla->dbh->do("DELETE FROM logincookies " .
"WHERE TO_DAYS(NOW()) - TO_DAYS(lastused) > 30");
exit;
}
# 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)
if ($authres == AUTH_LOGINFAILED) {
ThrowUserError("invalid_username_or_password");
}
# The account may be disabled
if ($authres == AUTH_DISABLED) {
clear_browser_cookies();
# and throw a user error
ThrowUserError("account_disabled",
{'disabled_reason' => $extra});
}
# If we get here, then we've run out of options, which shouldn't happen
ThrowCodeError("authres_unhandled", { authres => $authres,
type => $type, });
}
# Logs user out, according to the option provided; this consists of
# removing entries from logincookies for the specified $user.
sub logout {
my ($class, $user, $option) = @_;
my $dbh = Bugzilla->dbh;
$option = LOGOUT_ALL unless defined $option;
if ($option == LOGOUT_ALL) {
$dbh->do("DELETE FROM logincookies WHERE userid = ?",
undef, $user->id);
return;
}
# The LOGOUT_*_CURRENT options require a cookie
my $cookie = Bugzilla->cgi->cookie("Bugzilla_logincookie");
detaint_natural($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 ($option == LOGOUT_KEEP_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
undef, $cookie, $user->id);
} elsif ($option == LOGOUT_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
undef, $cookie, $user->id);
} else {
die("Invalid option $option supplied to logout()");
}
}
sub clear_browser_cookies {
my $cgi = Bugzilla->cgi;
$cgi->send_cookie(-name => "Bugzilla_login",
-value => "",
-expires => "Tue, 15-Sep-1998 21:49:00 GMT");
$cgi->send_cookie(-name => "Bugzilla_logincookie",
-value => "",
-expires => "Tue, 15-Sep-1998 21:49:00 GMT");
}
1;
__END__
=head1 NAME
Bugzilla::Auth::CGI - CGI-based logins for Bugzilla
=head1 SUMMARY
This is a L<login module|Bugzilla::Auth/"LOGIN"> for Bugzilla. Users connecting
from a CGI script use this module to authenticate. Logouts are also handled here.
=head1 BEHAVIOUR
Users are first authenticated against the default authentication handler,
using the CGI parameters I<Bugzilla_login> and I<Bugzilla_password>.
If no data is present for that, then cookies are tried, using
L<Bugzilla::Auth::Cookie>.
=head1 SEE ALSO
L<Bugzilla::Auth>

View File

@@ -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>
# 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>
package Bugzilla::Auth::Cookie;
use strict;
use Bugzilla::Auth;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Util;
sub authenticate {
my ($class, $login, $login_cookie) = @_;
return (AUTH_NODATA) unless defined $login && defined $login_cookie;
my $cgi = Bugzilla->cgi;
my $ipaddr = $cgi->remote_addr();
my $netaddr = Bugzilla::Auth::get_netaddr($ipaddr);
# Anything goes for these params - they're just strings which
# we're going to verify against the db
trick_taint($login);
trick_taint($login_cookie);
trick_taint($ipaddr);
my $query = "SELECT profiles.userid, profiles.disabledtext " .
"FROM logincookies, profiles " .
"WHERE logincookies.cookie=? AND " .
" logincookies.userid=profiles.userid AND " .
" logincookies.userid=? AND " .
" (logincookies.ipaddr=?";
if (defined $netaddr) {
trick_taint($netaddr);
$query .= " OR logincookies.ipaddr=?";
}
$query .= ")";
my $dbh = Bugzilla->dbh;
my ($userid, $disabledtext) = $dbh->selectrow_array($query, undef,
$login_cookie,
$login,
$ipaddr,
$netaddr);
return (AUTH_DISABLED, $userid, $disabledtext)
if ($disabledtext);
if ($userid) {
# If we logged in successfully, then update the lastused time on the
# login cookie
$dbh->do("UPDATE logincookies SET lastused=NULL WHERE cookie=?",
undef,
$login_cookie);
return (AUTH_OK, $userid);
}
# If we get here, then the login failed.
return (AUTH_LOGINFAILED);
}
1;
__END__
=head1 NAME
Bugzilla::Cookie - cookie authentication for Bugzilla
=head1 SUMMARY
This is an L<authentication module|Bugzilla::Auth/"AUTHENTICATION"> for
Bugzilla, which logs the user in using a persistent cookie stored in the
C<logincookies> table.
The actual password is not stored in the cookie; only the userid and a
I<logincookie> (which is used to reverify the login without requiring the
password to be sent over the network) are. These I<logincookies> are
restricted to certain IP addresses as a security meaure. The exact
restriction can be specified by the admin via the C<loginnetmask> parameter.
This module does not ever send a cookie (It has no way of knowing when a user
is successfully logged in). Instead L<Bugzilla::Auth::CGI> handles this.
=head1 SEE ALSO
L<Bugzilla::Auth>, L<Bugzilla::Auth::CGI>

View File

@@ -1,124 +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>
package Bugzilla::Auth::DB;
use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Util;
sub authenticate {
my ($class, $username, $passwd) = @_;
return (AUTH_NODATA) unless defined $username && defined $passwd;
# We're just testing against the db: any value is ok
trick_taint($username);
my $userid = $class->get_id_from_username($username);
return (AUTH_LOGINFAILED) unless defined $userid;
return (AUTH_LOGINFAILED, $userid)
unless $class->check_password($userid, $passwd);
# The user's credentials are okay, so delete any outstanding
# password tokens they may have generated.
require Bugzilla::Token;
Bugzilla::Token::DeletePasswordTokens($userid, "user_logged_in");
# Account may have been disabled
my $disabledtext = $class->get_disabled($userid);
return (AUTH_DISABLED, $userid, $disabledtext)
if $disabledtext ne '';
return (AUTH_OK, $userid);
}
sub can_edit { return 1; }
sub get_id_from_username {
my ($class, $username) = @_;
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare_cached("SELECT userid FROM profiles " .
"WHERE login_name=?");
my ($userid) = $dbh->selectrow_array($sth, undef, $username);
return $userid;
}
sub get_disabled {
my ($class, $userid) = @_;
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare_cached("SELECT disabledtext FROM profiles " .
"WHERE userid=?");
my ($text) = $dbh->selectrow_array($sth, undef, $userid);
return $text;
}
sub check_password {
my ($class, $userid, $passwd) = @_;
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare_cached("SELECT cryptpassword FROM profiles " .
"WHERE userid=?");
my ($realcryptpwd) = $dbh->selectrow_array($sth, undef, $userid);
# Get the salt from the user's crypted password.
my $salt = $realcryptpwd;
# Using the salt, crypt the password the user entered.
my $enteredCryptedPassword = crypt($passwd, $salt);
return $enteredCryptedPassword eq $realcryptpwd;
}
sub change_password {
my ($class, $userid, $password) = @_;
my $dbh = Bugzilla->dbh;
my $cryptpassword = Crypt($password);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $userid);
}
1;
__END__
=head1 NAME
Bugzilla::Auth::DB - database authentication for Bugzilla
=head1 SUMMARY
This is an L<authentication module|Bugzilla::Auth/"AUTHENTICATION"> for
Bugzilla, which logs the user in using the password stored in the C<profiles>
table. This is the most commonly used authentication module.
=head1 SEE ALSO
L<Bugzilla::Auth>

View File

@@ -1,185 +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>
package Bugzilla::Auth::LDAP;
use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
use Net::LDAP;
sub authenticate {
my ($class, $username, $passwd) = @_;
# If no password was provided, then fail the authentication.
# While it may be valid to not have an LDAP password, when you
# bind without a password (regardless of the binddn value), you
# will get an anonymous bind. I do not know of a way to determine
# whether a bind is anonymous or not without making changes to the
# LDAP access control settings
return (AUTH_NODATA) unless $username && $passwd;
# 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.
my $LDAPserver = Param("LDAPserver");
if ($LDAPserver eq "") {
return (AUTH_ERROR, undef, "server_not_defined");
}
my $LDAPport = "389"; # default LDAP port
if($LDAPserver =~ /:/) {
($LDAPserver, $LDAPport) = split(":",$LDAPserver);
}
my $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
if(!$LDAPconn) {
return (AUTH_ERROR, undef, "connect_failed");
}
my $mesg;
if (Param("LDAPbinddn")) {
my ($LDAPbinddn,$LDAPbindpass) = split(":",Param("LDAPbinddn"));
$mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass);
}
else {
$mesg = $LDAPconn->bind();
}
if($mesg->code) {
return (AUTH_ERROR, undef,
"connect_failed",
{ errstr => $mesg->error });
}
# We've got our anonymous bind; let's look up this user.
$mesg = $LDAPconn->search( base => Param("LDAPBaseDN"),
scope => "sub",
filter => '(&(' . Param("LDAPuidattribute") . "=$username)" . Param("LDAPfilter") . ')',
attrs => ['dn'],
);
return (AUTH_LOGINFAILED, undef, "lookup_failure")
unless $mesg->count;
# Now we get the DN from this search.
my $userDN = $mesg->shift_entry->dn;
# Now we attempt to bind as the specified user.
$mesg = $LDAPconn->bind( $userDN, password => $passwd);
return (AUTH_LOGINFAILED) if $mesg->code;
# And now we're going to repeat the search, so that we can get the
# mail attribute for this user.
$mesg = $LDAPconn->search( base => Param("LDAPBaseDN"),
scope => "sub",
filter => '(&(' . Param("LDAPuidattribute") . "=$username)" . Param("LDAPfilter") . ')',
);
my $user_entry = $mesg->shift_entry if !$mesg->code && $mesg->count;
if(!$user_entry || !$user_entry->exists(Param("LDAPmailattribute"))) {
return (AUTH_ERROR, undef,
"cannot_retreive_attr",
{ attr => Param("LDAPmailattribute") });
}
# get the mail attribute
$username = $user_entry->get_value(Param("LDAPmailattribute"));
# OK, so now we know that the user is valid. Lets try finding them in the
# Bugzilla database
# XXX - should this part be made more generic, and placed in
# Bugzilla::Auth? Lots of login mechanisms may have to do this, although
# until we actually get some more, its hard to know - BB
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare_cached("SELECT userid, disabledtext " .
"FROM profiles " .
"WHERE login_name=?");
my ($userid, $disabledtext) =
$dbh->selectrow_array($sth,
undef,
$username);
# If the user doesn't exist, then they need to be added
unless ($userid) {
# We'll want the user's name for this.
my $userRealName = $user_entry->get_value("displayName");
if($userRealName eq "") {
$userRealName = $user_entry->get_value("cn");
}
&::InsertNewUser($username, $userRealName);
($userid, $disabledtext) = $dbh->selectrow_array($sth,
undef,
$username);
return (AUTH_ERROR, $userid, "no_userid")
unless $userid;
}
# we're done, so disconnect
$LDAPconn->unbind;
# Test for disabled account
return (AUTH_DISABLED, $userid, $disabledtext)
if $disabledtext ne '';
# If we get to here, then the user is allowed to login, so we're done!
return (AUTH_OK, $userid);
}
sub can_edit { return 0; }
1;
__END__
=head1 NAME
Bugzilla::Auth::LDAP - LDAP based authentication for Bugzilla
This is an L<authentication module|Bugzilla::Auth/"AUTHENTICATION"> for
Bugzilla, which logs the user in using an LDAP directory.
=head1 DISCLAIMER
B<This module is experimental>. It is poorly documented, and not very flexible.
Search L<http://bugzilla.mozilla.org/> for a list of known LDAP bugs.
None of the core Bugzilla developers, nor any of the large installations, use
this module, and so it has received less testing. (In fact, this iteration
hasn't been tested at all)
Patches are accepted.
=head1 SEE ALSO
L<Bugzilla::Auth>

View File

@@ -1,522 +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): Dawn Endico <endico@mozilla.org>
# Terry Weissman <terry@mozilla.org>
# Chris Yeh <cyeh@bluemartini.com>
# Bradley Baetz <bbaetz@acm.org>
# Dave Miller <justdave@bugzilla.org>
package Bugzilla::Bug;
use strict;
use Bugzilla::RelationSet;
use vars qw($unconfirmedstate $legal_keywords @legal_platform
@legal_priority @legal_severity @legal_opsys @legal_bugs_status
@settable_resolution %components %versions %target_milestone
@enterable_products %milestoneurl %prodmaxvotes);
use CGI::Carp qw(fatalsToBrowser);
use Bugzilla::Attachment;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::User;
use Bugzilla::Util;
sub fields {
# Keep this ordering in sync with bugzilla.dtd
my @fields = qw(bug_id alias creation_ts short_desc delta_ts
reporter_accessible cclist_accessible
product component version rep_platform op_sys
bug_status resolution
bug_file_loc status_whiteboard keywords
priority bug_severity target_milestone
dependson blocked votes
reporter assigned_to cc
);
if (Param('useqacontact')) {
push @fields, "qa_contact";
}
if (Param('timetrackinggroup')) {
push @fields, qw(estimated_time remaining_time actual_time);
}
return @fields;
}
my %ok_field;
foreach my $key (qw(error groups
longdescs milestoneurl attachments
isopened isunconfirmed
flag_types num_attachment_flag_types
show_attachment_flags use_keywords any_flags_requesteeble
),
fields()) {
$ok_field{$key}++;
}
# create a new empty bug
#
sub new {
my $type = shift();
my %bug;
# create a ref to an empty hash and bless it
#
my $self = {%bug};
bless $self, $type;
# construct from a hash containing a bug's info
#
if ($#_ == 1) {
$self->initBug(@_);
} else {
confess("invalid number of arguments \($#_\)($_)");
}
# bless as a Bug
#
return $self;
}
# dump info about bug into hash unless user doesn't have permission
# user_id 0 is used when person is not logged in.
#
sub initBug {
my $self = shift();
my ($bug_id, $user_id) = (@_);
$bug_id = trim($bug_id);
my $old_bug_id = $bug_id;
# If the bug ID isn't numeric, it might be an alias, so try to convert it.
$bug_id = &::BugAliasToID($bug_id) if $bug_id !~ /^0*[1-9][0-9]*$/;
if ((! defined $bug_id) || (!$bug_id) || (!detaint_natural($bug_id))) {
# no bug number given or the alias didn't match a bug
$self->{'bug_id'} = $old_bug_id;
$self->{'error'} = "InvalidBugId";
return $self;
}
# default userid 0, or get DBID if you used an email address
unless (defined $user_id) {
$user_id = 0;
}
else {
if ($user_id =~ /^\@/) {
$user_id = &::DBname_to_id($user_id);
}
}
$self->{'whoid'} = $user_id;
my $query = "
SELECT
bugs.bug_id, alias, bugs.product_id, products.name, version,
rep_platform, op_sys, bug_status, resolution, priority,
bug_severity, bugs.component_id, components.name, assigned_to,
reporter, bug_file_loc, short_desc, target_milestone,
qa_contact, status_whiteboard,
DATE_FORMAT(creation_ts,'%Y.%m.%d %H:%i'),
delta_ts, ifnull(sum(votes.vote_count),0),
reporter_accessible, cclist_accessible,
estimated_time, remaining_time
from bugs left join votes using(bug_id),
products, components
where bugs.bug_id = $bug_id
AND products.id = bugs.product_id
AND components.id = bugs.component_id
group by bugs.bug_id";
&::SendSQL($query);
my @row = ();
if ((@row = &::FetchSQLData()) && &::CanSeeBug($bug_id, $self->{'whoid'})) {
my $count = 0;
my %fields;
foreach my $field ("bug_id", "alias", "product_id", "product", "version",
"rep_platform", "op_sys", "bug_status", "resolution",
"priority", "bug_severity", "component_id", "component",
"assigned_to", "reporter", "bug_file_loc", "short_desc",
"target_milestone", "qa_contact", "status_whiteboard",
"creation_ts", "delta_ts", "votes",
"reporter_accessible", "cclist_accessible",
"estimated_time", "remaining_time")
{
$fields{$field} = shift @row;
if (defined $fields{$field}) {
$self->{$field} = $fields{$field};
}
$count++;
}
} elsif (@row) {
$self->{'bug_id'} = $bug_id;
$self->{'error'} = "NotPermitted";
return $self;
} else {
$self->{'bug_id'} = $bug_id;
$self->{'error'} = "NotFound";
return $self;
}
$self->{'assigned_to'} = new Bugzilla::User($self->{'assigned_to'});
$self->{'reporter'} = new Bugzilla::User($self->{'reporter'});
if (Param('useqacontact') && $self->{'qa_contact'} > 0) {
$self->{'qa_contact'} = new Bugzilla::User($self->{'qa_contact'});
} else {
$self->{'qa_contact'} = undef;
}
my $ccSet = new Bugzilla::RelationSet;
$ccSet->mergeFromDB("select who from cc where bug_id=$bug_id");
my @cc = $ccSet->toArrayOfStrings();
if (@cc) {
$self->{'cc'} = \@cc;
}
if (@::legal_keywords) {
&::SendSQL("SELECT keyworddefs.name
FROM keyworddefs, keywords
WHERE keywords.bug_id = $bug_id
AND keyworddefs.id = keywords.keywordid
ORDER BY keyworddefs.name");
my @list;
while (&::MoreSQLData()) {
push(@list, &::FetchOneColumn());
}
if (@list) {
$self->{'keywords'} = join(', ', @list);
}
}
$self->{'attachments'} = Bugzilla::Attachment::query($self->{bug_id});
# The types of flags that can be set on this bug.
# If none, no UI for setting flags will be displayed.
my $flag_types =
Bugzilla::FlagType::match({ 'target_type' => 'bug',
'product_id' => $self->{'product_id'},
'component_id' => $self->{'component_id'} });
foreach my $flag_type (@$flag_types) {
$flag_type->{'flags'} =
Bugzilla::Flag::match({ 'bug_id' => $self->{bug_id},
'type_id' => $flag_type->{'id'},
'target_type' => 'bug',
'is_active' => 1 });
}
$self->{'flag_types'} = $flag_types;
$self->{'any_flags_requesteeble'} = grep($_->{'is_requesteeble'}, @$flag_types);
# The number of types of flags that can be set on attachments to this bug
# and the number of flags on those attachments. One of these counts must be
# greater than zero in order for the "flags" column to appear in the table
# of attachments.
my $num_attachment_flag_types =
Bugzilla::FlagType::count({ 'target_type' => 'attachment',
'product_id' => $self->{'product_id'},
'component_id' => $self->{'component_id'} });
my $num_attachment_flags =
Bugzilla::Flag::count({ 'target_type' => 'attachment',
'bug_id' => $self->{bug_id},
'is_active' => 1 });
$self->{'show_attachment_flags'}
= $num_attachment_flag_types || $num_attachment_flags;
$self->{'milestoneurl'} = $::milestoneurl{$self->{product}};
$self->{'isunconfirmed'} = ($self->{bug_status} eq $::unconfirmedstate);
$self->{'isopened'} = &::IsOpenedState($self->{bug_status});
my @depends = EmitDependList("blocked", "dependson", $bug_id);
if (@depends) {
$self->{'dependson'} = \@depends;
}
my @blocked = EmitDependList("dependson", "blocked", $bug_id);
if (@blocked) {
$self->{'blocked'} = \@blocked;
}
return $self;
}
sub dup_id {
my ($self) = @_;
return $self->{'dup_id'} if exists $self->{'dup_id'};
$self->{'dup_id'} = undef;
if ($self->{'resolution'} eq 'DUPLICATE') {
my $dbh = Bugzilla->dbh;
$self->{'dup_id'} =
$dbh->selectrow_array(q{SELECT dupe_of
FROM duplicates
WHERE dupe = ?},
undef,
$self->{'bug_id'});
}
return $self->{'dup_id'};
}
sub actual_time {
my ($self) = @_;
return $self->{'actual_time'} if exists $self->{'actual_time'};
return undef unless (Bugzilla->user &&
Bugzilla->user->in_group(Param("timetrackinggroup")));
my $sth = Bugzilla->dbh->prepare("SELECT SUM(work_time)
FROM longdescs
WHERE longdescs.bug_id=?");
$sth->execute($self->{bug_id});
$self->{'actual_time'} = $sth->fetchrow_array();
return $self->{'actual_time'};
}
sub longdescs {
my ($self) = @_;
return $self->{'longdescs'} if exists $self->{'longdescs'};
$self->{'longdescs'} = &::GetComments($self->{bug_id});
return $self->{'longdescs'};
}
sub use_keywords {
return @::legal_keywords;
}
sub use_votes {
my ($self) = @_;
return Param('usevotes')
&& $::prodmaxvotes{$self->{product}} > 0;
}
sub groups {
my $self = shift;
return $self->{'groups'} if exists $self->{'groups'};
my @groups;
# Some of this stuff needs to go into Bugzilla::User
# For every group, we need to know if there is ANY bug_group_map
# record putting the current bug in that group and if there is ANY
# user_group_map record putting the user in that group.
# The LEFT JOINs are checking for record existence.
#
&::SendSQL("SELECT DISTINCT groups.id, name, description," .
" bug_group_map.group_id IS NOT NULL," .
" user_group_map.group_id IS NOT NULL," .
" isactive, membercontrol, othercontrol" .
" FROM groups" .
" LEFT JOIN bug_group_map" .
" ON bug_group_map.group_id = groups.id" .
" AND bug_id = $self->{'bug_id'}" .
" LEFT JOIN user_group_map" .
" ON user_group_map.group_id = groups.id" .
" AND user_id = $::userid" .
" AND isbless = 0" .
" LEFT JOIN group_control_map" .
" ON group_control_map.group_id = groups.id" .
" AND group_control_map.product_id = " . $self->{'product_id'} .
" WHERE isbuggroup = 1");
while (&::MoreSQLData()) {
my ($groupid, $name, $description, $ison, $ingroup, $isactive,
$membercontrol, $othercontrol) = &::FetchSQLData();
$membercontrol ||= 0;
# For product groups, we only want to use the group if either
# (1) The bit is set and not required, or
# (2) The group is Shown or Default for members and
# the user is a member of the group.
if ($ison ||
($isactive && $ingroup
&& (($membercontrol == CONTROLMAPDEFAULT)
|| ($membercontrol == CONTROLMAPSHOWN))
))
{
my $ismandatory = $isactive
&& ($membercontrol == CONTROLMAPMANDATORY);
push (@groups, { "bit" => $groupid,
"name" => $name,
"ison" => $ison,
"ingroup" => $ingroup,
"mandatory" => $ismandatory,
"description" => $description });
}
}
$self->{'groups'} = \@groups;
return $self->{'groups'};
}
sub user {
my $self = shift;
return $self->{'user'} if exists $self->{'user'};
$self->{'user'} = {};
my $movers = Param("movers");
$movers =~ s/\s?,\s?/|/g;
$movers =~ s/@/\@/g;
$self->{'user'}->{'canmove'} = Param("move-enabled")
&& (defined $::COOKIE{"Bugzilla_login"})
&& ($::COOKIE{"Bugzilla_login"} =~ /$movers/);
# In the below, if the person hasn't logged in ($::userid == 0), then
# we treat them as if they can do anything. That's because we don't
# know why they haven't logged in; it may just be because they don't
# use cookies. Display everything as if they have all the permissions
# in the world; their permissions will get checked when they log in
# and actually try to make the change.
$self->{'user'}->{'canedit'} = $::userid == 0
|| $::userid == $self->{'reporter'}{'id'}
|| (Param('useqacontact') && $self->{'qa_contact'} && $::userid == $self->{'qa_contact'}{'id'})
|| $::userid == $self->{'assigned_to'}{'id'}
|| &::UserInGroup("editbugs");
$self->{'user'}->{'canconfirm'} = $::userid == 0
|| ($self->{'qa_contact'} && $::userid == $self->{'qa_contact'}{'id'})
|| $::userid == $self->{'assigned_to'}{'id'}
|| &::UserInGroup("editbugs")
|| &::UserInGroup("canconfirm");
return $self->{'user'};
}
sub choices {
my $self = shift;
return $self->{'choices'} if exists $self->{'choices'};
&::GetVersionTable();
$self->{'choices'} = {};
# Fiddle the product list.
my $seen_curr_prod;
my @prodlist;
foreach my $product (@::enterable_products) {
if ($product eq $self->{'product'}) {
# if it's the product the bug is already in, it's ALWAYS in
# the popup, period, whether the user can see it or not, and
# regardless of the disallownew setting.
$seen_curr_prod = 1;
push(@prodlist, $product);
next;
}
if (!&::CanEnterProduct($product)) {
# If we're using bug groups to restrict entry on products, and
# this product has an entry group, and the user is not in that
# group, we don't want to include that product in this list.
next;
}
push(@prodlist, $product);
}
# The current product is part of the popup, even if new bugs are no longer
# allowed for that product
if (!$seen_curr_prod) {
push (@prodlist, $self->{'product'});
@prodlist = sort @prodlist;
}
# Hack - this array contains "". See bug 106589.
my @res = grep ($_, @::settable_resolution);
$self->{'choices'} =
{
'product' => \@prodlist,
'rep_platform' => \@::legal_platform,
'priority' => \@::legal_priority,
'bug_severity' => \@::legal_severity,
'op_sys' => \@::legal_opsys,
'bug_status' => \@::legal_bugs_status,
'resolution' => \@res,
'component' => $::components{$self->{product}},
'version' => $::versions{$self->{product}},
'target_milestone' => $::target_milestone{$self->{product}},
};
return $self->{'choices'};
}
sub EmitDependList {
my ($myfield, $targetfield, $bug_id) = (@_);
my @list;
&::SendSQL("select dependencies.$targetfield, bugs.bug_status
from dependencies, bugs
where dependencies.$myfield = $bug_id
and bugs.bug_id = dependencies.$targetfield
order by dependencies.$targetfield");
while (&::MoreSQLData()) {
my ($i, $stat) = (&::FetchSQLData());
push @list, $i;
}
return @list;
}
sub ValidateTime {
my ($time, $field) = @_;
if ($time > 99999.99 || $time < 0 || !($time =~ /^(?:\d+(?:\.\d*)?|\.\d+)$/)) {
&::ThrowUserError("need_positive_number", {field => "$field"}, "abort");
}
}
sub AUTOLOAD {
use vars qw($AUTOLOAD);
my $attr = $AUTOLOAD;
$attr =~ s/.*:://;
return unless $attr=~ /[^A-Z]/;
confess ("invalid bug attribute $attr") unless $ok_field{$attr};
no strict 'refs';
*$AUTOLOAD = sub {
my $self = shift;
if (defined $self->{$attr}) {
return $self->{$attr};
} else {
return '';
}
};
goto &$AUTOLOAD;
}
1;

View File

@@ -1,878 +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>
use strict;
package Bugzilla::BugMail;
use Bugzilla::RelationSet;
use Bugzilla::Config qw(:DEFAULT $datadir);
use Bugzilla::Util;
# This code is really ugly. It was a commandline interface, then it was moved
# There are package-global variables which we rely on ProcessOneBug to clean
# up each time, and other sorts of fun.
# This really needs to be cleaned at some point.
my $nametoexclude = "";
my %nomail;
my $last_changed;
my @excludedAddresses = ();
# disable email flag for offline debugging work
my $enableSendMail = 1;
my %force;
my %seen;
my @sentlist;
# I got sick of adding &:: to everything.
# However, 'Yuck!'
# I can't require, cause that pulls it in only once, so it won't then be
# in the global package, and these aren't modules, so I can't use globals.pl
# Remove this evilness once our stuff uses real packages.
sub AUTOLOAD {
no strict 'refs';
use vars qw($AUTOLOAD);
my $subName = $AUTOLOAD;
$subName =~ s/.*::/::/; # remove package name
*$AUTOLOAD = \&$subName;
goto &$AUTOLOAD;
}
# This is run when we load the package
if (open(NOMAIL, '<', "$datadir/nomail")) {
while (<NOMAIL>) {
$nomail{trim($_)} = 1;
}
close(NOMAIL);
}
sub FormatTriple {
my ($a, $b, $c) = (@_);
$^A = "";
my $temp = formline << 'END', $a, $b, $c;
^>>>>>>>>>>>>>>>>>>|^<<<<<<<<<<<<<<<<<<<<<<<<<<<|^<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
END
; # This semicolon appeases my emacs editor macros. :-)
return $^A;
}
sub FormatDouble {
my ($a, $b) = (@_);
$a .= ":";
$^A = "";
my $temp = formline << 'END', $a, $b;
^>>>>>>>>>>>>>>>>>> ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
END
; # This semicolon appeases my emacs editor macros. :-)
return $^A;
}
# 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
sub Send($;$) {
my ($id, $recipients) = (@_);
# This only works in a sub. Probably something to do with the
# require abuse we do.
GetVersionTable();
# Make sure to clean up _all_ package vars here. Yuck...
$nametoexclude = $recipients->{'changer'} || "";
@{$force{'CClist'}} = (exists $recipients->{'cc'} &&
scalar($recipients->{'cc'}) > 0) ? map(trim($_),
@{$recipients->{'cc'}}) : ();
@{$force{'Owner'}} = $recipients->{'owner'} ?
(trim($recipients->{'owner'})) : ();
@{$force{'QAcontact'}} = $recipients->{'qacontact'} ?
(trim($recipients->{'qacontact'})) : ();
@{$force{'Reporter'}} = $recipients->{'reporter'} ?
(trim($recipients->{'reporter'})) : ();
@{$force{'Voter'}} = ();
%seen = ();
@excludedAddresses = ();
@sentlist = ();
return ProcessOneBug($id);
}
sub ProcessOneBug($) {
my ($id) = (@_);
my @headerlist;
my %values;
my %defmailhead;
my %fielddescription;
my $msg = "";
SendSQL("SELECT name, description, mailhead FROM fielddefs " .
"ORDER BY sortkey");
while (MoreSQLData()) {
my ($field, $description, $mailhead) = (FetchSQLData());
push(@headerlist, $field);
$defmailhead{$field} = $mailhead;
$fielddescription{$field} = $description;
}
SendSQL("SELECT " . join(',', @::log_columns) . ", lastdiffed, now() " .
"FROM bugs WHERE bug_id = $id");
my @row = FetchSQLData();
foreach my $i (@::log_columns) {
$values{$i} = shift(@row);
}
$values{product} = get_product_name($values{product_id});
$values{component} = get_component_name($values{component_id});
my ($start, $end) = (@row);
# $start and $end are considered safe because users can't touch them
trick_taint($start);
trick_taint($end);
my $ccSet = new Bugzilla::RelationSet();
$ccSet->mergeFromDB("SELECT who FROM cc WHERE bug_id = $id");
$values{'cc'} = $ccSet->toString();
my @voterList;
SendSQL("SELECT profiles.login_name FROM votes, profiles " .
"WHERE votes.bug_id = $id AND profiles.userid = votes.who");
while (MoreSQLData()) {
push(@voterList, FetchOneColumn());
}
$values{'assigned_to'} = DBID_to_name($values{'assigned_to'});
$values{'reporter'} = DBID_to_name($values{'reporter'});
if ($values{'qa_contact'}) {
$values{'qa_contact'} = DBID_to_name($values{'qa_contact'});
}
$values{'estimated_time'} = FormatTimeUnit($values{'estimated_time'});
my @dependslist;
SendSQL("SELECT dependson FROM dependencies WHERE
blocked = $id ORDER BY dependson");
while (MoreSQLData()) {
push(@dependslist, FetchOneColumn());
}
$values{'dependson'} = join(",", @dependslist);
my @blockedlist;
SendSQL("SELECT blocked FROM dependencies WHERE
dependson = $id ORDER BY blocked");
while (MoreSQLData()) {
push(@blockedlist, FetchOneColumn());
}
$values{'blocked'} = join(",", @blockedlist);
my @diffs;
SendSQL("SELECT profiles.login_name, fielddefs.description, " .
" bug_when, removed, added, attach_id, fielddefs.name " .
"FROM bugs_activity, fielddefs, profiles " .
"WHERE bug_id = $id " .
" AND fielddefs.fieldid = bugs_activity.fieldid " .
" AND profiles.userid = who " .
" AND bug_when > '$start' " .
" AND bug_when <= '$end' " .
"ORDER BY bug_when"
);
while (MoreSQLData()) {
my @row = FetchSQLData();
push(@diffs, \@row);
}
my $difftext = "";
my $diffheader = "";
my @diffparts;
my $lastwho = "";
foreach my $ref (@diffs) {
my ($who, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref);
my $diffpart = {};
if ($who ne $lastwho) {
$lastwho = $who;
$diffheader = "\n$who" . Param('emailsuffix') . " changed:\n\n";
$diffheader .= FormatTriple("What ", "Removed", "Added");
$diffheader .= ('-' x 76) . "\n";
}
$what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid;
if( $fieldname eq 'estimated_time' ||
$fieldname eq 'remaining_time' ) {
$old = FormatTimeUnit($old);
$new = FormatTimeUnit($new);
}
if ($attachid) {
SendSQL("SELECT isprivate FROM attachments
WHERE attach_id = $attachid");
$diffpart->{'isprivate'} = FetchOneColumn();
}
$difftext = FormatTriple($what, $old, $new);
$diffpart->{'header'} = $diffheader;
$diffpart->{'fieldname'} = $fieldname;
$diffpart->{'text'} = $difftext;
push(@diffparts, $diffpart);
}
my $deptext = "";
SendSQL("SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name, " .
" removed, added " .
"FROM bugs_activity, bugs, dependencies, fielddefs ".
"WHERE bugs_activity.bug_id = dependencies.dependson " .
" AND bugs.bug_id = bugs_activity.bug_id ".
" AND dependencies.blocked = $id " .
" AND fielddefs.fieldid = bugs_activity.fieldid" .
" AND (fielddefs.name = 'bug_status' " .
" OR fielddefs.name = 'resolution') " .
" AND bug_when > '$start' " .
" AND bug_when <= '$end' " .
"ORDER BY bug_when, bug_id");
my $thisdiff = "";
my $lastbug = "";
my $interestingchange = 0;
my $depbug = 0;
my @depbugs;
while (MoreSQLData()) {
my ($summary, $what, $old, $new);
($depbug, $summary, $what, $old, $new) = (FetchSQLData());
if ($depbug ne $lastbug) {
if ($interestingchange) {
$deptext .= $thisdiff;
}
$lastbug = $depbug;
my $urlbase = Param("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 .= FormatTriple("What ", "Old Value", "New Value");
$thisdiff .= ('-' x 76) . "\n";
$interestingchange = 0;
}
$thisdiff .= FormatTriple($fielddescription{$what}, $old, $new);
if ($what eq 'bug_status' && IsOpenedState($old) ne IsOpenedState($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 ($newcomments, $anyprivate) = GetLongDescriptionAsText($id, $start, $end);
#
# Start of email filtering code
#
my $count = 0;
# Get a list of the reasons a user might receive email about the bug.
my @currentEmailAttributes =
getEmailAttributes(\%values, \@diffs, $newcomments);
my (@assigned_toList,@reporterList,@qa_contactList,@ccList) = ();
#open(LOG, ">>/tmp/maillog");
#print LOG "\nBug ID: $id CurrentEmailAttributes:";
#print LOG join(',', @currentEmailAttributes) . "\n";
@excludedAddresses = (); # zero out global list
@assigned_toList = filterEmailGroup('Owner',
\@currentEmailAttributes,
$values{'assigned_to'});
@reporterList = filterEmailGroup('Reporter',
\@currentEmailAttributes,
$values{'reporter'});
if (Param('useqacontact') && $values{'qa_contact'}) {
@qa_contactList = filterEmailGroup('QAcontact',
\@currentEmailAttributes,
$values{'qa_contact'});
} else {
@qa_contactList = ();
}
@ccList = filterEmailGroup('CClist', \@currentEmailAttributes,
$values{'cc'});
@voterList = filterEmailGroup('Voter', \@currentEmailAttributes,
join(',',@voterList));
my @emailList = (@assigned_toList, @reporterList,
@qa_contactList, @ccList, @voterList);
# only need one entry per person
my @allEmail = ();
my %AlreadySeen = ();
my $checkperson = "";
foreach my $person (@emailList) {
# don't modify the original so it sends out with the right case
# based on who came first.
$checkperson = lc($person);
if ( !($AlreadySeen{$checkperson}) ) {
push(@allEmail,$person);
$AlreadySeen{$checkperson}++;
}
}
#print LOG "\nbug $id email sent: " . join(',', @allEmail) . "\n";
@excludedAddresses = filterExcludeList(\@excludedAddresses,
\@allEmail);
# print LOG "excluded: " . join(',',@excludedAddresses) . "\n\n";
foreach my $person ( @allEmail ) {
my @reasons;
$count++;
push(@reasons, 'AssignedTo') if lsearch(\@assigned_toList, $person) != -1;
push(@reasons, 'Reporter') if lsearch(\@reporterList, $person) != -1;
push(@reasons, 'QAcontact') if lsearch(\@qa_contactList, $person) != -1;
push(@reasons, 'CC') if lsearch(\@ccList, $person) != -1;
push(@reasons, 'Voter') if lsearch(\@voterList, $person) != -1;
if ( !defined(NewProcessOnePerson($person, $count, \@headerlist,
\@reasons, \%values,
\%defmailhead,
\%fielddescription, \@diffparts,
$newcomments,
$anyprivate, $start, $id,
\@depbugs)))
{
# if a value is not returned, this means that the person
# was not sent mail. add them to the excludedAddresses list.
# it will be filtered later for dups.
#
push @excludedAddresses, $person;
}
}
SendSQL("UPDATE bugs SET lastdiffed = '$end', delta_ts = delta_ts " .
"WHERE bug_id = $id");
# Filter the exclude list for dupes one last time
@excludedAddresses = filterExcludeList(\@excludedAddresses,
\@sentlist);
return { sent => \@sentlist, excluded => \@excludedAddresses };
}
# When one person is in different fields on one bug, they may be
# excluded from email because of one relationship to the bug (eg
# they're the QA contact) but included because of another (eg they
# also reported the bug). Inclusion takes precedence, so this
# function looks for and removes any users from the exclude list who
# are also on the include list. Additionally, it removes duplicate
# entries from the exclude list.
#
# Arguments are the exclude list and the include list; the cleaned up
# exclude list is returned.
#
sub filterExcludeList ($$) {
if ($#_ != 1) {
die ("filterExcludeList called with wrong number of args");
}
my ($refExcluded, $refAll) = @_;
my @excludedAddrs = @$refExcluded;
my @allEmail = @$refAll;
my @tmpList = @excludedAddrs;
my (@result,@uniqueResult) = ();
my %alreadySeen;
foreach my $excluded (@tmpList) {
push (@result,$excluded);
foreach my $included (@allEmail) {
# match found, so we remove the entry
if (lc($included) eq lc($excluded)) {
pop(@result);
last;
}
}
}
# only need one entry per person
my $checkperson = "";
foreach my $person (@result) {
$checkperson = lc($person);
if ( !($alreadySeen{$checkperson}) ) {
push(@uniqueResult,$person);
$alreadySeen{$checkperson}++;
}
}
return @uniqueResult;
}
# if the Status was changed to Resolved or Verified
# set the Resolved flag
#
# else if Severity, Status, Target Milestone OR Priority fields have any change
# set the Status flag
#
# else if Keywords has changed
# set the Keywords flag
#
# else if CC has changed
# set the CC flag
#
# if the Comments field shows an attachment
# set the Attachment flag
#
# else if Comments exist
# set the Comments flag
#
# if no flags are set and there was some other field change
# set the Status flag
#
sub getEmailAttributes (\%\@$) {
my ($bug, $fieldDiffs, $commentField) = @_;
my (@flags,@uniqueFlags,%alreadySeen) = ();
# Add a flag if the status of the bug is "unconfirmed".
if ($bug->{'bug_status'} eq $::unconfirmedstate) {
push (@flags, 'Unconfirmed')
};
foreach my $ref (@$fieldDiffs) {
my ($who, $fieldName, $when, $old, $new) = (@$ref);
#print qq{field: $fieldName $new<br>};
# the STATUS will be flagged for Severity, Status, Target Milestone and
# Priority changes
#
if ( $fieldName eq 'Status' && ($new eq 'RESOLVED' || $new eq 'VERIFIED')) {
push (@flags, 'Resolved');
}
elsif ( $fieldName eq 'Severity' || $fieldName eq 'Status' ||
$fieldName eq 'Priority' || $fieldName eq 'Target Milestone') {
push (@flags, 'Status');
} elsif ( $fieldName eq 'Keywords') {
push (@flags, 'Keywords');
} elsif ( $fieldName eq 'CC') {
push (@flags, 'CC');
}
# These next few lines find out who has been added
# to the Owner, QA, CC, etc. fields. They do not affect
# the @flags array at all, but are run here because they
# affect filtering later and we're already in the loop.
if ($fieldName eq 'AssignedTo') {
push (@{$force{'Owner'}}, $new);
} elsif ($fieldName eq 'QAcontact') {
push (@{$force{'QAcontact'}}, $new);
} elsif ($fieldName eq 'CC') {
my @added = split (/[ ,]/, $new);
push (@{$force{'CClist'}}, @added);
}
}
if ( $commentField =~ /Created an attachment \(/ ) {
push (@flags, 'Attachments');
}
elsif ( ($commentField ne '') && !(scalar(@flags) == 1 && $flags[0] eq 'Resolved')) {
push (@flags, 'Comments');
}
# default fallthrough for any unflagged change is 'Other'
if ( @flags == 0 && @$fieldDiffs != 0 ) {
push (@flags, 'Other');
}
# only need one flag per attribute type
foreach my $flag (@flags) {
if ( !($alreadySeen{$flag}) ) {
push(@uniqueFlags,$flag);
$alreadySeen{$flag}++;
}
}
#print "\nEmail Attributes: ", join(' ,',@uniqueFlags), "<br>\n";
# catch-all default, just in case the above logic is faulty
if ( @uniqueFlags == 0) {
push (@uniqueFlags, 'Comments');
}
return @uniqueFlags;
}
sub filterEmailGroup ($$$) {
# This function figures out who should receive email about the bug
# based on the user's role with respect to the bug (assignee, reporter,
# etc.), the changes that occurred (new comments, attachment added,
# status changed, etc.) and the user's email preferences.
# Returns a filtered list of those users who would receive email
# about these changes, and adds the names of those users who would
# not receive email about them to the global @excludedAddresses list.
my ($role, $reasons, $users) = @_;
# Make a list of users to filter.
my @users = split( /,/ , $users );
# Treat users who are in the process of being removed from this role
# as if they still have it.
push @users, @{$force{$role}};
# If this installation supports user watching, add to the list those
# users who are watching other users already on the list. By doing
# this here, we allow watchers to inherit the roles of the people
# they are watching while at the same time filtering email for watchers
# based on their own preferences.
if (Param("supportwatchers") && scalar(@users)) {
# Convert the unfiltered list of users into an SQL-quoted,
# comma-separated string suitable for use in an SQL query.
my $watched = join(",", map(SqlQuote($_), @users));
SendSQL("SELECT watchers.login_name
FROM watch, profiles AS watchers, profiles AS watched
WHERE watched.login_name IN ($watched)
AND watched.userid = watch.watched
AND watch.watcher = watchers.userid");
push (@users, FetchOneColumn()) while MoreSQLData();
}
# Initialize the list of recipients.
my @recipients = ();
USER: foreach my $user (@users) {
next unless $user;
# Get the user's unique ID, and if the user is not registered
# (no idea why unregistered users should even be on this list,
# but the code that was here before I re-wrote it allows this),
# then we do not have any preferences for them, so assume the
# default preference is to receive all mail.
my $userid = DBname_to_id($user);
if (!$userid) {
push(@recipients, $user);
next;
}
# Get the user's email preferences from the database.
SendSQL("SELECT emailflags FROM profiles WHERE userid = $userid");
my $prefs = FetchOneColumn();
# Write the user's preferences into a Perl record indexed by
# preference name. We pass the value "255" to the split function
# because otherwise split will trim trailing null fields, causing
# Perl to generate lots of warnings. Any suitably large number
# would do.
my %prefs = split(/~/, $prefs, 255);
# If this user is the one who made the change in the first place,
# and they prefer not to receive mail about their own changes,
# filter them from the list.
if (lc($user) eq lc($nametoexclude) && $prefs{'ExcludeSelf'} eq 'on') {
push(@excludedAddresses, $user);
next;
}
# If the user doesn't want to receive email about unconfirmed
# bugs, that setting overrides their other preferences, including
# the preference to receive email when they are added to or removed
# from a role, so remove them from the list before checking their
# other preferences.
if (grep(/Unconfirmed/, @$reasons)
&& exists($prefs{"email${role}Unconfirmed"})
&& $prefs{"email${role}Unconfirmed"} eq '')
{
push(@excludedAddresses, $user);
next;
}
# If the user was added to or removed from this role, and they
# prefer to receive email when that happens, send them mail.
# Note: This was originally written to send email when users
# were removed from roles and was later enhanced for additions,
# but for simplicity's sake the name "Removeme" was retained.
if (grep($_ eq $user, @{$force{$role}})
&& $prefs{"email${role}Removeme"} eq 'on')
{
push (@recipients, $user);
next;
}
# If the user prefers to be included in mail about this change,
# add them to the list of recipients.
foreach my $reason (@$reasons) {
my $pref = "email$role$reason";
if (!exists($prefs{$pref}) || $prefs{$pref} eq 'on') {
push(@recipients, $user);
next USER;
}
}
# At this point there's no way the user wants to receive email
# about this change, so exclude them from the list of recipients.
push(@excludedAddresses, $user);
} # for each user on the unfiltered list
return @recipients;
}
sub NewProcessOnePerson ($$$$$$$$$$$$$) {
my ($person, $count, $hlRef, $reasonsRef, $valueRef, $dmhRef, $fdRef,
$diffRef, $newcomments, $anyprivate, $start,
$id, $depbugsRef) = @_;
my %values = %$valueRef;
my @headerlist = @$hlRef;
my @reasons = @$reasonsRef;
my %defmailhead = %$dmhRef;
my %fielddescription = %$fdRef;
my @diffparts = @$diffRef;
my @depbugs = @$depbugsRef;
if ($seen{$person}) {
return;
}
if ($nomail{$person}) {
return;
}
# This routine should really get passed a userid
# This rederives groups as a side effect
my $user = Bugzilla::User->new_from_login($person);
if (!$user) { # person doesn't exist, probably changed email
return;
}
my $userid = $user->id;
$seen{$person} = 1;
# if this person doesn't have permission to see info on this bug,
# return.
#
# XXX - This currently means that if a bug is suddenly given
# more restrictive permissions, people without those permissions won't
# see the action of restricting the bug itself; the bug will just
# quietly disappear from their radar.
#
return unless CanSeeBug($id, $userid);
# Drop any non-insiders if the comment is private
return if (Param("insidergroup") &&
($anyprivate != 0) &&
(!$user->groups->{Param("insidergroup")}));
# We shouldn't send changedmail if this is a dependency mail, and any of
# the depending bugs are not visible to the user.
foreach my $dep_id (@depbugs) {
my $save_id = $dep_id;
detaint_natural($dep_id) || warn("Unexpected Error: \@depbugs contains a non-numeric value: '$save_id'")
&& return;
return unless CanSeeBug($dep_id, $userid);
}
my %mailhead = %defmailhead;
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' ||
$user->groups->{Param('timetrackinggroup')}) {
my $desc = $fielddescription{$f};
$head .= FormatDouble($desc, $value);
}
}
}
# 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')) {
if ($user->groups->{Param("timetrackinggroup")}) {
$add_diff = 1;
}
} elsif (($diff->{'isprivate'})
&& Param('insidergroup')
&& !($user->groups->{Param('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 "") {
# Whoops, no differences!
return;
}
my $reasonsbody = "------- You are receiving this mail because: -------\n";
if (scalar(@reasons) == 0) {
$reasonsbody .= "Whoops! I have no idea!\n";
} else {
foreach my $reason (@reasons) {
if ($reason eq 'AssignedTo') {
$reasonsbody .= "You are the assignee for the bug, or are watching the assignee.\n";
} elsif ($reason eq 'Reporter') {
$reasonsbody .= "You reported the bug, or are watching the reporter.\n";
} elsif ($reason eq 'QAcontact') {
$reasonsbody .= "You are the QA contact for the bug, or are watching the QA contact.\n";
} elsif ($reason eq 'CC') {
$reasonsbody .= "You are on the CC list for the bug, or are watching someone who is.\n";
} elsif ($reason eq 'Voter') {
$reasonsbody .= "You are a voter for the bug, or are watching someone who is.\n";
} else {
$reasonsbody .= "Whoops! There is an unknown reason!\n";
}
}
}
my $isnew = ($start !~ m/[1-9]/);
my %substs;
# 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 =
Param('urlbase') . "attachment.cgi?id=";
$newcomments =~ s/(Created an attachment \(id=([0-9]+)\))/$1\n --> \(${showattachurlbase}$2&action=view\)/g;
}
$person .= Param('emailsuffix');
# 09/13/2000 cyeh@bluemartini.com
# If a bug is changed, don't put the word "Changed" in the subject mail
# since if the bug didn't change, you wouldn't be getting mail
# in the first place! see http://bugzilla.mozilla.org/show_bug.cgi?id=29820
# for details.
$substs{"neworchanged"} = $isnew ? ' New: ' : '';
$substs{"to"} = $person;
$substs{"cc"} = '';
$substs{"bugid"} = $id;
if ($isnew) {
$substs{"diffs"} = $head . "\n\n" . $newcomments;
} else {
$substs{"diffs"} = $difftext . "\n\n" . $newcomments;
}
$substs{"summary"} = $values{'short_desc'};
$substs{"reasonsheader"} = join(" ", @reasons);
$substs{"reasonsbody"} = $reasonsbody;
$substs{"space"} = " ";
my $template = Param("newchangedmail");
my $msg = PerformSubsts($template, \%substs);
MessageToMTA($msg);
push(@sentlist, $person);
return 1;
}
# XXX: Should eventually add $mail_from and $mail_to options to
# control the SMTP Envelope. -mkanat
sub MessageToMTA ($) {
my ($msg) = (@_);
my $sendmailparam = "";
unless (Param("sendmailnow")) {
$sendmailparam = "-ODeliveryMode=deferred";
}
if ($enableSendMail == 1) {
open(SENDMAIL, "|/usr/lib/sendmail $sendmailparam -t -i") ||
die "Can't open sendmail";
print SENDMAIL trim($msg) . "\n";
close SENDMAIL;
}
}
1;

View File

@@ -1,245 +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>
use strict;
package Bugzilla::CGI;
use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH);
use base qw(CGI);
use Bugzilla::Util;
use Bugzilla::Config;
# We need to disable output buffering - see bug 179174
$| = 1;
# 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 {};
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} = [];
# Make sure that we don't send any charset headers
$self->charset('');
# 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) {
# XXX - under mod_perl we can use the request object to
# enable the apache ErrorDocument stuff, which is localisable
# (and localised by default under apache2).
# This doesn't appear to be possible under mod_cgi.
# Under mod_perl v2, though, this happens automatically, and the
# message body is ignored.
# 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 ($value) {
my $esc_value = url_quote($value);
push(@parameters, "$esc_key=$esc_value");
}
}
}
return join("&", @parameters);
}
# 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 behavour.
# Allow multiple calls to $cgi->header()
$CGI::HEADERS_ONCE = 0;
return $self->header(
%param,
) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
}
# 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(@_) || "";
}
# Override multipart_start to ensure our cookies are added and avoid bad quoting of
# CGI's multipart_start (bug 275108)
sub multipart_start {
my $self = shift;
return $self->header(@_);
}
# The various parts of Bugzilla which create cookies don't want to have to
# pass them arround to all of the callers. Instead, store them locally here,
# and then output as required from |header|.
sub send_cookie {
my $self = shift;
# Add the default path in
unshift(@_, '-path' => Param('cookiepath'));
# Use CGI::Cookie directly, because CGI.pm's |cookie| method gives the
# current value if there isn't a -value attribute, which happens when
# we're expiring an entry.
require CGI::Cookie;
my $cookie = CGI::Cookie->new(@_);
push @{$self->{Bugzilla_cookie_list}}, $cookie;
return;
}
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 CGI.pm's C<cookie> routine, except that the cookie
is sent to the browser, rather than returned. 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.
=back
=head1 SEE ALSO
L<CGI|CGI>, L<CGI::Cookie|CGI::Cookie>

View File

@@ -1,366 +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>
use strict;
use lib ".";
# 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::Util;
use Bugzilla::Series;
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...
# &gt=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 = @_;
# If we are going from < 2 to >= 2 series, add the Grand Total line.
if (!$self->{'gt'}) {
my $current_size = scalar($self->getSeriesIDs());
if ($current_size < 2 &&
$current_size + scalar(@series_ids) >= 2)
{
$self->{'gt'} = 1;
}
}
# 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'}}, "");
}
}
}
# 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;
# Note: you get a bad image if getSeriesIDs returns nothing
# We need to handle errors better.
my $series_ids = join(",", $self->getSeriesIDs());
# 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'};
}
# Prepare the query which retrieves the data for each series
my $query = "SELECT TO_DAYS(series_date) - " .
" TO_DAYS(FROM_UNIXTIME($datefrom)), " .
"series_value FROM series_data " .
"WHERE series_id = ? " .
"AND series_date >= FROM_UNIXTIME($datefrom)";
if ($dateto) {
$query .= " AND series_date <= FROM_UNIXTIME($dateto)";
}
my $sth = $dbh->prepare($query);
my $gt_index = $self->{'gt'} ? scalar(@{$self->{'lines'}}) : undef;
my $line_index = 0;
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] = [];
foreach my $series (@$line) {
# Get the data for this series and add it on
$sth->execute($series->{'series_id'});
my $points = $sth->fetchall_arrayref();
foreach my $point (@$points) {
my ($datediff, $value) = @$point;
$data[$line_index][$datediff] ||= 0;
$data[$line_index][$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;
}
}
}
$line_index++;
}
# 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 " .
"GROUP BY series_id");
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;

View File

@@ -1,427 +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>
package Bugzilla::Config;
use strict;
use base qw(Exporter);
use Bugzilla::Util;
# Under mod_perl, get this from a .htaccess config variable,
# and/or default from the current 'real' dir
# At some stage after this, it may be possible for these dir locations
# to go into localconfig. localconfig can't be specified in a config file,
# except possibly with mod_perl. If you move localconfig, you need to change
# the define here.
# $libpath is really only for mod_perl; its not yet possible to move the
# .pms elsewhere.
# $webdotdir must be in the webtree somewhere. Even if you use a local dot,
# we output images to there. Also, if $webdot dir is not relative to the
# bugzilla root directory, you'll need to change showdependancygraph.cgi to
# set image_url to the correct location.
# The script should really generate these graphs directly...
# Note that if $libpath is changed, some stuff will break, notably dependancy
# graphs (since the path will be wrong in the HTML). This will be fixed at
# some point.
our $libpath = '.';
our $localconfig = "$libpath/localconfig";
our $datadir = "$libpath/data";
our $templatedir = "$libpath/template";
our $webdotdir = "$datadir/webdot";
# Module stuff
@Bugzilla::Config::EXPORT = qw(Param);
# 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
# ChmodDataFile is here until that stuff all moves out of globals.pl
# into this file
@Bugzilla::Config::EXPORT_OK = qw(ChmodDataFile);
%Bugzilla::Config::EXPORT_TAGS =
(
admin => [qw(GetParamList UpdateParams SetParam WriteParams)],
db => [qw($db_host $db_port $db_name $db_user $db_pass $db_sock)],
locations => [qw($libpath $localconfig $datadir $templatedir $webdotdir)],
);
Exporter::export_ok_tags('admin', 'db', 'locations');
# Bugzilla version
$Bugzilla::Config::VERSION = "2.18";
use Safe;
use vars qw(@param_list);
# Data::Dumper is required as needed, below. The problem is that then when
# the code locally sets $Data::Dumper::Foo, this triggers 'used only once'
# warnings.
# We can't predeclare another package's vars, though, so just use them
{
local $Data::Dumper::Sortkeys;
local $Data::Dumper::Terse;
local $Data::Dumper::Indent;
}
my %param;
# INITIALISATION CODE
# XXX - mod_perl - need to register Apache init handler for params
sub _load_datafiles {
# read in localconfig variables
do $localconfig;
if (-e "$datadir/params") {
# Handle reading old param files by munging the symbol table
# Don't have to do this if we use safe mode, since its evaled
# in a sandbox where $foo is in the same module as $::foo
#local *::param = \%param;
# 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
%param = %{$s->varglob('param')};
}
}
# Load in the datafiles
_load_datafiles();
# Load in the param defintions
unless (my $ret = do 'defparams.pl') {
die "Couldn't parse defparams.pl: $@" if $@;
die "Couldn't do defparams.pl: $!" unless defined $ret;
die "Couldn't run defparams.pl" unless $ret;
}
# Stick the params into a hash
my %params;
foreach my $item (@param_list) {
$params{$item->{'name'}} = $item;
}
# END INIT CODE
# Subroutines go here
sub Param {
my ($param) = @_;
# By this stage, the param must be in the hash
die "Can't find param named $param" unless (exists $params{$param});
# When module startup code runs (which is does even via -c, when using
# |use|), we may try to grab params which don't exist yet. This affects
# tests, so have this as a fallback for the -c case
return $params{$param}->{default} if ($^C && not exists $param{$param});
# If we have a value for the param, return it
return $param{$param} if exists $param{$param};
# Else error out
die "No value for param $param (try running checksetup.pl again)";
}
sub GetParamList {
return @param_list;
}
sub SetParam {
my ($name, $value) = @_;
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 sideeffect, and so the
# checker fails the second time arround...
if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
my $err = $entry->{'checker'}->($value, $entry);
die "Param $name is not valid: $err" unless $err eq '';
}
$param{$name} = $value;
}
sub UpdateParams {
# --- PARAM CONVERSION CODE ---
# Note that this isn't particuarly 'clean' in terms of separating
# the backend code (ie this) from the actual params.
# We don't care about that, though
# 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 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";
}
# --- DEFAULTS FOR NEW PARAMS ---
foreach my $item (@param_list) {
my $name = $item->{'name'};
$param{$name} = $item->{'default'} unless exists $param{$name};
}
# --- REMOVE OLD PARAMS ---
my @oldparams = ();
# Remove any old params
foreach my $item (keys %param) {
if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
require Data::Dumper;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
push (@oldparams, [$item, Data::Dumper->Dump([$param{$item}])]);
delete $param{$item};
}
}
return @oldparams;
}
sub WriteParams {
require Data::Dumper;
# This only has an affect for Data::Dumper >= 2.12 (ie perl >= 5.8.0)
# Its just cosmetic, though, so that doesn't matter
local $Data::Dumper::Sortkeys = 1;
require File::Temp;
my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
DIR => $datadir );
print $fh (Data::Dumper->Dump([ \%param ], [ '*param' ]))
|| die "Can't write param file: $!";
close $fh;
rename $tmpname, "$datadir/params"
|| die "Can't rename $tmpname to $datadir/params: $!";
ChmodDataFile("$datadir/params", 0666);
}
# Some files in the data directory must be world readable iff 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($datadir))[2] & 0002) {
$perm = 0777;
}
$perm = $perm & $mask;
chmod $perm,$file;
}
sub check_multi {
my ($value, $param) = (@_);
if ($param->{'type'} eq "s") {
unless (lsearch($param->{'choices'}, $value) >= 0) {
return "Invalid choice '$value' for single-select list param '$param'";
}
return "";
}
elsif ($param->{'type'} eq "m") {
foreach my $chkParam (@$value) {
unless (lsearch($param->{'choices'}, $chkParam) >= 0) {
return "Invalid choice '$chkParam' for multi-select list param '$param'";
}
}
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 $@;
}
1;
__END__
=head1 NAME
Bugzilla::Config - Configuration parameters for Bugzilla
=head1 SYNOPSIS
# Getting parameters
use Bugzilla::Config;
my $fooSetting = Param('foo');
# Administration functions
use Bugzilla::Config qw(:admin);
my @valid_params = GetParamList();
my @removed_params = UpgradeParams();
SetParam($param, $value);
WriteParams();
# Localconfig variables may also be imported
use Bugzilla::Config qw(:db);
print "Connecting to $db_name as $db_user with $db_pass\n";
=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<Param($name)>
Returns the Param with the specified name. Either a string, or, in the case
of multiple-choice parameters, an array reference.
=item C<GetParamList()>
Returns the list of known parameter types, from defparams.pl. Users should not
rely on this method; it is intended for editparams/doeditparams only
The format for the list is specified in defparams.pl
=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<UpdateParams()>
Updates the parameters, by transitioning old params to new formats, setting
defaults for new params, and removing obsolete ones.
Any removed params are returned in a list, with elements [$item, $oldvalue]
where $item is the entry in the param list.
=item C<WriteParams()>
Writes the parameters to disk.
=back
=head2 Parameter checking functions
All parameter checking functions are called with two parameters:
=over 4
=item *
The new value for the parameter
=item *
A reference to the entry in the param list for this parameter
=back
Functions should return error text, or the empty string if there was no error.
=over 4
=item C<check_multi>
Checks that a multi-valued parameter (ie type C<s> or type 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

View File

@@ -1,194 +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>
package Bugzilla::Constants;
use strict;
use base qw(Exporter);
@Bugzilla::Constants::EXPORT = qw(
CONTROLMAPNA
CONTROLMAPSHOWN
CONTROLMAPDEFAULT
CONTROLMAPMANDATORY
AUTH_OK
AUTH_NODATA
AUTH_ERROR
AUTH_LOGINFAILED
AUTH_DISABLED
LOGIN_OPTIONAL
LOGIN_NORMAL
LOGIN_REQUIRED
LOGOUT_ALL
LOGOUT_CURRENT
LOGOUT_KEEP_CURRENT
DEFAULT_FLAG_EMAIL_SETTINGS
DEFAULT_EMAIL_SETTINGS
GRANT_DIRECT
GRANT_DERIVED
GRANT_REGEXP
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
# CONSTANTS
#
# 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 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" ,
"xml" => "text/xml" ,
"js" => "application/x-javascript" ,
"csv" => "text/plain" ,
"png" => "image/png" ,
"ics" => "text/calendar" ,
};
use constant DEFAULT_FLAG_EMAIL_SETTINGS =>
"~FlagRequestee~on" .
"~FlagRequester~on";
# By default, almost all bugmail is turned on, with the exception
# of CC list additions for anyone except the Assignee/Owner.
# If you want to customize the default settings for new users at
# your own site, ensure that each of the lines ends with either
# "~on" or just "~" (for off).
use constant DEFAULT_EMAIL_SETTINGS =>
"ExcludeSelf~on" .
"~FlagRequestee~on" .
"~FlagRequester~on" .
"~emailOwnerRemoveme~on" .
"~emailOwnerComments~on" .
"~emailOwnerAttachments~on" .
"~emailOwnerStatus~on" .
"~emailOwnerResolved~on" .
"~emailOwnerKeywords~on" .
"~emailOwnerCC~on" .
"~emailOwnerOther~on" .
"~emailOwnerUnconfirmed~on" .
"~emailReporterRemoveme~on" .
"~emailReporterComments~on" .
"~emailReporterAttachments~on" .
"~emailReporterStatus~on" .
"~emailReporterResolved~on" .
"~emailReporterKeywords~on" .
"~emailReporterCC~" .
"~emailReporterOther~on" .
"~emailReporterUnconfirmed~on" .
"~emailQAcontactRemoveme~on" .
"~emailQAcontactComments~on" .
"~emailQAcontactAttachments~on" .
"~emailQAcontactStatus~on" .
"~emailQAcontactResolved~on" .
"~emailQAcontactKeywords~on" .
"~emailQAcontactCC~" .
"~emailQAcontactOther~on" .
"~emailQAcontactUnconfirmed~on" .
"~emailCClistRemoveme~on" .
"~emailCClistComments~on" .
"~emailCClistAttachments~on" .
"~emailCClistStatus~on" .
"~emailCClistResolved~on" .
"~emailCClistKeywords~on" .
"~emailCClistCC~" .
"~emailCClistOther~on" .
"~emailCClistUnconfirmed~on" .
"~emailVoterRemoveme~on" .
"~emailVoterComments~on" .
"~emailVoterAttachments~on" .
"~emailVoterStatus~on" .
"~emailVoterResolved~on" .
"~emailVoterKeywords~on" .
"~emailVoterCC~" .
"~emailVoterOther~on" .
"~emailVoterUnconfirmed~on";
use constant GRANT_DIRECT => 0;
use constant GRANT_DERIVED => 1;
use constant GRANT_REGEXP => 2;
1;

View File

@@ -1,270 +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>
package Bugzilla::DB;
use strict;
use DBI;
use base qw(Exporter);
%Bugzilla::DB::EXPORT_TAGS =
(
deprecated => [qw(SendSQL SqlQuote
MoreSQLData FetchSQLData FetchOneColumn
PushGlobalSQLState PopGlobalSQLState)
],
);
Exporter::export_ok_tags('deprecated');
use Bugzilla::Config qw(:DEFAULT :db);
use Bugzilla::Util;
# All this code is backwards compat fu. As such, its a bit ugly. Note the
# circular dependancies on Bugzilla.pm
# This is old cruft which will be removed, so theres not much use in
# having a separate package for it, or otherwise trying to avoid the circular
# dependancy
# XXX - mod_perl
# These use |our| instead of |my| because they need to be cleared from
# Bugzilla.pm. See bug 192531 for details.
our $_current_sth;
our @SQLStateStack = ();
sub SendSQL {
my ($str) = @_;
$_current_sth = Bugzilla->dbh->prepare($str);
$_current_sth->execute;
# This is really really ugly, but its what we get for not doing
# error checking for 5 years. See bug 189446 and bug 192531
$_current_sth->{RaiseError} = 0;
}
# Its much much better to use bound params instead of this
sub SqlQuote {
my ($str) = @_;
# Backwards compat code
return "''" if not defined $str;
my $res = Bugzilla->dbh->quote($str);
trick_taint($res);
return $res;
}
# XXX - mod_perl
my $_fetchahead;
sub MoreSQLData {
return 1 if defined $_fetchahead;
if ($_fetchahead = $_current_sth->fetchrow_arrayref()) {
return 1;
}
return 0;
}
sub FetchSQLData {
if (defined $_fetchahead) {
my @result = @$_fetchahead;
undef $_fetchahead;
return @result;
}
return $_current_sth->fetchrow_array;
}
sub FetchOneColumn {
my @row = FetchSQLData();
return $row[0];
}
sub PushGlobalSQLState() {
push @SQLStateStack, $_current_sth;
push @SQLStateStack, $_fetchahead;
}
sub PopGlobalSQLState() {
die ("PopGlobalSQLState: stack underflow") if ( scalar(@SQLStateStack) < 1 );
$_fetchahead = pop @SQLStateStack;
$_current_sth = pop @SQLStateStack;
}
# MODERN CODE BELOW
sub connect_shadow {
die "Tried to connect to non-existent shadowdb" unless Param('shadowdb');
my $dsn = "DBI:mysql:host=" . Param("shadowdbhost") .
";database=" . Param('shadowdb') . ";port=" . Param("shadowdbport");
$dsn .= ";mysql_socket=" . Param("shadowdbsock") if Param('shadowdbsock');
return _connect($dsn);
}
sub connect_main {
my $dsn = "DBI:mysql:host=$db_host;database=$db_name;port=$db_port";
$dsn .= ";mysql_socket=$db_sock" if $db_sock;
return _connect($dsn);
}
sub _connect {
my ($dsn) = @_;
# connect using our known info to the specified db
# Apache::DBI will cache this when using mod_perl
my $dbh = DBI->connect($dsn,
'',
'',
{ RaiseError => 1,
PrintError => 0,
Username => $db_user,
Password => $db_pass,
ShowErrorStatement => 1,
HandleError => \&_handle_error,
TaintIn => 1,
FetchHashKeyName => 'NAME',
# Note: NAME_lc causes crash on ActiveState Perl
# 5.8.4 (see Bug 253696)
});
return $dbh;
}
sub _handle_error {
require Carp;
# Cut down the error string to a reasonable size
$_[0] = substr($_[0], 0, 2000) . ' ... ' . substr($_[0], -2000)
if length($_[0]) > 4000;
$_[0] = Carp::longmess($_[0]);
return 0; # Now let DBI handle raising the error
}
my $cached_server_version;
sub server_version {
return $cached_server_version if defined($cached_server_version);
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare('SELECT VERSION()');
$sth->execute();
($cached_server_version) = $sth->fetchrow_array();
return $cached_server_version;
}
1;
__END__
=head1 NAME
Bugzilla::DB - Database access routines, using L<DBI>
=head1 SYNOPSIS
my $dbh = Bugzilla::DB->connect_main;
my $shadow = Bugzilla::DB->connect_shadow;
SendSQL("SELECT COUNT(*) FROM bugs");
my $cnt = FetchOneColumn();
=head1 DESCRIPTION
This allows creation of a database handle to connect to the Bugzilla database.
This should never be done directly; all users should use the L<Bugzilla> module
to access the current C<dbh> instead.
Access to the old SendSQL-based database routines are also provided by
importing the C<:deprecated> tag. These routines should not be used in new
code.
=head1 CONNECTION
A new database handle to the required database can be created using this
module. This is normally done by the L<Bugzilla> module, and so these routines
should not be called from anywhere else.
=over 4
=item C<connect_main>
Connects to the main database, returning a new dbh.
=item C<connect_shadow>
Connects to the shadow database, returning a new dbh. This routine C<die>s if
no shadow database is configured.
=back
=head1 DEPRECATED ROUTINES
Several database routines are deprecated. They should not be used in new code,
and so are not documented.
=over 4
=item *
SendSQL
=item *
SqlQuote
=item *
MoreSQLData
=item *
FetchSQLData
=item *
FetchOneColumn
=item *
PushGlobalSQLState
=item *
PopGlobalSQLState
=back
=head1 SEE ALSO
L<DBI>
=cut

View File

@@ -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): Bradley Baetz <bbaetz@acm.org>
package Bugzilla::Error;
use strict;
use base qw(Exporter);
@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError);
use Bugzilla::Config;
use Bugzilla::Util;
sub _throw_error {
my ($name, $error, $vars, $unlock_tables) = @_;
$vars ||= {};
$vars->{error} = $error;
Bugzilla->dbh->do("UNLOCK TABLES") if $unlock_tables;
print Bugzilla->cgi->header();
my $template = Bugzilla->template;
$template->process($name, $vars)
|| ThrowTemplateError($template->error());
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 $vars = {};
$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 = Param('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,"&amp;")
.replace(/</g,"&lt;")
.replace(/>/g,"&gt;") + "</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 localisable manner.
=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.
An optional third argument may be supplied. If present (and defined), then the
error handling code will unlock the database tables. In the long term, this
argument will go away, to be replaced by transactional C<rollback> calls. There
is no timeframe for doing so, however.
=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>

View File

@@ -1,685 +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>
# Jouni Heikniemi <jouni@heikniemi.net>
################################################################################
# Module Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use strict;
# This module implements bug and attachment flags.
package Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::User;
use Bugzilla::Config;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Attachment;
use Bugzilla::BugMail;
use constant TABLES_ALREADY_LOCKED => 1;
# Note that this line doesn't actually import these variables for some reason,
# so I have to use them as $::template and $::vars in the package code.
use vars qw($template $vars);
# Note! This module requires that its caller have said "require CGI.pl"
# to import relevant functions from that script and its companion globals.pl.
################################################################################
# Global Variables
################################################################################
# basic sets of columns and tables for getting flags from the database
my @base_columns =
("is_active", "id", "type_id", "bug_id", "attach_id", "requestee_id",
"setter_id", "status");
# Note: when adding tables to @base_tables, make sure to include the separator
# (i.e. a comma or 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.
my @base_tables = ("flags");
################################################################################
# Searching/Retrieving Flags
################################################################################
# !!! Implement a cache for this function!
sub get {
# Retrieves and returns a flag from the database.
my ($id) = @_;
my $select_clause = "SELECT " . join(", ", @base_columns);
my $from_clause = "FROM " . join(" ", @base_tables);
# Execute the query, retrieve the result, and write it into a record.
&::PushGlobalSQLState();
&::SendSQL("$select_clause $from_clause WHERE flags.id = $id");
my $flag = perlify_record(&::FetchSQLData());
&::PopGlobalSQLState();
return $flag;
}
sub match {
# Queries the database for flags matching the given criteria
# (specified as a hash of field names and their matching values)
# and returns an array of matching records.
my ($criteria) = @_;
my $select_clause = "SELECT " . join(", ", @base_columns);
my $from_clause = "FROM " . join(" ", @base_tables);
my @criteria = sqlify_criteria($criteria);
my $where_clause = "WHERE " . join(" AND ", @criteria);
# Execute the query, retrieve the results, and write them into records.
&::PushGlobalSQLState();
&::SendSQL("$select_clause $from_clause $where_clause");
my @flags;
while (&::MoreSQLData()) {
my $flag = perlify_record(&::FetchSQLData());
push(@flags, $flag);
}
&::PopGlobalSQLState();
return \@flags;
}
sub count {
# Queries the database for flags matching the given criteria
# (specified as a hash of field names and their matching values)
# and returns an array of matching records.
my ($criteria) = @_;
my @criteria = sqlify_criteria($criteria);
my $where_clause = "WHERE " . join(" AND ", @criteria);
# Execute the query, retrieve the result, and write it into a record.
&::PushGlobalSQLState();
&::SendSQL("SELECT COUNT(id) FROM flags $where_clause");
my $count = &::FetchOneColumn();
&::PopGlobalSQLState();
return $count;
}
################################################################################
# Creating and Modifying
################################################################################
sub validate {
# Validates fields containing flag modifications.
my ($data, $bug_id) = @_;
# Get a list of flags to validate. Uses the "map" function
# to extract flag IDs from form field names by matching fields
# whose name looks like "flag-nnn", where "nnn" is the ID,
# and returning just the ID portion of matching field names.
my @ids = map(/^flag-(\d+)$/ ? $1 : (), keys %$data);
foreach my $id (@ids)
{
my $status = $data->{"flag-$id"};
# Make sure the flag exists.
my $flag = get($id);
$flag || ThrowCodeError("flag_nonexistent", { id => $id });
# Note that the deletedness of the flag (is_active or not) is not
# checked here; we do want to allow changes to deleted flags in
# certain cases. Flag::modify() will revive the modified flags.
# See bug 223878 for details.
# Make sure the user chose a valid status.
grep($status eq $_, qw(X + - ?))
|| ThrowCodeError("flag_status_invalid",
{ id => $id, status => $status });
# Make sure the user didn't request the flag unless it's requestable.
# If the flag was requested before it became unrequestable, leave it as is.
if ($status eq '?' && $flag->{status} ne '?' &&
!$flag->{type}->{is_requestable}) {
ThrowCodeError("flag_status_invalid",
{ id => $id, status => $status });
}
# Make sure the requestee is authorized to access the bug.
# (and attachment, if this installation is using the "insider group"
# feature and the attachment is marked private).
if ($status eq '?'
&& $flag->{type}->{is_requesteeble}
&& trim($data->{"requestee-$id"}))
{
my $requestee_email = trim($data->{"requestee-$id"});
if ($requestee_email ne $flag->{'requestee'}->{'email'}) {
# We know the requestee exists because we ran
# Bugzilla::User::match_field before getting here.
my $requestee = Bugzilla::User->new_from_login($requestee_email);
# Throw an error if the user can't see the bug.
if (!&::CanSeeBug($bug_id, $requestee->id))
{
ThrowUserError("flag_requestee_unauthorized",
{ flag_type => $flag->{'type'},
requestee => $requestee,
bug_id => $bug_id,
attach_id =>
$flag->{target}->{attachment}->{id} });
}
# Throw an error if the target is a private attachment and
# the requestee isn't in the group of insiders who can see it.
if ($flag->{target}->{attachment}->{exists}
&& $data->{'isprivate'}
&& Param("insidergroup")
&& !$requestee->in_group(Param("insidergroup")))
{
ThrowUserError("flag_requestee_unauthorized_attachment",
{ flag_type => $flag->{'type'},
requestee => $requestee,
bug_id => $bug_id,
attach_id =>
$flag->{target}->{attachment}->{id} });
}
}
}
}
}
sub process {
# Processes changes to flags.
# The target is the bug or attachment this flag is about, the timestamp
# is the date/time the bug was last touched (so that changes to the flag
# can be stamped with the same date/time), the data is the form data
# with flag fields that the user submitted, the old bug is the bug record
# before the user made changes to it, and the new bug is the bug record
# after the user made changes to it.
my ($target, $timestamp, $data, $oldbug, $newbug) = @_;
# Use the date/time we were given if possible (allowing calling code
# to synchronize the comment's timestamp with those of other records).
$timestamp = ($timestamp ? &::SqlQuote($timestamp) : "NOW()");
# Take a snapshot of flags before any changes.
my $flags = match({ 'bug_id' => $target->{'bug'}->{'id'} ,
'attach_id' => $target->{'attachment'}->{'id'} ,
'is_active' => 1 });
my @old_summaries;
foreach my $flag (@$flags) {
my $summary = $flag->{'type'}->{'name'} . $flag->{'status'};
$summary .= "(" . $flag->{'requestee'}->login . ")" if $flag->{'requestee'};
push(@old_summaries, $summary);
}
# Create new flags and update existing flags.
my $new_flags = FormToNewFlags($target, $data);
foreach my $flag (@$new_flags) { create($flag, $timestamp) }
modify($data, $timestamp);
# In case the bug's product/component has changed, clear flags that are
# no longer valid.
&::SendSQL("
SELECT flags.id
FROM (flags INNER JOIN bugs ON flags.bug_id = bugs.bug_id)
LEFT OUTER JOIN flaginclusions i
ON (flags.type_id = i.type_id
AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
AND (bugs.component_id = i.component_id OR i.component_id IS NULL))
WHERE bugs.bug_id = $target->{'bug'}->{'id'}
AND flags.is_active = 1
AND i.type_id IS NULL
");
clear(&::FetchOneColumn()) while &::MoreSQLData();
&::SendSQL("
SELECT flags.id
FROM flags, bugs, flagexclusions e
WHERE bugs.bug_id = $target->{'bug'}->{'id'}
AND flags.bug_id = bugs.bug_id
AND flags.type_id = e.type_id
AND flags.is_active = 1
AND (bugs.product_id = e.product_id OR e.product_id IS NULL)
AND (bugs.component_id = e.component_id OR e.component_id IS NULL)
");
clear(&::FetchOneColumn()) while &::MoreSQLData();
# Take a snapshot of flags after changes.
$flags = match({ 'bug_id' => $target->{'bug'}->{'id'} ,
'attach_id' => $target->{'attachment'}->{'id'} ,
'is_active' => 1 });
my @new_summaries;
foreach my $flag (@$flags) {
my $summary = $flag->{'type'}->{'name'} . $flag->{'status'};
$summary .= "(" . $flag->{'requestee'}->login . ")" if $flag->{'requestee'};
push(@new_summaries, $summary);
}
my $old_summaries = join(", ", @old_summaries);
my $new_summaries = join(", ", @new_summaries);
my ($removed, $added) = &::DiffStrings($old_summaries, $new_summaries);
if ($removed ne $added) {
my $sql_removed = &::SqlQuote($removed);
my $sql_added = &::SqlQuote($added);
my $field_id = &::GetFieldID('flagtypes.name');
my $attach_id = $target->{'attachment'}->{'id'} || 'NULL';
&::SendSQL("INSERT INTO bugs_activity (bug_id, attach_id, who, " .
"bug_when, fieldid, removed, added) VALUES " .
"($target->{'bug'}->{'id'}, $attach_id, $::userid, " .
"$timestamp, $field_id, $sql_removed, $sql_added)");
}
}
sub create {
# Creates a flag record in the database.
my ($flag, $timestamp) = @_;
# Determine the ID for the flag record by retrieving the last ID used
# and incrementing it.
&::SendSQL("SELECT MAX(id) FROM flags");
$flag->{'id'} = (&::FetchOneColumn() || 0) + 1;
# Insert a record for the flag into the flags table.
my $attach_id = $flag->{'target'}->{'attachment'}->{'id'} || "NULL";
my $requestee_id = $flag->{'requestee'} ? $flag->{'requestee'}->id : "NULL";
&::SendSQL("INSERT INTO flags (id, type_id,
bug_id, attach_id,
requestee_id, setter_id, status,
creation_date, modification_date)
VALUES ($flag->{'id'},
$flag->{'type'}->{'id'},
$flag->{'target'}->{'bug'}->{'id'},
$attach_id,
$requestee_id,
" . $flag->{'setter'}->id . ",
'$flag->{'status'}',
$timestamp,
$timestamp)");
# Send an email notifying the relevant parties about the flag creation.
if (($flag->{'requestee'}
&& $flag->{'requestee'}->email_prefs->{'FlagRequestee'})
|| $flag->{'type'}->{'cc_list'})
{
notify($flag, "request/email.txt.tmpl");
}
}
sub migrate {
# Moves a flag from one attachment to another. Useful for migrating
# a flag from an obsolete attachment to the attachment that obsoleted it.
my ($old_attach_id, $new_attach_id, $timestamp) = @_;
# Use the date/time we were given if possible (allowing calling code
# to synchronize the comment's timestamp with those of other records).
$timestamp = ($timestamp ? &::SqlQuote($timestamp) : "NOW()");
# Update the record in the flags table to point to the new attachment.
&::SendSQL("UPDATE flags " .
"SET attach_id = $new_attach_id , " .
" modification_date = $timestamp " .
"WHERE attach_id = $old_attach_id");
}
sub modify {
# Modifies flags in the database when a user changes them.
# Note that modified flags are always set active (is_active = 1) -
# this will revive deleted flags that get changed through
# attachment.cgi midairs. See bug 223878 for details.
my ($data, $timestamp) = @_;
# Use the date/time we were given if possible (allowing calling code
# to synchronize the comment's timestamp with those of other records).
$timestamp = ($timestamp ? &::SqlQuote($timestamp) : "NOW()");
# Extract a list of flags from the form data.
my @ids = map(/^flag-(\d+)$/ ? $1 : (), keys %$data);
# Loop over flags and update their record in the database if necessary.
# Two kinds of changes can happen to a flag: it can be set to a different
# state, and someone else can be asked to set it. We take care of both
# those changes.
my @flags;
foreach my $id (@ids) {
my $flag = get($id);
my $status = $data->{"flag-$id"};
my $requestee_email = trim($data->{"requestee-$id"});
# Ignore flags the user didn't change. There are two components here:
# either the status changes (trivial) or the requestee changes.
# Change of either field will cause full update of the flag.
my $status_changed = ($status ne $flag->{'status'});
# Requestee is considered changed, if all of the following apply:
# 1. Flag status is '?' (requested)
# 2. Flag can have a requestee
# 3. The requestee specified on the form is different from the
# requestee specified in the db.
my $old_requestee =
$flag->{'requestee'} ? $flag->{'requestee'}->login : '';
my $requestee_changed =
($status eq "?" &&
$flag->{'type'}->{'is_requesteeble'} &&
$old_requestee ne $requestee_email);
next unless ($status_changed || $requestee_changed);
# Since the status is validated, we know it's safe, but it's still
# tainted, so we have to detaint it before using it in a query.
&::trick_taint($status);
if ($status eq '+' || $status eq '-') {
&::SendSQL("UPDATE flags
SET setter_id = $::userid ,
requestee_id = NULL ,
status = '$status' ,
modification_date = $timestamp ,
is_active = 1
WHERE id = $flag->{'id'}");
# Send an email notifying the relevant parties about the fulfillment.
if ($flag->{'setter'}->email_prefs->{'FlagRequester'}
|| $flag->{'type'}->{'cc_list'})
{
$flag->{'status'} = $status;
notify($flag, "request/email.txt.tmpl");
}
}
elsif ($status eq '?') {
# Get the requestee, if any.
my $requestee_id = "NULL";
if ($requestee_email) {
$requestee_id = &::DBname_to_id($requestee_email);
$flag->{'requestee'} = new Bugzilla::User($requestee_id);
}
# Update the database with the changes.
&::SendSQL("UPDATE flags
SET setter_id = $::userid ,
requestee_id = $requestee_id ,
status = '$status' ,
modification_date = $timestamp ,
is_active = 1
WHERE id = $flag->{'id'}");
# Send an email notifying the relevant parties about the request.
if ($flag->{'requestee'}
&& ($flag->{'requestee'}->email_prefs->{'FlagRequestee'}
|| $flag->{'type'}->{'cc_list'}))
{
notify($flag, "request/email.txt.tmpl");
}
}
# The user unset the flag; set is_active = 0
elsif ($status eq 'X') {
clear($flag->{'id'});
}
push(@flags, $flag);
}
return \@flags;
}
sub clear {
my ($id) = @_;
my $flag = get($id);
&::PushGlobalSQLState();
&::SendSQL("UPDATE flags SET is_active = 0 WHERE id = $id");
&::PopGlobalSQLState();
$flag->{'exists'} = 0;
# Set the flag's status to "cleared" so the email template
# knows why email is being sent about the request.
$flag->{'status'} = "X";
notify($flag, "request/email.txt.tmpl") if $flag->{'requestee'};
}
################################################################################
# Utility Functions
################################################################################
sub FormToNewFlags {
my ($target, $data) = @_;
# Get information about the setter to add to each flag.
# Uses a conditional to suppress Perl's "used only once" warnings.
my $setter = new Bugzilla::User($::userid);
# Extract a list of flag type IDs from field names.
my @type_ids = map(/^flag_type-(\d+)$/ ? $1 : (), keys %$data);
@type_ids = grep($data->{"flag_type-$_"} ne 'X', @type_ids);
# Process the form data and create an array of flag objects.
my @flags;
foreach my $type_id (@type_ids) {
my $status = $data->{"flag_type-$type_id"};
&::trick_taint($status);
# Create the flag record and populate it with data from the form.
my $flag = {
type => Bugzilla::FlagType::get($type_id) ,
target => $target ,
setter => $setter ,
status => $status
};
if ($status eq "?") {
my $requestee = $data->{"requestee_type-$type_id"};
if ($requestee) {
my $requestee_id = &::DBname_to_id($requestee);
$flag->{'requestee'} = new Bugzilla::User($requestee_id);
}
}
# Add the flag to the array of flags.
push(@flags, $flag);
}
# Return the list of flags.
return \@flags;
}
# Ideally, we'd use Bug.pm, but it's way too heavyweight, and it can't be
# made lighter without totally rewriting it, so we'll use this function
# until that one gets rewritten.
sub GetBug {
# Returns a hash of information about a target bug.
my ($id) = @_;
# Save the currently running query (if any) so we do not overwrite it.
&::PushGlobalSQLState();
&::SendSQL("SELECT 1, short_desc, product_id, component_id,
COUNT(bug_group_map.group_id)
FROM bugs LEFT JOIN bug_group_map
ON (bugs.bug_id = bug_group_map.bug_id)
WHERE bugs.bug_id = $id
GROUP BY bugs.bug_id");
my $bug = { 'id' => $id };
($bug->{'exists'}, $bug->{'summary'}, $bug->{'product_id'},
$bug->{'component_id'}, $bug->{'restricted'}) = &::FetchSQLData();
# Restore the previously running query (if any).
&::PopGlobalSQLState();
return $bug;
}
sub GetTarget {
my ($bug_id, $attach_id) = @_;
# Create an object representing the target bug/attachment.
my $target = { 'exists' => 0 };
if ($attach_id) {
$target->{'attachment'} = new Bugzilla::Attachment($attach_id);
if ($bug_id) {
# Make sure the bug and attachment IDs correspond to each other
# (i.e. this is the bug to which this attachment is attached).
$bug_id == $target->{'attachment'}->{'bug_id'}
|| return { 'exists' => 0 };
}
$target->{'bug'} = GetBug($target->{'attachment'}->{'bug_id'});
$target->{'exists'} = $target->{'attachment'}->{'exists'};
$target->{'type'} = "attachment";
}
elsif ($bug_id) {
$target->{'bug'} = GetBug($bug_id);
$target->{'exists'} = $target->{'bug'}->{'exists'};
$target->{'type'} = "bug";
}
return $target;
}
sub notify {
# Sends an email notification about a flag being created or fulfilled.
my ($flag, $template_file) = @_;
# If the target bug is restricted to one or more groups, then we need
# to make sure we don't send email about it to unauthorized users
# on the request type's CC: list, so we have to trawl the list for users
# not in those groups or email addresses that don't have an account.
if ($flag->{'target'}->{'bug'}->{'restricted'}
|| $flag->{'target'}->{'attachment'}->{'isprivate'})
{
my @new_cc_list;
foreach my $cc (split(/[, ]+/, $flag->{'type'}->{'cc_list'})) {
my $ccuser = Bugzilla::User->new_from_login($cc,
TABLES_ALREADY_LOCKED)
|| next;
next if $flag->{'target'}->{'bug'}->{'restricted'}
&& !&::CanSeeBug($flag->{'target'}->{'bug'}->{'id'}, $ccuser->id);
next if $flag->{'target'}->{'attachment'}->{'isprivate'}
&& Param("insidergroup")
&& !$ccuser->in_group(Param("insidergroup"));
push(@new_cc_list, $cc);
}
$flag->{'type'}->{'cc_list'} = join(", ", @new_cc_list);
}
$flag->{'requestee'}->{'email'} .= Param('emailsuffix');
$flag->{'setter'}->{'email'} .= Param('emailsuffix');
$::vars->{'flag'} = $flag;
my $message;
my $rv =
$::template->process($template_file, $::vars, \$message);
if (!$rv) {
Bugzilla->cgi->header();
ThrowTemplateError($::template->error());
}
Bugzilla::BugMail::MessageToMTA($message);
}
################################################################################
# Private Functions
################################################################################
sub sqlify_criteria {
# Converts a hash of criteria into a list of SQL criteria.
# a reference to a hash containing the criteria (field => value)
my ($criteria) = @_;
# 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 the caller specified only bug or attachment flags,
# limit the query to those kinds of flags.
if (defined($criteria->{'target_type'})) {
if ($criteria->{'target_type'} eq 'bug') { push(@criteria, "attach_id IS NULL") }
elsif ($criteria->{'target_type'} eq 'attachment') { push(@criteria, "attach_id IS NOT NULL") }
}
# Go through each criterion from the calling code and add it to the query.
foreach my $field (keys %$criteria) {
my $value = $criteria->{$field};
next unless defined($value);
if ($field eq 'type_id') { push(@criteria, "type_id = $value") }
elsif ($field eq 'bug_id') { push(@criteria, "bug_id = $value") }
elsif ($field eq 'attach_id') { push(@criteria, "attach_id = $value") }
elsif ($field eq 'requestee_id') { push(@criteria, "requestee_id = $value") }
elsif ($field eq 'setter_id') { push(@criteria, "setter_id = $value") }
elsif ($field eq 'status') { push(@criteria, "status = '$value'") }
elsif ($field eq 'is_active') { push(@criteria, "is_active = $value") }
}
return @criteria;
}
sub perlify_record {
# Converts a row from the database into a Perl record.
my ($exists, $id, $type_id, $bug_id, $attach_id,
$requestee_id, $setter_id, $status) = @_;
return undef unless defined($exists);
my $flag =
{
exists => $exists ,
id => $id ,
type => Bugzilla::FlagType::get($type_id) ,
target => GetTarget($bug_id, $attach_id) ,
requestee => $requestee_id ? new Bugzilla::User($requestee_id) : undef,
setter => new Bugzilla::User($setter_id) ,
status => $status ,
};
return $flag;
}
1;

View File

@@ -1,376 +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>
################################################################################
# Module Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use strict;
# This module implements flag types for the flag tracker.
package Bugzilla::FlagType;
# Use Bugzilla's User module which contains utilities for handling users.
use Bugzilla::User;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Config;
# Note! This module requires that its caller have said "require CGI.pl"
# to import relevant functions from that script and its companion globals.pl.
################################################################################
# Global Variables
################################################################################
# basic sets of columns and tables for getting flag types from the database
my @base_columns =
("1", "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");
# Note: when adding tables to @base_tables, make sure to include the separator
# (i.e. a comma or 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.
my @base_tables = ("flagtypes");
################################################################################
# Public Functions
################################################################################
sub get {
# Returns a hash of information about a flag type.
my ($id) = @_;
my $select_clause = "SELECT " . join(", ", @base_columns);
my $from_clause = "FROM " . join(" ", @base_tables);
&::PushGlobalSQLState();
&::SendSQL("$select_clause $from_clause WHERE flagtypes.id = $id");
my @data = &::FetchSQLData();
my $type = perlify_record(@data);
&::PopGlobalSQLState();
return $type;
}
sub get_inclusions {
my ($id) = @_;
return get_clusions($id, "in");
}
sub get_exclusions {
my ($id) = @_;
return get_clusions($id, "ex");
}
sub get_clusions {
my ($id, $type) = @_;
&::PushGlobalSQLState();
&::SendSQL("SELECT products.name, 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 = $id AND flag${type}clusions.type_id = flagtypes.id");
my @clusions = ();
while (&::MoreSQLData()) {
my ($product, $component) = &::FetchSQLData();
$product ||= "Any";
$component ||= "Any";
push(@clusions, "$product:$component");
}
&::PopGlobalSQLState();
return \@clusions;
}
sub match {
# Queries the database for flag types matching the given criteria
# and returns the set of matching types.
my ($criteria, $include_count) = @_;
my @tables = @base_tables;
my @columns = @base_columns;
my $having = "";
# Include a count of the number of flags per type if requested.
if ($include_count) {
push(@columns, "COUNT(flags.id)");
push(@tables, "LEFT OUTER JOIN flags ON flagtypes.id = flags.type_id");
}
# Generate the SQL WHERE criteria.
my @criteria = sqlify_criteria($criteria, \@tables, \@columns, \$having);
# Build the query, grouping the types if we are counting flags.
my $select_clause = "SELECT " . join(", ", @columns);
my $from_clause = "FROM " . join(" ", @tables);
my $where_clause = "WHERE " . join(" AND ", @criteria);
my $query = "$select_clause $from_clause $where_clause";
$query .= " GROUP BY flagtypes.id " if ($include_count || $having ne "");
$query .= " HAVING $having " if $having ne "";
$query .= " ORDER BY flagtypes.sortkey, flagtypes.name";
# Execute the query and retrieve the results.
&::PushGlobalSQLState();
&::SendSQL($query);
my @types;
while (&::MoreSQLData()) {
my @data = &::FetchSQLData();
my $type = perlify_record(@data);
push(@types, $type);
}
&::PopGlobalSQLState();
return \@types;
}
sub count {
# Returns the total number of flag types matching the given criteria.
my ($criteria) = @_;
# Generate query components.
my @tables = @base_tables;
my @columns = ("COUNT(flagtypes.id)");
my $having = "";
my @criteria = sqlify_criteria($criteria, \@tables, \@columns, \$having);
# Build the query.
my $select_clause = "SELECT " . join(", ", @columns);
my $from_clause = "FROM " . join(" ", @tables);
my $where_clause = "WHERE " . join(" AND ", @criteria);
my $query = "$select_clause $from_clause $where_clause";
$query .= " GROUP BY flagtypes.id HAVING $having " if $having ne "";
# Execute the query and get the results.
&::PushGlobalSQLState();
&::SendSQL($query);
my $count = &::FetchOneColumn();
&::PopGlobalSQLState();
return $count;
}
sub validate {
my ($data, $bug_id, $attach_id) = @_;
# Get a list of flag types to validate. Uses the "map" function
# to extract flag type IDs from form field names by matching columns
# whose name looks like "flag_type-nnn", where "nnn" is the ID,
# and returning just the ID portion of matching field names.
my @ids = map(/^flag_type-(\d+)$/ ? $1 : (), keys %$data);
foreach my $id (@ids)
{
my $status = $data->{"flag_type-$id"};
# Don't bother validating types the user didn't touch.
next if $status eq "X";
# Make sure the flag type exists.
my $flag_type = get($id);
$flag_type
|| ThrowCodeError("flag_type_nonexistent", { id => $id });
# Make sure the value of the field is a valid status.
grep($status eq $_, qw(X + - ?))
|| ThrowCodeError("flag_status_invalid",
{ id => $id , status => $status });
# Make sure the user didn't request the flag unless it's requestable.
if ($status eq '?' && !$flag_type->{is_requestable}) {
ThrowCodeError("flag_status_invalid",
{ id => $id , status => $status });
}
# Make sure the requestee is authorized to access the bug
# (and attachment, if this installation is using the "insider group"
# feature and the attachment is marked private).
if ($status eq '?'
&& $flag_type->{is_requesteeble}
&& trim($data->{"requestee_type-$id"}))
{
my $requestee_email = trim($data->{"requestee_type-$id"});
# We know the requestee exists because we ran
# Bugzilla::User::match_field before getting here.
my $requestee = Bugzilla::User->new_from_login($requestee_email);
# Throw an error if the user can't see the bug.
if (!&::CanSeeBug($bug_id, $requestee->id))
{
ThrowUserError("flag_requestee_unauthorized",
{ flag_type => $flag_type,
requestee => $requestee,
bug_id => $bug_id,
attach_id => $attach_id });
}
# Throw an error if the target is a private attachment and
# the requestee isn't in the group of insiders who can see it.
if ($attach_id
&& Param("insidergroup")
&& $data->{'isprivate'}
&& !$requestee->in_group(Param("insidergroup")))
{
ThrowUserError("flag_requestee_unauthorized_attachment",
{ flag_type => $flag_type,
requestee => $requestee,
bug_id => $bug_id,
attach_id => $attach_id });
}
}
}
}
sub normalize {
# Given a list of flag types, checks its flags to make sure they should
# still exist after a change to the inclusions/exclusions lists.
# A list of IDs of flag types to normalize.
my (@ids) = @_;
my $ids = join(", ", @ids);
# Check for flags whose product/component is no longer included.
&::SendSQL("
SELECT flags.id
FROM (flags INNER JOIN bugs ON flags.bug_id = bugs.bug_id)
LEFT OUTER JOIN flaginclusions AS i
ON (flags.type_id = i.type_id
AND (bugs.product_id = i.product_id OR i.product_id IS NULL)
AND (bugs.component_id = i.component_id OR i.component_id IS NULL))
WHERE flags.type_id IN ($ids)
AND flags.is_active = 1
AND i.type_id IS NULL
");
Bugzilla::Flag::clear(&::FetchOneColumn()) while &::MoreSQLData();
&::SendSQL("
SELECT flags.id
FROM flags, bugs, flagexclusions AS e
WHERE flags.type_id IN ($ids)
AND flags.bug_id = bugs.bug_id
AND flags.type_id = e.type_id
AND flags.is_active = 1
AND (bugs.product_id = e.product_id OR e.product_id IS NULL)
AND (bugs.component_id = e.component_id OR e.component_id IS NULL)
");
Bugzilla::Flag::clear(&::FetchOneColumn()) while &::MoreSQLData();
}
################################################################################
# Private Functions
################################################################################
sub sqlify_criteria {
# 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, $columns is a reference to an array of columns
# being returned by the query, and $having is a reference to
# a criterion to put into the HAVING clause.
my ($criteria, $tables, $columns, $having) = @_;
# 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}) {
push(@criteria, "flagtypes.name = " . &::SqlQuote($criteria->{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 = &::SqlQuote(substr($criteria->{target_type}, 0, 1));
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, ", flaginclusions");
push(@criteria, "flagtypes.id = flaginclusions.type_id");
push(@criteria, "(flaginclusions.product_id = $product_id " .
" OR flaginclusions.product_id IS NULL)");
push(@criteria, "(flaginclusions.component_id = $component_id " .
" OR flaginclusions.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 count the number of exclusions records returned
# and use a HAVING clause to weed out types with one or more exclusions.
my $join_clause = "flagtypes.id = flagexclusions.type_id " .
"AND (flagexclusions.product_id = $product_id " .
"OR flagexclusions.product_id IS NULL) " .
"AND (flagexclusions.component_id = $component_id " .
"OR flagexclusions.component_id IS NULL)";
push(@$tables, "LEFT JOIN flagexclusions ON ($join_clause)");
push(@$columns, "COUNT(flagexclusions.type_id) AS num_exclusions");
$$having = "num_exclusions = 0";
}
return @criteria;
}
sub perlify_record {
# Converts data retrieved from the database into a Perl record.
my $type = {};
$type->{'exists'} = $_[0];
$type->{'id'} = $_[1];
$type->{'name'} = $_[2];
$type->{'description'} = $_[3];
$type->{'cc_list'} = $_[4];
$type->{'target_type'} = $_[5] eq "b" ? "bug" : "attachment";
$type->{'sortkey'} = $_[6];
$type->{'is_active'} = $_[7];
$type->{'is_requestable'} = $_[8];
$type->{'is_requesteeble'} = $_[9];
$type->{'is_multiplicable'} = $_[10];
$type->{'flag_count'} = $_[11];
return $type;
}
1;

View File

@@ -1,267 +0,0 @@
#
# 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) 2000 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Dan Mosedale <dmose@mozilla.org>
# Terry Weissman <terry@mozilla.org>
# Dave Miller <justdave@syndicomm.com>
# This object models a set of relations between one item and a group
# of other items. An example is the set of relations between one bug
# and the users CCed on that bug. Currently, the relation objects are
# expected to be bugzilla userids. However, this could and perhaps
# should be generalized to work with non userid objects, such as
# keywords associated with a bug. That shouldn't be hard to do; it
# might involve turning this into a virtual base class, and having
# UserSet and KeywordSet types that inherit from it.
use strict;
# XXX - mod_perl
# Everything that uses Bugzilla::RelationSet should already have globals.pl
# loaded so we don't want to load it here. Doing so causes a loop in Perl
# because globals.pl turns around and does a 'use Bugzilla::RelationSet'
# See http://bugzilla.mozilla.org/show_bug.cgi?id=72862
#require "../globals.pl";
package Bugzilla::RelationSet;
# create a new empty Bugzilla::RelationSet
#
sub new {
my $type = shift();
# create a ref to an empty hash and bless it
#
my $self = {};
bless $self, $type;
# construct from a comma-delimited string
#
if ($#_ == 0) {
$self->mergeFromString($_[0]);
}
# unless this was a constructor for an empty list, somebody screwed up.
#
elsif ( $#_ != -1 ) {
confess("invalid number of arguments");
}
# bless as a Bugzilla::RelationSet
#
return $self;
}
# Assumes that the set of relations "FROM $table WHERE $constantSql and
# $column = $value" is currently represented by $self, and this set should
# be updated to look like $other.
#
# Returns an array of two strings, one INSERT and one DELETE, which will
# make this change. Either or both strings may be the empty string,
# meaning that no INSERT or DELETE or both (respectively) need to be done.
#
# THE CALLER IS RESPONSIBLE FOR ANY DESIRED LOCKING AND/OR CONSISTENCY
# CHECKS (not to mention doing the SendSQL() calls).
#
sub generateSqlDeltas {
($#_ == 5) || confess("invalid number of arguments");
my ( $self, # instance ptr to set representing the existing state
$endState, # instance ptr to set representing the desired state
$table, # table where these relations are kept
$invariantName, # column held const for a Bugzilla::RelationSet (often "bug_id")
$invariantValue, # what to hold the above column constant at
$columnName # the column which varies (often a userid)
) = @_;
# construct the insert list by finding relations which exist in the
# end state but not the current state.
#
my @endStateRelations = keys(%$endState);
my @insertList = ();
foreach ( @endStateRelations ) {
push ( @insertList, $_ ) if ( ! exists $$self{"$_"} );
}
# we've built the list. If it's non-null, add required sql chrome.
#
my $sqlInsert="";
if ( $#insertList > -1 ) {
$sqlInsert = "INSERT INTO $table ($invariantName, $columnName) VALUES " .
join (",",
map ( "($invariantValue, $_)" , @insertList )
);
}
# construct the delete list by seeing which relations exist in the
# current state but not the end state
#
my @selfRelations = keys(%$self);
my @deleteList = ();
foreach ( @selfRelations ) {
push (@deleteList, $_) if ( ! exists $$endState{"$_"} );
}
# we've built the list. if it's non-empty, add required sql chrome.
#
my $sqlDelete = "";
if ( $#deleteList > -1 ) {
$sqlDelete = "DELETE FROM $table WHERE $invariantName = $invariantValue " .
"AND $columnName IN ( " . join (",", @deleteList) . " )";
}
return ($sqlInsert, $sqlDelete);
}
# compare the current object with another.
#
sub isEqual {
($#_ == 1) || confess("invalid number of arguments");
my $self = shift();
my $other = shift();
# get arrays of the keys for faster processing
#
my @selfRelations = keys(%$self);
my @otherRelations = keys(%$other);
# make sure the arrays are the same size
#
return 0 if ( $#selfRelations != $#otherRelations );
# bail out if any of the elements are different
#
foreach my $relation ( @selfRelations ) {
return 0 if ( !exists $$other{$relation})
}
# we made it!
#
return 1;
}
# merge the results of a SQL command into this set
#
sub mergeFromDB {
( $#_ == 1 ) || confess("invalid number of arguments");
my $self = shift();
&::SendSQL(shift());
while (my @row = &::FetchSQLData()) {
$$self{$row[0]} = 1;
}
return;
}
# merge a set in string form into this set
#
sub mergeFromString {
($#_ == 1) || confess("invalid number of arguments");
my $self = shift();
# do the merge
#
foreach my $person (split(/[ ,]/, shift())) {
if ($person ne "") {
$$self{&::DBNameToIdAndCheck($person)} = 1;
}
}
}
# remove a set in string form from this set
#
sub removeItemsInString {
($#_ == 1) || confess("invalid number of arguments");
my $self = shift();
# do the merge
#
foreach my $person (split(/[ ,]/, shift())) {
if ($person ne "") {
my $dbid = &::DBNameToIdAndCheck($person);
if (exists $$self{$dbid}) {
delete $$self{$dbid};
}
}
}
}
# remove a set in array form from this set
#
sub removeItemsInArray {
($#_ > 0) || confess("invalid number of arguments");
my $self = shift();
# do the merge
#
while (my $person = shift()) {
if ($person ne "") {
my $dbid = &::DBNameToIdAndCheck($person);
if (exists $$self{$dbid}) {
delete $$self{$dbid};
}
}
}
}
# return the number of elements in this set
#
sub size {
my $self = shift();
my @k = keys(%$self);
return $#k++;
}
# return this set in array form
#
sub toArray {
my $self= shift();
return keys(%$self);
}
# return this set as an array of strings
#
sub toArrayOfStrings {
($#_ == 0) || confess("invalid number of arguments");
my $self = shift();
my @result = ();
foreach my $i ( keys %$self ) {
push @result, &::DBID_to_name($i);
}
return sort { lc($a) cmp lc($b) } @result;
}
# return this set in string form (comma-separated and sorted)
#
sub toString {
($#_ == 0) || confess("invalid number of arguments");
my $self = shift();
my @result = ();
foreach my $i ( keys %$self ) {
push @result, &::DBID_to_name($i);
}
return join(',', sort(@result));
}
1;

File diff suppressed because it is too large Load Diff

View File

@@ -1,258 +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>
use strict;
use lib ".";
# 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;
use Bugzilla::Util;
use Bugzilla::User;
use constant PUBLIC_USER_ID => 0;
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.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 " .
"(public = 1 OR creator = " . Bugzilla->user->id . " OR " .
"(ugm.group_id IS NOT NULL)) " .
"GROUP BY series_id");
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'}) = @_;
}
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 &::UserInGroup('admin');
}
sub writeToDatabase {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->do("LOCK TABLES series_categories WRITE, series WRITE");
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 = ?, 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, public) VALUES " .
"($self->{'creator'}, " .
"$category_id, $subcategory_id, " .
$dbh->quote($self->{'name'}) . ", $self->{'frequency'}," .
$dbh->quote($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->do("UNLOCK TABLES");
}
# 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;

View File

@@ -1,410 +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>
package Bugzilla::Template;
use strict;
use Bugzilla::Config qw(:DEFAULT $templatedir $datadir);
use Bugzilla::Util;
# for time2str - replace by TT Date plugin??
use Date::Format ();
use base qw(Template);
# XXX - mod_perl
my $template_include_path;
# 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 sortAcceptLanguage {
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));
}
# 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
sub getTemplateIncludePath () {
# Return cached value if available
# XXXX - mod_perl!
if ($template_include_path) {
return $template_include_path;
}
my $languages = trim(Param('languages'));
if (not ($languages =~ /,/)) {
return $template_include_path =
["$templatedir/$languages/custom",
"$templatedir/$languages/extension",
"$templatedir/$languages/default"];
}
my @languages = sortAcceptLanguage($languages);
my @accept_language = sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "" );
my @usedlanguages;
foreach my $lang (@accept_language) {
# Per RFC 1766 and RFC 2616 any language tag matches also its
# primary tag. That is 'en' (accept lanuage) matches 'en-us',
# 'en-uk' etc. but not the otherway round. (This is unfortunally
# 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 /^$lang(-.+)?$/i, @languages) {
push (@usedlanguages, @found);
}
}
push(@usedlanguages, Param('defaultlanguage'));
return $template_include_path =
[map(("$templatedir/$_/custom",
"$templatedir/$_/extension",
"$templatedir/$_/default"),
@usedlanguages)];
}
###############################################################################
# Templatization Code
# 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;
# 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 => "$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 checksetup.pl
# and 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;
},
# HTML collapses newlines in element attributes to a single space,
# so form elements which may have whitespace (ie comments) need
# to be encoded using &#013;
# See bugs 4928, 22983 and 32000 for more details
html_linebreak => sub {
my ($var) = @_;
$var =~ s/\r\n/\&#013;/g;
$var =~ s/\n\r/\&#013;/g;
$var =~ s/\r/\&#013;/g;
$var =~ s/\n/\&#013;/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 => \&::quoteUrls ,
bug_link => [ sub {
my ($context, $bug) = @_;
return sub {
my $text = shift;
return &::GetBugLink($bug, $text);
};
},
1
],
# 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,
# Override html filter to obscure the '@' in user visible strings
# See bug 120030 for details
html => sub {
my ($var) = Template::Filters::html_filter(@_);
$var =~ s/\@/\&#64;/g;
return $var;
},
# 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
],
# 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',
# Default variables for all templates
VARIABLES => {
# Function for retrieving global parameters.
'Param' => \&Bugzilla::Config::Param,
# Function to create date strings
'time2str' => \&Date::Format::time2str,
# Generic linear search function
'lsearch' => \&Bugzilla::Util::lsearch,
# Currently logged in user, if any
'user' => sub { return Bugzilla->user; },
# UserInGroup. Deprecated - use the user.* functions instead
'UserInGroup' => \&::UserInGroup,
# SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
'SendBugMail' => sub {
my ($id, $mailrecipients) = (@_);
require Bugzilla::BugMail;
Bugzilla::BugMail::Send($id, $mailrecipients);
},
# Bugzilla version
# This could be made a ref, or even a CONSTANT with TT2.08
'VERSION' => $Bugzilla::Config::VERSION ,
},
}) || die("Template creation failed: " . $class->error());
}
1;
__END__
=head1 NAME
Bugzilla::Template - Wrapper arround the Template Toolkit C<Template> object
=head1 SYNOPSYS
my $template = Bugzilla::Template->create;
=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 SEE ALSO
L<Bugzilla>, L<Template>

View File

@@ -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>

View File

@@ -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): Myk Melez <myk@mozilla.org>
#
package Bugzilla::Template::Plugin::Hook;
use strict;
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) = @_;
my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
my $template = $self->{_CONTEXT}->stash->{component}->{name};
my @hooks = ();
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 SEE ALSO
L<Template::Plugin>,
L<http://bugzilla.mozilla.org/show_bug.cgi?id=229658>

View File

@@ -1,267 +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>
################################################################################
# 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::Config;
use Bugzilla::Error;
use Bugzilla::BugMail;
use Date::Format;
# This module requires that its caller have said "require CGI.pl" to import
# relevant functions from that script and its companion globals.pl.
################################################################################
# Constants
################################################################################
# The maximum number of days a token will remain valid.
my $maxtokenage = 3;
################################################################################
# Functions
################################################################################
sub IssueEmailChangeToken {
my ($userid, $old_email, $new_email) = @_;
my $token_ts = time();
my $issuedate = time2str("%Y-%m-%d %H:%M", $token_ts);
# Generate a unique token and insert it into the tokens table.
# We have to lock the tokens table before generating the token,
# since the database must be queried for token uniqueness.
&::SendSQL("LOCK TABLES tokens WRITE");
my $token = GenerateUniqueToken();
my $quotedtoken = &::SqlQuote($token);
my $quoted_emails = &::SqlQuote($old_email . ":" . $new_email);
&::SendSQL("INSERT INTO tokens ( userid , issuedate , token ,
tokentype , eventdata )
VALUES ( $userid , '$issuedate' , $quotedtoken ,
'emailold' , $quoted_emails )");
my $newtoken = GenerateUniqueToken();
$quotedtoken = &::SqlQuote($newtoken);
&::SendSQL("INSERT INTO tokens ( userid , issuedate , token ,
tokentype , eventdata )
VALUES ( $userid , '$issuedate' , $quotedtoken ,
'emailnew' , $quoted_emails )");
&::SendSQL("UNLOCK TABLES");
# Mail the user the token along with instructions for using it.
my $template = $::template;
my $vars = $::vars;
$vars->{'oldemailaddress'} = $old_email . Param('emailsuffix');
$vars->{'newemailaddress'} = $new_email . Param('emailsuffix');
$vars->{'max_token_age'} = $maxtokenage;
$vars->{'token_ts'} = $token_ts;
$vars->{'token'} = $token;
$vars->{'emailaddress'} = $old_email . Param('emailsuffix');
my $message;
$template->process("account/email/change-old.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla::BugMail::MessageToMTA($message);
$vars->{'token'} = $newtoken;
$vars->{'emailaddress'} = $new_email . Param('emailsuffix');
$message = "";
$template->process("account/email/change-new.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla::BugMail::MessageToMTA($message);
}
sub IssuePasswordToken {
# 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.
my ($loginname) = @_;
# Retrieve the user's ID from the database.
my $quotedloginname = &::SqlQuote($loginname);
&::SendSQL("SELECT profiles.userid, tokens.issuedate FROM profiles
LEFT JOIN tokens
ON tokens.userid = profiles.userid
AND tokens.tokentype = 'password'
AND tokens.issuedate > DATE_SUB(NOW(), INTERVAL 10 MINUTE)
WHERE login_name = $quotedloginname");
my ($userid, $toosoon) = &::FetchSQLData();
if ($toosoon) {
ThrowUserError('too_soon_for_new_token');
};
my $token_ts = time();
# Generate a unique token and insert it into the tokens table.
# We have to lock the tokens table before generating the token,
# since the database must be queried for token uniqueness.
&::SendSQL("LOCK TABLES tokens WRITE");
my $token = GenerateUniqueToken();
my $quotedtoken = &::SqlQuote($token);
my $quotedipaddr = &::SqlQuote($::ENV{'REMOTE_ADDR'});
&::SendSQL("INSERT INTO tokens ( userid , issuedate , token , tokentype , eventdata )
VALUES ( $userid , NOW() , $quotedtoken , 'password' , $quotedipaddr )");
&::SendSQL("UNLOCK TABLES");
# Mail the user the token along with instructions for using it.
my $template = $::template;
my $vars = $::vars;
$vars->{'token'} = $token;
$vars->{'emailaddress'} = $loginname . Param('emailsuffix');
$vars->{'max_token_age'} = $maxtokenage;
$vars->{'token_ts'} = $token_ts;
my $message = "";
$template->process("account/password/forgotten-password.txt.tmpl",
$vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla::BugMail::MessageToMTA($message);
}
sub CleanTokenTable {
&::SendSQL("LOCK TABLES tokens WRITE");
&::SendSQL("DELETE FROM tokens
WHERE TO_DAYS(NOW()) - TO_DAYS(issuedate) >= " . $maxtokenage);
&::SendSQL("UNLOCK TABLES");
}
sub GenerateUniqueToken {
# Generates a unique random token. Uses &GenerateRandomPassword
# 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 $token;
my $duplicate = 1;
my $tries = 0;
while ($duplicate) {
++$tries;
if ($tries > 100) {
ThrowCodeError("token_generation_error");
}
$token = &::GenerateRandomPassword();
&::SendSQL("SELECT userid FROM tokens WHERE token = " . &::SqlQuote($token));
$duplicate = &::FetchSQLData();
}
return $token;
}
sub Cancel {
# Cancels a previously issued token and notifies the system administrator.
# 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.
my ($token, $cancelaction) = @_;
# Quote the token for inclusion in SQL statements.
my $quotedtoken = &::SqlQuote($token);
# Get information about the token being cancelled.
&::SendSQL("SELECT issuedate , tokentype , eventdata , login_name , realname
FROM tokens, profiles
WHERE tokens.userid = profiles.userid
AND token = $quotedtoken");
my ($issuedate, $tokentype, $eventdata, $loginname, $realname) = &::FetchSQLData();
# Get the email address of the Bugzilla maintainer.
my $maintainer = Param('maintainer');
my $template = $::template;
my $vars = $::vars;
$vars->{'emailaddress'} = $loginname . Param('emailsuffix');
$vars->{'maintainer'} = $maintainer;
$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 $message;
$template->process("account/cancel-token.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla::BugMail::MessageToMTA($message);
# Delete the token from the database.
&::SendSQL("LOCK TABLES tokens WRITE");
&::SendSQL("DELETE FROM tokens WHERE token = $quotedtoken");
&::SendSQL("UNLOCK TABLES");
}
sub DeletePasswordTokens {
my ($userid, $reason) = @_;
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare("SELECT token " .
"FROM tokens " .
"WHERE userid=? AND tokentype='password'");
$sth->execute($userid);
while (my $token = $sth->fetchrow_array) {
Bugzilla::Token::Cancel($token, $reason);
}
}
sub HasEmailChangeToken {
# Returns an email change token if the user has one.
my ($userid) = @_;
&::SendSQL("SELECT token FROM tokens WHERE userid = $userid " .
"AND (tokentype = 'emailnew' OR tokentype = 'emailold') " .
"LIMIT 1");
my ($token) = &::FetchSQLData();
return $token;
}
1;

View File

@@ -1,857 +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>
# Erik Stambaugh <not_erik@dasbistro.com>
# Bradley Baetz <bbaetz@acm.org>
# Joel Peshkin <bugreport@peshkin.net>
################################################################################
# Module Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use strict;
# This module implements utilities for dealing with Bugzilla users.
package Bugzilla::User;
use Bugzilla::Config;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Constants;
################################################################################
# Functions
################################################################################
sub new {
my $invocant = shift;
return $invocant->_create("userid=?", @_);
}
# This routine is sort of evil. Nothing except the login stuff should
# be dealing with addresses as an input, and they can get the id as a
# side effect of the other sql they have to do anyway.
# Bugzilla::BugMail still does this, probably as a left over from the
# pre-id days. Provide this as a helper, but don't document it, and hope
# that it can go away.
# The request flag stuff also does this, but it really should be passing
# in the id its already had to validate (or the User.pm object, of course)
sub new_from_login {
my $invocant = shift;
return $invocant->_create("login_name=?", @_);
}
# Internal helper for the above |new| methods
# $cond is a string (including a placeholder ?) for the search
# requirement for the profiles table
sub _create {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $cond = shift;
my $val = shift;
# We're checking for validity here, so any value is OK
trick_taint($val);
my $tables_locked_for_derive_groups = shift;
my $dbh = Bugzilla->dbh;
my ($id,
$login,
$name,
$mybugslink) = $dbh->selectrow_array(qq{SELECT userid,
login_name,
realname,
mybugslink
FROM profiles
WHERE $cond},
undef,
$val);
return undef unless defined $id;
my $self = { id => $id,
name => $name,
login => $login,
showmybugslink => $mybugslink,
};
bless ($self, $class);
# Now update any old group information if needed
my $result = $dbh->selectrow_array(q{SELECT 1
FROM profiles, groups
WHERE userid=?
AND profiles.refreshed_when <=
groups.last_changed},
undef,
$id);
if ($result) {
my $is_main_db;
unless ($is_main_db = Bugzilla->dbwritesallowed()) {
Bugzilla->switch_to_main_db();
}
$self->derive_groups($tables_locked_for_derive_groups);
unless ($is_main_db) {
Bugzilla->switch_to_shadow_db();
}
}
return $self;
}
# Accessors for user attributes
sub id { $_[0]->{id}; }
sub login { $_[0]->{login}; }
sub email { $_[0]->{login} . Param('emailsuffix'); }
sub name { $_[0]->{name}; }
sub showmybugslink { $_[0]->{showmybugslink}; }
# Generate a string to identify the user by name + login if the user
# has a name or by login only if she doesn't.
sub identity {
my $self = shift;
if (!defined $self->{identity}) {
$self->{identity} =
$self->{name} ? "$self->{name} <$self->{login}>" : $self->{login};
}
return $self->{identity};
}
sub nick {
my $self = shift;
if (!defined $self->{nick}) {
$self->{nick} = (split(/@/, $self->{login}, 2))[0];
}
return $self->{nick};
}
sub queries {
my $self = shift;
return $self->{queries} if defined $self->{queries};
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare(q{ SELECT name, query, linkinfooter
FROM namedqueries
WHERE userid=?
ORDER BY UPPER(name)});
$sth->execute($self->{id});
my @queries;
while (my $row = $sth->fetch) {
push (@queries, {
name => $row->[0],
query => $row->[1],
linkinfooter => $row->[2],
});
}
$self->{queries} = \@queries;
return $self->{queries};
}
sub flush_queries_cache {
my $self = shift;
delete $self->{queries};
}
sub groups {
my $self = shift;
return $self->{groups} if defined $self->{groups};
my $dbh = Bugzilla->dbh;
my $groups = $dbh->selectcol_arrayref(q{SELECT DISTINCT groups.name, group_id
FROM groups, user_group_map
WHERE groups.id=user_group_map.group_id
AND user_id=?
AND isbless=0},
{ Columns=>[1,2] },
$self->{id});
# The above gives us an arrayref [name, id, name, id, ...]
# Convert that into a hashref
my %groups = @$groups;
$self->{groups} = \%groups;
return $self->{groups};
}
sub in_group {
my ($self, $group) = @_;
# If we already have the info, just return it.
return defined($self->{groups}->{$group}) if defined $self->{groups};
# Otherwise, go check for it
my $dbh = Bugzilla->dbh;
my ($res) = $dbh->selectrow_array(q{SELECT 1
FROM groups, user_group_map
WHERE groups.id=user_group_map.group_id
AND user_group_map.user_id=?
AND isbless=0
AND groups.name=?},
undef,
$self->id,
$group);
return defined($res);
}
sub derive_groups {
my ($self, $already_locked) = @_;
my $id = $self->id;
my $dbh = Bugzilla->dbh;
my $sth;
$dbh->do(q{LOCK TABLES profiles WRITE,
user_group_map WRITE,
group_group_map READ,
groups READ}) unless $already_locked;
# avoid races, we are only up to date as of the BEGINNING of this process
my $time = $dbh->selectrow_array("SELECT NOW()");
# first remove any old derived stuff for this user
$dbh->do(q{DELETE FROM user_group_map
WHERE user_id = ?
AND grant_type != ?},
undef,
$id,
GRANT_DIRECT);
my %groupidsadded = ();
# add derived records for any matching regexps
$sth = $dbh->prepare("SELECT id, userregexp FROM groups WHERE userregexp != ''");
$sth->execute;
my $group_insert;
while (my $row = $sth->fetch) {
if ($self->{login} =~ m/$row->[1]/i) {
$group_insert ||= $dbh->prepare(q{INSERT INTO user_group_map
(user_id, group_id, isbless, grant_type)
VALUES (?, ?, 0, ?)});
$groupidsadded{$row->[0]} = 1;
$group_insert->execute($id, $row->[0], GRANT_REGEXP);
}
}
# Get a list of the groups of which the user is a member.
my %groupidschecked = ();
my @groupidstocheck = @{$dbh->selectcol_arrayref(q{SELECT group_id
FROM user_group_map
WHERE user_id=?},
undef,
$id)};
# Each group needs to be checked for inherited memberships once.
my $group_sth;
while (@groupidstocheck) {
my $group = shift @groupidstocheck;
if (!defined($groupidschecked{"$group"})) {
$groupidschecked{"$group"} = 1;
$group_sth ||= $dbh->prepare(q{SELECT grantor_id
FROM group_group_map
WHERE member_id=?
AND isbless=0});
$group_sth->execute($group);
while (my $groupid = $group_sth->fetchrow_array) {
if (!defined($groupidschecked{"$groupid"})) {
push(@groupidstocheck,$groupid);
}
if (!$groupidsadded{$groupid}) {
$groupidsadded{$groupid} = 1;
$group_insert ||= $dbh->prepare(q{INSERT INTO user_group_map
(user_id, group_id, isbless, grant_type)
VALUES (?, ?, 0, ?)});
$group_insert->execute($id, $groupid, GRANT_DERIVED);
}
}
}
}
$dbh->do(q{UPDATE profiles
SET refreshed_when = ?
WHERE userid=?},
undef,
$time,
$id);
$dbh->do("UNLOCK TABLES") unless $already_locked;
}
sub can_bless {
my $self = shift;
return $self->{can_bless} if defined $self->{can_bless};
my $dbh = Bugzilla->dbh;
# First check if the user can explicitly bless a group
my $res = $dbh->selectrow_arrayref(q{SELECT 1
FROM user_group_map
WHERE user_id=?
AND isbless=1},
undef,
$self->{id});
if (!$res) {
# Now check if user is a member of a group that can bless a group
$res = $dbh->selectrow_arrayref(q{SELECT 1
FROM user_group_map, group_group_map
WHERE user_group_map.user_id=?
AND user_group_map.group_id=member_id
AND group_group_map.isbless=1},
undef,
$self->{id});
}
$self->{can_bless} = $res ? 1 : 0;
return $self->{can_bless};
}
sub match {
# Generates a list of users whose login name (email address) or real name
# matches a substring or wildcard.
# This is also called if matches are disabled (for error checking), but
# in this case only the exact match code will end up running.
# $str contains the string to match, while $limit contains the
# maximum number of records to retrieve.
my ($str, $limit, $exclude_disabled) = @_;
my @users = ();
return \@users if $str =~ /^\s*$/;
# The search order is wildcards, then exact match, then substring search.
# Wildcard matching is skipped if there is no '*', and exact matches will
# not (?) have a '*' in them. If any search comes up with something, the
# ones following it will not execute.
# first try wildcards
my $wildstr = $str;
if ($wildstr =~ s/\*/\%/g && # don't do wildcards if no '*' in the string
Param('usermatchmode') ne 'off') { # or if we only want exact matches
# Build the query.
my $sqlstr = &::SqlQuote($wildstr);
my $query = "SELECT userid, realname, login_name " .
"FROM profiles " .
"WHERE (login_name LIKE $sqlstr " .
"OR realname LIKE $sqlstr) ";
$query .= "AND disabledtext = '' " if $exclude_disabled;
$query .= "ORDER BY length(login_name) ";
$query .= "LIMIT $limit " if $limit;
# Execute the query, retrieve the results, and make them into
# User objects.
&::PushGlobalSQLState();
&::SendSQL($query);
push(@users, new Bugzilla::User(&::FetchSQLData())) while &::MoreSQLData();
&::PopGlobalSQLState();
}
else { # try an exact match
my $sqlstr = &::SqlQuote($str);
my $query = "SELECT userid, realname, login_name " .
"FROM profiles " .
"WHERE login_name = $sqlstr ";
# Exact matches don't care if a user is disabled.
&::PushGlobalSQLState();
&::SendSQL($query);
push(@users, new Bugzilla::User(&::FetchSQLData())) if &::MoreSQLData();
&::PopGlobalSQLState();
}
# then try substring search
if ((scalar(@users) == 0)
&& (&::Param('usermatchmode') eq 'search')
&& (length($str) >= 3))
{
my $sqlstr = &::SqlQuote(uc($str));
my $query = "SELECT userid, realname, login_name " .
"FROM profiles " .
"WHERE (INSTR(UPPER(login_name), $sqlstr) " .
"OR INSTR(UPPER(realname), $sqlstr)) ";
$query .= "AND disabledtext = '' " if $exclude_disabled;
$query .= "ORDER BY length(login_name) ";
$query .= "LIMIT $limit " if $limit;
&::PushGlobalSQLState();
&::SendSQL($query);
push(@users, new Bugzilla::User(&::FetchSQLData())) while &::MoreSQLData();
&::PopGlobalSQLState();
}
# order @users by alpha
@users = sort { uc($a->login) cmp uc($b->login) } @users;
return \@users;
}
# match_field() is a CGI wrapper for the match() function.
#
# Here's what it does:
#
# 1. Accepts a list of fields along with whether they may take multiple values
# 2. Takes the values of those fields from $::FORM and passes them to match()
# 3. Checks the results of the match and displays confirmation or failure
# messages as appropriate.
#
# The confirmation screen functions the same way as verify-new-product and
# confirm-duplicate, by rolling all of the state information into a
# form which is passed back, but in this case the searched fields are
# replaced with the search results.
#
# The act of displaying the confirmation or failure messages means it must
# throw a template and terminate. When confirmation is sent, all of the
# searchable fields have been replaced by exact fields and the calling script
# is executed as normal.
#
# match_field must be called early in a script, before anything external is
# done with the form data.
#
# In order to do a simple match without dealing with templates, confirmation,
# or globals, simply calling Bugzilla::User::match instead will be
# sufficient.
# How to call it:
#
# Bugzilla::User::match_field ({
# 'field_name' => { 'type' => fieldtype },
# 'field_name2' => { 'type' => fieldtype },
# [...]
# });
#
# fieldtype can be either 'single' or 'multi'.
#
sub match_field {
my $fields = shift; # arguments as a hash
my $matches = {}; # the values sent to the template
my $matchsuccess = 1; # did the match fail?
my $need_confirm = 0; # whether to display confirmation screen
# prepare default form values
my $vars = $::vars;
$vars->{'form'} = \%::FORM;
$vars->{'mform'} = \%::MFORM;
# What does a "--do_not_change--" field look like (if any)?
my $dontchange = $vars->{'form'}->{'dontchange'};
# Fields can be regular expressions matching multiple form fields
# (f.e. "requestee-(\d+)"), so expand each non-literal field
# into the list of form fields it matches.
my $expanded_fields = {};
foreach my $field_pattern (keys %{$fields}) {
# Check if the field has any non-word characters. Only those fields
# can be regular expressions, so don't expand the field if it doesn't
# have any of those characters.
if ($field_pattern =~ /^\w+$/) {
$expanded_fields->{$field_pattern} = $fields->{$field_pattern};
}
else {
my @field_names = grep(/$field_pattern/, keys %{$vars->{'form'}});
foreach my $field_name (@field_names) {
$expanded_fields->{$field_name} =
{ type => $fields->{$field_pattern}->{'type'} };
# The field is a requestee field; in order for its name
# to show up correctly on the confirmation page, we need
# to find out the name of its flag type.
if ($field_name =~ /^requestee-(\d+)$/) {
my $flag = Bugzilla::Flag::get($1);
$expanded_fields->{$field_name}->{'flag_type'} =
$flag->{'type'};
}
elsif ($field_name =~ /^requestee_type-(\d+)$/) {
$expanded_fields->{$field_name}->{'flag_type'} =
Bugzilla::FlagType::get($1);
}
}
}
}
$fields = $expanded_fields;
for my $field (keys %{$fields}) {
# Tolerate fields that do not exist.
#
# This is so that fields like qa_contact can be specified in the code
# and it won't break if $::MFORM does not define them.
#
# It has the side-effect that if a bad field name is passed it will be
# quietly ignored rather than raising a code error.
next if !defined($vars->{'mform'}->{$field});
# Skip it if this is a --do_not_change-- field
next if $dontchange && $dontchange eq $vars->{'form'}->{$field};
# We need to move the query to $raw_field, where it will be split up,
# modified by the search, and put back into $::FORM and $::MFORM
# incrementally.
my $raw_field = join(" ", @{$vars->{'mform'}->{$field}});
$vars->{'form'}->{$field} = '';
$vars->{'mform'}->{$field} = [];
my @queries = ();
# Now we either split $raw_field by spaces/commas and put the list
# into @queries, or in the case of fields which only accept single
# entries, we simply use the verbatim text.
$raw_field =~ s/^\s+|\s+$//sg; # trim leading/trailing space
# single field
if ($fields->{$field}->{'type'} eq 'single') {
@queries = ($raw_field) unless $raw_field =~ /^\s*$/;
# multi-field
}
elsif ($fields->{$field}->{'type'} eq 'multi') {
@queries = split(/[\s,]+/, $raw_field);
}
else {
# bad argument
ThrowCodeError('bad_arg',
{ argument => $fields->{$field}->{'type'},
function => 'Bugzilla::User::match_field',
});
}
my $limit = 0;
if (&::Param('maxusermatches')) {
$limit = &::Param('maxusermatches') + 1;
}
for my $query (@queries) {
my $users = match(
$query, # match string
$limit, # match limit
1 # exclude_disabled
);
# skip confirmation for exact matches
if ((scalar(@{$users}) == 1)
&& (@{$users}[0]->{'login'} eq $query))
{
# delimit with spaces if necessary
if ($vars->{'form'}->{$field}) {
$vars->{'form'}->{$field} .= " ";
}
$vars->{'form'}->{$field} .= @{$users}[0]->{'login'};
push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'login'};
next;
}
$matches->{$field}->{$query}->{'users'} = $users;
$matches->{$field}->{$query}->{'status'} = 'success';
# here is where it checks for multiple matches
if (scalar(@{$users}) == 1) { # exactly one match
# delimit with spaces if necessary
if ($vars->{'form'}->{$field}) {
$vars->{'form'}->{$field} .= " ";
}
$vars->{'form'}->{$field} .= @{$users}[0]->{'login'};
push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'login'};
$need_confirm = 1 if &::Param('confirmuniqueusermatch');
}
elsif ((scalar(@{$users}) > 1)
&& (&::Param('maxusermatches') != 1)) {
$need_confirm = 1;
if ((&::Param('maxusermatches'))
&& (scalar(@{$users}) > &::Param('maxusermatches')))
{
$matches->{$field}->{$query}->{'status'} = 'trunc';
pop @{$users}; # take the last one out
}
}
else {
# everything else fails
$matchsuccess = 0; # fail
$matches->{$field}->{$query}->{'status'} = 'fail';
$need_confirm = 1; # confirmation screen shows failures
}
}
}
return 1 unless $need_confirm; # skip confirmation if not needed.
$vars->{'script'} = $ENV{'SCRIPT_NAME'}; # for self-referencing URLs
$vars->{'fields'} = $fields; # fields being matched
$vars->{'matches'} = $matches; # matches that were made
$vars->{'matchsuccess'} = $matchsuccess; # continue or fail
print Bugzilla->cgi->header();
$::template->process("global/confirm-user-match.html.tmpl", $vars)
|| ThrowTemplateError($::template->error());
exit;
}
sub email_prefs {
# Get or set (not implemented) the user's email notification preferences.
my $self = shift;
# If the calling code is setting the email preferences, update the object
# but don't do anything else. This needs to write email preferences back
# to the database.
if (@_) { $self->{email_prefs} = shift; return; }
# If we already got them from the database, return the existing values.
return $self->{email_prefs} if $self->{email_prefs};
# Retrieve the values from the database.
&::SendSQL("SELECT emailflags FROM profiles WHERE userid = $self->{id}");
my ($flags) = &::FetchSQLData();
my @roles = qw(Owner Reporter QAcontact CClist Voter);
my @reasons = qw(Removeme Comments Attachments Status Resolved Keywords
CC Other Unconfirmed);
# Convert the prefs from the flags string from the database into
# a Perl record. The 255 param is here because split will trim
# any trailing null fields without a third param, which causes Perl
# to eject lots of warnings. Any suitably large number would do.
my $prefs = { split(/~/, $flags, 255) };
# Determine the value of the "excludeself" global email preference.
# Note that the value of "excludeself" is assumed to be off if the
# preference does not exist in the user's list, unlike other
# preferences whose value is assumed to be on if they do not exist.
$prefs->{ExcludeSelf} =
exists($prefs->{ExcludeSelf}) && $prefs->{ExcludeSelf} eq "on";
# Determine the value of the global request preferences.
foreach my $pref (qw(FlagRequestee FlagRequester)) {
$prefs->{$pref} = !exists($prefs->{$pref}) || $prefs->{$pref} eq "on";
}
# Determine the value of the rest of the preferences by looping over
# all roles and reasons and converting their values to Perl booleans.
foreach my $role (@roles) {
foreach my $reason (@reasons) {
my $key = "email$role$reason";
$prefs->{$key} = !exists($prefs->{$key}) || $prefs->{$key} eq "on";
}
}
$self->{email_prefs} = $prefs;
return $self->{email_prefs};
}
1;
__END__
=head1 NAME
Bugzilla::User - Object for a Bugzilla user
=head1 SYNOPSIS
use Bugzilla::User;
my $user = new Bugzilla::User($id);
=head1 DESCRIPTION
This package handles Bugzilla users. Data obtained from here is read-only;
there is currently no way to modify a user from this package.
Note that the currently logged in user (if any) is available via
L<Bugzilla-E<gt>user|Bugzilla/"user">.
=head1 METHODS
=over 4
=item C<new($userid)>
Creates a new C<Bugzilla::User> object for the given user id. Returns
C<undef> if no matching user is found.
=begin undocumented
=item C<new_from_login($login)>
Creates a new C<Bugzilla::User> object given the provided login. Returns
C<undef> if no matching user is found.
This routine should not be required in general; most scripts should be using
userids instead.
This routine and C<new> both take an extra optional argument, which is
passed as the argument to C<derive_groups> to avoid locking. See that
routine's documentation for details.
=end undocumented
=item C<id>
Returns the userid for this user.
=item C<login>
Returns the login name for this user.
=item C<email>
Returns the user's email address. Currently this is the same value as the
login.
=item C<name>
Returns the 'real' name for this user, if any.
=item C<showmybugslink>
Returns C<1> if the user has set his preference to show the 'My Bugs' link in
the page footer, and C<0> otherwise.
=item C<identity>
Retruns a string for the identity of the user. This will be of the form
C<name E<lt>emailE<gt>> if the user has specified a name, and C<email>
otherwise.
=item C<nick>
Returns a user "nickname" -- i.e. a shorter, not-necessarily-unique name by
which to identify the user. Currently the part of the user's email address
before the at sign (@), but that could change, especially if we implement
usernames not dependent on email address.
=item C<queries>
Returns an array of the user's named queries, sorted in a case-insensitive
order by name. Each entry is a hash with three keys:
=over
=item *
name - The name of the query
=item *
query - The text for the query
=item *
linkinfooter - Whether or not the query should be displayed in the footer.
=back
=item C<flush_queries_cache>
Some code modifies the set of stored queries. Because C<Bugzilla::User> does
not handle these modifications, but does cache the result of calling C<queries>
internally, such code must call this method to flush the cached result.
=item C<groups>
Returns a hashref of group names for groups the user is a member of. The keys
are the names of the groups, whilst the values are the respective group ids.
(This is so that a set of all groupids for groups the user is in can be
obtained by C<values(%{$user->groups})>.)
=item C<in_group>
Determines whether or not a user is in the given group. This method is mainly
intended for cases where we are not looking at the currently logged in user,
and only need to make a quick check for the group, where calling C<groups>
and getting all of the groups would be overkill.
=item C<derive_groups>
Bugzilla allows for group inheritance. When data about the user (or any of the
groups) changes, the database must be updated. Handling updated groups is taken
care of by the constructor. However, when updating the email address, the
user may be placed into different groups, based on a new email regexp. This
method should be called in such a case to force reresolution of these groups.
=begin undocumented
This routine takes an optional argument. If true, then this routine will not
lock the tables, but will rely on the caller to ahve done so itsself.
This is required because mysql will only execute a query if all of the tables
are locked, or if none of them are, not a mixture. If the caller has already
done some locking, then this routine would fail. Thus the caller needs to lock
all the tables required by this method, and then C<derive_groups> won't do
any locking.
This is a really ugly solution, and when Bugzilla supports transactions
instead of using the explicit table locking we were forced to do when thats
all MySQL supported, this will go away.
=end undocumented
=item C<can_bless>
Returns C<1> if the user can bless at least one group. Otherwise returns C<0>.
=back
=head1 SEE ALSO
L<Bugzilla|Bugzilla>

View File

@@ -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.
#
# 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>
package Bugzilla::Util;
use strict;
use base qw(Exporter);
@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
html_quote url_quote value_quote xml_quote
css_class_quote
lsearch max min
trim format_time);
use Bugzilla::Config;
# This is from the perlsec page, slightly modifed 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];
$_[0] =~ /^(.*)$/s;
$_[0] = $1;
return (defined($_[0]));
}
sub detaint_natural {
$_[0] =~ /^(\d+)$/;
$_[0] = $1;
return (defined($_[0]));
}
sub html_quote {
my ($var) = (@_);
$var =~ s/\&/\&amp;/g;
$var =~ s/</\&lt;/g;
$var =~ s/>/\&gt;/g;
$var =~ s/\"/\&quot;/g;
return $var;
}
# This orignally came from CGI.pm, by Lincoln D. Stein
sub url_quote {
my ($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 value_quote {
my ($var) = (@_);
$var =~ s/\&/\&amp;/g;
$var =~ s/</\&lt;/g;
$var =~ s/>/\&gt;/g;
$var =~ s/\"/\&quot;/g;
# See bug http://bugzilla.mozilla.org/show_bug.cgi?id=4928 for
# explanaion of why bugzilla does this linebreak substitution.
# This caused form submission problems in mozilla (bug 22983, 32000).
$var =~ s/\r\n/\&#013;/g;
$var =~ s/\n\r/\&#013;/g;
$var =~ s/\r/\&#013;/g;
$var =~ s/\n/\&#013;/g;
return $var;
}
sub xml_quote {
my ($var) = (@_);
$var =~ s/\&/\&amp;/g;
$var =~ s/</\&lt;/g;
$var =~ s/>/\&gt;/g;
$var =~ s/\"/\&quot;/g;
$var =~ s/\'/\&apos;/g;
return $var;
}
sub lsearch {
my ($list,$item) = (@_);
my $count = 0;
foreach my $i (@$list) {
if ($i eq $item) {
return $count;
}
$count++;
}
return -1;
}
sub max {
my $max = shift(@_);
foreach my $val (@_) {
$max = $val if $val > $max;
}
return $max;
}
sub min {
my $min = shift(@_);
foreach my $val (@_) {
$min = $val if $val < $min;
}
return $min;
}
sub trim {
my ($str) = @_;
if ($str) {
$str =~ s/^\s+//g;
$str =~ s/\s+$//g;
}
return $str;
}
sub format_time {
my ($time) = @_;
my ($year, $month, $day, $hour, $min, $sec);
if ($time =~ m/^\d{14}$/) {
# We appear to have a timestamp direct from MySQL
$year = substr($time,0,4);
$month = substr($time,4,2);
$day = substr($time,6,2);
$hour = substr($time,8,2);
$min = substr($time,10,2);
}
elsif ($time =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) {
$year = $1;
$month = $2;
$day = $3;
$hour = $4;
$min = $5;
$sec = $7;
}
else {
warn "Date/Time format ($time) unrecogonzied";
}
if (defined $year) {
$time = "$year-$month-$day $hour:$min";
if (defined $sec) {
$time .= ":$sec";
}
$time .= " " . &::Param('timezone') if &::Param('timezone');
}
return $time;
}
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);
# Functions for quoting
html_quote($var);
url_quote($var);
value_quote($var);
xml_quote($var);
# Functions for searching
$loc = lsearch(\@arr, $val);
$val = max($a, $b, $c);
$val = min($a, $b, $c);
# Functions for trimming variables
$val = trim(" abc ");
# Functions for formatting time
format_time($time);
=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.
=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<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<value_quote($val)>
As well as escaping html like C<html_quote>, this routine converts newlines
into &#013;, suitable for use in html attributes.
=item C<xml_quote($val)>
This is similar to C<html_quote>, except that ' is escaped to &apos;. This
is kept separate from html_quote partly for compatibility with previous code
(for &apos;) and partly for future handling of non-ASCII characters.
=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.
=item C<max($a, $b, ...)>
Returns the maximum from a set of values.
=item C<min($a, $b, ...)>
Returns the minimum from a set of values.
=back
=head2 Trimming
=over 4
=item C<trim($str)>
Removes any leading or trailing whitespace from a string. This routine does not
modify the existing string.
=back
=head2 Formatting Time
=over 4
=item C<format_time($time)>
Takes a time and appends the timezone as defined in editparams.cgi. This routine
will be expanded in the future to adjust for user preferences regarding what
timezone to display times in. In the future, it may also allow for the time to be
shown in different formats.
=back

View File

@@ -1,443 +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>
# Contains some global routines used throughout the CGI scripts of Bugzilla.
use strict;
use lib ".";
# use Carp; # for confess
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 Bugzilla::Util;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::BugMail;
# Shut up misguided -w warnings about "used only once". For some reason,
# "use vars" chokes on me when I try it here.
sub CGI_pl_sillyness {
my $zz;
$zz = $::buffer;
}
use CGI::Carp qw(fatalsToBrowser);
require 'globals.pl';
use vars qw($template $vars);
# If Bugzilla is shut down, do not go any further, just display a message
# to the user about the downtime. (do)editparams.cgi is exempted from
# this message, of course, since it needs to be available in order for
# the administrator to open Bugzilla back up.
if (Param("shutdownhtml") && $0 !~ m:(^|[\\/])(do)?editparams\.cgi$:) {
$::vars->{'message'} = "shutdown";
# Return the appropriate HTTP response headers.
print Bugzilla->cgi->header();
# Generate and return an HTML message about the downtime.
$::template->process("global/message.html.tmpl", $::vars)
|| ThrowTemplateError($::template->error());
exit;
}
# Implementations of several of the below were blatently stolen from CGI.pm,
# by Lincoln D. Stein.
# Get rid of all the %xx encoding and the like from the given URL.
sub url_decode {
my ($todecode) = (@_);
$todecode =~ tr/+/ /; # pluses become spaces
$todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
return $todecode;
}
# check and see if a given field exists, is non-empty, and is set to a
# legal value. assume a browser bug and abort appropriately if not.
# if $legalsRef is not passed, just check to make sure the value exists and
# is non-NULL
sub CheckFormField (\%$;\@) {
my ($formRef, # a reference to the form to check (a hash)
$fieldname, # the fieldname to check
$legalsRef # (optional) ref to a list of legal values
) = @_;
if ( !defined $formRef->{$fieldname} ||
trim($formRef->{$fieldname}) eq "" ||
(defined($legalsRef) &&
lsearch($legalsRef, $formRef->{$fieldname})<0) ){
SendSQL("SELECT description FROM fielddefs WHERE name=" . SqlQuote($fieldname));
my $result = FetchOneColumn();
my $field;
if ($result) {
$field = $result;
}
else {
$field = $fieldname;
}
ThrowCodeError("illegal_field", { field => $field }, "abort");
}
}
# check and see if a given field is defined, and abort if not
sub CheckFormFieldDefined (\%$) {
my ($formRef, # a reference to the form to check (a hash)
$fieldname, # the fieldname to check
) = @_;
if (!defined $formRef->{$fieldname}) {
ThrowCodeError("undefined_field", { field => $fieldname });
}
}
sub BugAliasToID {
# Queries the database for the bug with a given alias, and returns
# the ID of the bug if it exists or the undefined value if it doesn't.
my ($alias) = @_;
return undef unless Param("usebugaliases");
PushGlobalSQLState();
SendSQL("SELECT bug_id FROM bugs WHERE alias = " . SqlQuote($alias));
my $id = FetchOneColumn();
PopGlobalSQLState();
return $id;
}
sub ValidateBugID {
# Validates and verifies a bug ID, making sure the number is a
# positive integer, that it represents an existing bug in the
# database, and that the user is authorized to access that bug.
# We detaint the number here, too
my ($id, $field) = @_;
# Get rid of white-space around the ID.
$id = trim($id);
# If the ID isn't a number, it might be an alias, so try to convert it.
my $alias = $id;
if (!detaint_natural($id)) {
$id = BugAliasToID($alias);
$id || ThrowUserError("invalid_bug_id_or_alias",
{'bug_id' => $alias,
'field' => $field });
}
# Modify the calling code's original variable to contain the trimmed,
# converted-from-alias ID.
$_[0] = $id;
# First check that the bug exists
SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $id");
FetchOneColumn()
|| ThrowUserError("invalid_bug_id_non_existent", {'bug_id' => $id});
return if (defined $field && ($field eq "dependson" || $field eq "blocked"));
return if CanSeeBug($id, $::userid);
# The user did not pass any of the authorization tests, which means they
# are not authorized to see the bug. Display an error and stop execution.
# The error the user sees depends on whether or not they are logged in
# (i.e. $::userid contains the user's positive integer ID).
if ($::userid) {
ThrowUserError("bug_access_denied", {'bug_id' => $id});
} else {
ThrowUserError("bug_access_query", {'bug_id' => $id});
}
}
sub ValidateComment {
# Make sure a comment is not too large (greater than 64K).
my ($comment) = @_;
if (defined($comment) && length($comment) > 65535) {
ThrowUserError("comment_too_long");
}
}
sub PasswordForLogin {
my ($login) = (@_);
SendSQL("select cryptpassword from profiles where login_name = " .
SqlQuote($login));
my $result = FetchOneColumn();
if (!defined $result) {
$result = "";
}
return $result;
}
sub CheckEmailSyntax {
my ($addr) = (@_);
my $match = Param('emailregexp');
if ($addr !~ /$match/ || $addr =~ /[\\\(\)<>&,;:"\[\] \t\r\n]/) {
ThrowUserError("illegal_email_address", { addr => $addr });
}
}
sub MailPassword {
my ($login, $password) = (@_);
my $urlbase = Param("urlbase");
my $template = Param("passwordmail");
my $msg = PerformSubsts($template,
{"mailaddress" => $login . Param('emailsuffix'),
"login" => $login,
"password" => $password});
Bugzilla::BugMail::MessageToMTA($msg);
}
sub PutHeader {
($vars->{'title'}, $vars->{'h1'}, $vars->{'h2'}) = (@_);
$::template->process("global/header.html.tmpl", $::vars)
|| ThrowTemplateError($::template->error());
$vars->{'header_done'} = 1;
}
sub PutFooter {
$::template->process("global/footer.html.tmpl", $::vars)
|| ThrowTemplateError($::template->error());
}
sub CheckIfVotedConfirmed {
my ($id, $who) = (@_);
PushGlobalSQLState();
SendSQL("SELECT bugs.votes, bugs.bug_status, products.votestoconfirm, " .
" bugs.everconfirmed " .
"FROM bugs, products " .
"WHERE bugs.bug_id = $id AND products.id = bugs.product_id");
my ($votes, $status, $votestoconfirm, $everconfirmed) = (FetchSQLData());
my $ret = 0;
if ($votes >= $votestoconfirm && $status eq $::unconfirmedstate) {
SendSQL("UPDATE bugs SET bug_status = 'NEW', everconfirmed = 1 " .
"WHERE bug_id = $id");
my $fieldid = GetFieldID("bug_status");
SendSQL("INSERT INTO bugs_activity " .
"(bug_id,who,bug_when,fieldid,removed,added) VALUES " .
"($id,$who,now(),$fieldid,'$::unconfirmedstate','NEW')");
if (!$everconfirmed) {
$fieldid = GetFieldID("everconfirmed");
SendSQL("INSERT INTO bugs_activity " .
"(bug_id,who,bug_when,fieldid,removed,added) VALUES " .
"($id,$who,now(),$fieldid,'0','1')");
}
AppendComment($id, DBID_to_name($who),
"*** This bug has been confirmed by popular vote. ***", 0);
$vars->{'type'} = "votes";
$vars->{'id'} = $id;
$vars->{'mailrecipients'} = { 'changer' => $who };
$template->process("bug/process/results.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
$ret = 1;
}
PopGlobalSQLState();
return $ret;
}
sub LogActivityEntry {
my ($i,$col,$removed,$added,$whoid,$timestamp) = @_;
# in the case of CCs, deps, and keywords, there's a possibility that someone
# might try to add or remove a lot of them at once, which might take more
# space than the activity table allows. We'll solve this by splitting it
# into multiple entries if it's too long.
while ($removed || $added) {
my ($removestr, $addstr) = ($removed, $added);
if (length($removestr) > 254) {
my $commaposition = FindWrapPoint($removed, 254);
$removestr = substr($removed,0,$commaposition);
$removed = substr($removed,$commaposition);
$removed =~ s/^[,\s]+//; # remove any comma or space
} else {
$removed = ""; # no more entries
}
if (length($addstr) > 254) {
my $commaposition = FindWrapPoint($added, 254);
$addstr = substr($added,0,$commaposition);
$added = substr($added,$commaposition);
$added =~ s/^[,\s]+//; # remove any comma or space
} else {
$added = ""; # no more entries
}
$addstr = SqlQuote($addstr);
$removestr = SqlQuote($removestr);
my $fieldid = GetFieldID($col);
SendSQL("INSERT INTO bugs_activity " .
"(bug_id,who,bug_when,fieldid,removed,added) VALUES " .
"($i,$whoid," . SqlQuote($timestamp) . ",$fieldid,$removestr,$addstr)");
}
}
sub GetBugActivity {
my ($id, $starttime) = (@_);
my $datepart = "";
die "Invalid id: $id" unless $id=~/^\s*\d+\s*$/;
if (defined $starttime) {
$datepart = "and bugs_activity.bug_when > " . SqlQuote($starttime);
}
my $suppjoins = "";
my $suppwhere = "";
if (Param("insidergroup") && !UserInGroup(Param('insidergroup'))) {
$suppjoins = "LEFT JOIN attachments
ON attachments.attach_id = bugs_activity.attach_id";
$suppwhere = "AND NOT(COALESCE(attachments.isprivate,0))";
}
my $query = "
SELECT IFNULL(fielddefs.description, bugs_activity.fieldid),
fielddefs.name,
bugs_activity.attach_id,
DATE_FORMAT(bugs_activity.bug_when,'%Y.%m.%d %H:%i:%s'),
bugs_activity.removed, bugs_activity.added,
profiles.login_name
FROM bugs_activity $suppjoins LEFT JOIN fielddefs ON
bugs_activity.fieldid = fielddefs.fieldid,
profiles
WHERE bugs_activity.bug_id = $id $datepart
AND profiles.userid = bugs_activity.who $suppwhere
ORDER BY bugs_activity.bug_when";
SendSQL($query);
my @operations;
my $operation = {};
my $changes = [];
my $incomplete_data = 0;
while (my ($field, $fieldname, $attachid, $when, $removed, $added, $who)
= FetchSQLData())
{
my %change;
my $activity_visible = 1;
# check if the user should see this field's activity
if ($fieldname eq 'remaining_time' ||
$fieldname eq 'estimated_time' ||
$fieldname eq 'work_time') {
if (!UserInGroup(Param('timetrackinggroup'))) {
$activity_visible = 0;
} else {
$activity_visible = 1;
}
} else {
$activity_visible = 1;
}
if ($activity_visible) {
# This gets replaced with a hyperlink in the template.
$field =~ s/^Attachment// if $attachid;
# Check for the results of an old Bugzilla data corruption bug
$incomplete_data = 1 if ($added =~ /^\?/ || $removed =~ /^\?/);
# An operation, done by 'who' at time 'when', has a number of
# 'changes' associated with it.
# If this is the start of a new operation, store the data from the
# previous one, and set up the new one.
if ($operation->{'who'}
&& ($who ne $operation->{'who'}
|| $when ne $operation->{'when'}))
{
$operation->{'changes'} = $changes;
push (@operations, $operation);
# Create new empty anonymous data structures.
$operation = {};
$changes = [];
}
$operation->{'who'} = $who;
$operation->{'when'} = $when;
$change{'field'} = $field;
$change{'fieldname'} = $fieldname;
$change{'attachid'} = $attachid;
$change{'removed'} = $removed;
$change{'added'} = $added;
push (@$changes, \%change);
}
}
if ($operation->{'who'}) {
$operation->{'changes'} = $changes;
push (@operations, $operation);
}
return(\@operations, $incomplete_data);
}
############# Live code below here (that is, not subroutine defs) #############
use Bugzilla;
# XXX - mod_perl - reset this between runs
$::cgi = Bugzilla->cgi;
# Set up stuff for compatibility with the old CGI.pl code
# This code will be removed as soon as possible, in favour of
# using the CGI.pm stuff directly
# XXX - mod_perl - reset these between runs
foreach my $name ($::cgi->param()) {
my @val = $::cgi->param($name);
$::FORM{$name} = join('', @val);
$::MFORM{$name} = \@val;
}
$::buffer = $::cgi->query_string();
foreach my $name ($::cgi->cookie()) {
$::COOKIE{$name} = $::cgi->cookie($name);
}
# This could be needed in any CGI, so we set it here.
$vars->{'help'} = $::cgi->param('help') ? 1 : 0;
1;

View File

@@ -1,90 +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 and
Sendmail 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 from.
2. Unpack distribution into the chosen directory (there is no copying or
installation involved).
3. Run ./checksetup.pl, look for unsolved requirements, install them.
You can run checksetup as many times as necessary to check if
everything required is installed.
This will usually include assorted Perl modules, MySQL and sendmail.
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.
If you want to change platforms, operating systems, severities and
priorities, this can also be done in localconfig at this time.
You should also update localconfig.js to reflect these changes. This
includes setting the URL you chose in step 1 as the 'bugzilla' JS
variable.
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 section 4.1.6 in the Bugzilla Guide.
6. Run checksetup.pl once more; if all goes well, it should set up the
Bugzilla database for you. If not, move back 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/discussion.html

View File

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

View File

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

View File

@@ -1,407 +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.
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,53 +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, product, component, version, rep_platform, op_sys, bug_status, resolution?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, votes?, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time)?, groups*, 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 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 bug_file_loc (#PCDATA)>
<!ELEMENT short_desc (#PCDATA)>
<!ELEMENT keywords (#PCDATA)>
<!ELEMENT dependson (#PCDATA)>
<!ELEMENT blocked (#PCDATA)>
<!ELEMENT cc (#PCDATA)>
<!ELEMENT group (#PCDATA)>
<!ELEMENT estimated_time (#PCDATA)>
<!ELEMENT remaining_time (#PCDATA)>
<!ELEMENT actual_time (#PCDATA)>
<!ELEMENT long_desc (who, bug_when, thetext)>
<!ELEMENT who (#PCDATA)>
<!ELEMENT bug_when (#PCDATA)>
<!ELEMENT thetext (#PCDATA)>
<!ELEMENT attachment (attachid, date, desc, type?, data?)>
<!ELEMENT attachid (#PCDATA)>
<!ELEMENT date (#PCDATA)>
<!ELEMENT desc (#PCDATA)>
<!ELEMENT type (#PCDATA)>
<!ELEMENT data (#PCDATA)>

View File

@@ -1,315 +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>
# 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 UserInGroup('editbugs') not scattered
# everywhere.
# User documentation :-)
#
# Bonus:
# Offer subscription when you get a "series already exists" error?
use strict;
use lib qw(.);
require "CGI.pl";
use Bugzilla::Constants;
use Bugzilla::Chart;
use Bugzilla::Series;
use vars qw($cgi $template $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 $template = Bugzilla->template;
my $action = $cgi->param('action');
my $series_id = $cgi->param('series_id');
# Because some actions are chosen by buttons, we can't encode them as the value
# of the action param, because that value is localisation-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;
}
Bugzilla->login(LOGIN_REQUIRED);
UserInGroup(Param("chartgroup"))
|| ThrowUserError("authorization_failure",
{action => "use this feature"});
# Only admins may create public queries
UserInGroup('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($::userid);
}
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) = @_;
return if UserInGroup("admin");
my $dbh = Bugzilla->dbh;
my $iscreator = $dbh->selectrow_array("SELECT creator = ? FROM series " .
"WHERE series_id = ?", undef,
$::userid, $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;
UserInGroup("editbugs") || ThrowUserError("illegal_series_creation");
# Check permission for frequency
my $min_freq = 7;
if ($cgi->param('frequency') < $min_freq && !UserInGroup("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 = &::GetFormat("reports/chart",
"",
$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",
"Bugzilla_login", "Bugzilla_password");
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());
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,142 +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>
# Gervase Markham <gerv@gerv.net>
use strict;
use lib qw(.);
use vars qw(
@legal_keywords
$buffer
$template
$vars
);
use Bugzilla;
require "CGI.pl";
Bugzilla->login();
GetVersionTable();
my $cgi = Bugzilla->cgi;
# The master list not only says what fields are possible, but what order
# they get displayed in.
my @masterlist = ("opendate", "changeddate", "bug_severity", "priority",
"rep_platform", "assigned_to", "assigned_to_realname",
"reporter", "reporter_realname", "bug_status",
"resolution", "product", "component", "version",
"op_sys");
if (Param("usebugaliases")) {
unshift(@masterlist, "alias");
}
if (Param("usevotes")) {
push (@masterlist, "votes");
}
if (Param("usetargetmilestone")) {
push(@masterlist, "target_milestone");
}
if (Param("useqacontact")) {
push(@masterlist, "qa_contact");
push(@masterlist, "qa_contact_realname");
}
if (Param("usestatuswhiteboard")) {
push(@masterlist, "status_whiteboard");
}
if (@::legal_keywords) {
push(@masterlist, "keywords");
}
if (UserInGroup(Param("timetrackinggroup"))) {
push(@masterlist, ("estimated_time", "remaining_time", "actual_time",
"percentage_complete"));
}
push(@masterlist, ("short_desc", "short_short_desc"));
$vars->{'masterlist'} = \@masterlist;
my @collist;
if (defined $cgi->param('rememberedquery')) {
my $splitheader = 0;
if (defined $cgi->param('resetit')) {
@collist = @::default_column_list;
} else {
foreach my $i (@masterlist) {
if (defined $cgi->param("column_$i")) {
push @collist, $i;
}
}
if (defined $cgi->param('splitheader')) {
$splitheader = $cgi->param('splitheader');
}
}
my $list = join(" ", @collist);
my $urlbase = Param("urlbase");
$cgi->send_cookie(-name => 'COLUMNLIST',
-value => $list,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
$cgi->send_cookie(-name => 'SPLITHEADER',
-value => $cgi->param('splitheader'),
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
$vars->{'message'} = "change_columns";
$vars->{'redirect_url'} = "buglist.cgi?".$cgi->param('rememberedquery');
# If we're running on Microsoft IIS, using cgi->redirect discards
# the Set-Cookie lines -- workaround is to use the old-fashioned
# redirection mechanism. See bug 214466 for details.
if ($ENV{'SERVER_SOFTWARE'} =~ /Microsoft-IIS/
|| $ENV{'SERVER_SOFTWARE'} =~ /Sun ONE Web/)
{
print $cgi->header(-type => "text/html",
-refresh => "0; URL=$vars->{'redirect_url'}");
}
else {
print $cgi->redirect($vars->{'redirect_url'});
}
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
if (defined $cgi->cookie('COLUMNLIST')) {
@collist = split(/ /, $cgi->cookie('COLUMNLIST'));
} else {
@collist = @::default_column_list;
}
$vars->{'collist'} = \@collist;
$vars->{'splitheader'} = $cgi->cookie('SPLITHEADER') ? 1 : 0;
$vars->{'buffer'} = $::buffer;
# Generate and return the UI (HTML page) from the appropriate template.
print $cgi->header();
$template->process("list/change-columns.html.tmpl", $vars)
|| ThrowTemplateError($template->error());

View File

@@ -1,508 +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 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>,
# Harrison Page <harrison@netscape.com>
# Gervase Markham <gerv@gerv.net>
# Richard Walters <rwalters@qualcomm.com>
# Jean-Sebastien Guay <jean_seb@hybride.com>
# Run me out of cron at midnight to collect Bugzilla statistics.
#
# To run new charts for a specific date, pass it in on the command line in
# ISO (2004-08-14) format.
use AnyDBM_File;
use strict;
use IO::Handle;
use vars @::legal_product;
use lib ".";
require "globals.pl";
use Bugzilla::Search;
use Bugzilla::User;
use Bugzilla;
use Bugzilla::Config qw(:DEFAULT $datadir);
# Turn off output buffering (probably needed when displaying output feedback
# in the regenerate mode.)
$| = 1;
# Tidy up after graphing module
if (chdir("graphs")) {
unlink <./*.gif>;
unlink <./*.png>;
chdir("..");
}
GetVersionTable();
Bugzilla->switch_to_shadow_db();
# To recreate the daily statistics, run "collectstats.pl --regenerate" .
my $regenerate = 0;
if ($#ARGV >= 0 && $ARGV[0] eq "--regenerate") {
shift(@ARGV);
$regenerate = 1;
}
my @myproducts;
push( @myproducts, "-All-", @::legal_product );
my $tstart = time;
foreach (@myproducts) {
my $dir = "$datadir/mining";
&check_data_dir ($dir);
if ($regenerate) {
&regenerate_stats($dir, $_);
} else {
&collect_stats($dir, $_);
}
}
my $tend = time;
# Uncomment the following line for performance testing.
#print "Total time taken " . delta_time($tstart, $tend) . "\n";
&calculate_dupes();
CollectSeriesData();
{
local $ENV{'GATEWAY_INTERFACE'} = 'cmdline';
local $ENV{'REQUEST_METHOD'} = 'GET';
local $ENV{'QUERY_STRING'} = 'ctype=rdf';
my $perl = $^X;
trick_taint($perl);
# Generate a static RDF file containing the default view of the duplicates data.
open(CGI, "$perl -T duplicates.cgi |")
|| die "can't fork duplicates.cgi: $!";
open(RDF, ">$datadir/duplicates.tmp")
|| die "can't write to $datadir/duplicates.tmp: $!";
my $headers_done = 0;
while (<CGI>) {
print RDF if $headers_done;
$headers_done = 1 if $_ eq "\r\n";
}
close CGI;
close RDF;
}
if (-s "$datadir/duplicates.tmp") {
rename("$datadir/duplicates.rdf", "$datadir/duplicates-old.rdf");
rename("$datadir/duplicates.tmp", "$datadir/duplicates.rdf");
}
sub check_data_dir {
my $dir = shift;
if (! -d $dir) {
mkdir $dir, 0755;
chmod 0755, $dir;
}
}
sub collect_stats {
my $dir = shift;
my $product = shift;
my $when = localtime (time);
my $product_id = get_product_id($product) unless $product eq '-All-';
die "Unknown product $product" unless ($product_id or $product eq '-All-');
# NB: Need to mangle the product for the filename, but use the real
# product name in the query
my $file_product = $product;
$file_product =~ s/\//-/gs;
my $file = join '/', $dir, $file_product;
my $exists = -f $file;
if (open DATA, ">>$file") {
push my @row, &today;
foreach my $status ('NEW', 'ASSIGNED', 'REOPENED', 'UNCONFIRMED', 'RESOLVED', 'VERIFIED', 'CLOSED') {
if( $product eq "-All-" ) {
SendSQL("SELECT COUNT(bug_status) FROM bugs WHERE bug_status='$status'");
} else {
SendSQL("SELECT COUNT(bug_status) FROM bugs WHERE bug_status='$status' AND product_id=$product_id");
}
push @row, FetchOneColumn();
}
foreach my $resolution ('FIXED', 'INVALID', 'WONTFIX', 'LATER', 'REMIND', 'DUPLICATE', 'WORKSFORME', 'MOVED') {
if( $product eq "-All-" ) {
SendSQL("SELECT COUNT(resolution) FROM bugs WHERE resolution='$resolution'");
} else {
SendSQL("SELECT COUNT(resolution) FROM bugs WHERE resolution='$resolution' AND product_id=$product_id");
}
push @row, FetchOneColumn();
}
if (! $exists) {
print DATA <<FIN;
# Bugzilla Daily Bug Stats
#
# Do not edit me! This file is generated.
#
# fields: DATE|NEW|ASSIGNED|REOPENED|UNCONFIRMED|RESOLVED|VERIFIED|CLOSED|FIXED|INVALID|WONTFIX|LATER|REMIND|DUPLICATE|WORKSFORME|MOVED
# Product: $product
# Created: $when
FIN
}
print DATA (join '|', @row) . "\n";
close DATA;
chmod 0644, $file;
} else {
print "$0: $file, $!";
}
}
sub calculate_dupes {
SendSQL("SELECT * FROM duplicates");
my %dupes;
my %count;
my @row;
my $key;
my $changed = 1;
my $today = &today_dash;
# Save % count here in a date-named file
# so we can read it back in to do changed counters
# First, delete it if it exists, so we don't add to the contents of an old file
if (my @files = <$datadir/duplicates/dupes$today*>) {
map { trick_taint($_) } @files;
unlink @files;
}
dbmopen(%count, "$datadir/duplicates/dupes$today", 0644) || die "Can't open DBM dupes file: $!";
# Create a hash with key "a bug number", value "bug which that bug is a
# direct dupe of" - straight from the duplicates table.
while (@row = FetchSQLData()) {
my $dupe_of = shift @row;
my $dupe = shift @row;
$dupes{$dupe} = $dupe_of;
}
# Total up the number of bugs which are dupes of a given bug
# count will then have key = "bug number",
# value = "number of immediate dupes of that bug".
foreach $key (keys(%dupes))
{
my $dupe_of = $dupes{$key};
if (!defined($count{$dupe_of})) {
$count{$dupe_of} = 0;
}
$count{$dupe_of}++;
}
# Now we collapse the dupe tree by iterating over %count until
# there is no further change.
while ($changed == 1)
{
$changed = 0;
foreach $key (keys(%count)) {
# if this bug is actually itself a dupe, and has a count...
if (defined($dupes{$key}) && $count{$key} > 0) {
# add that count onto the bug it is a dupe of,
# and zero the count; the check is to avoid
# loops
if ($count{$dupes{$key}} != 0) {
$count{$dupes{$key}} += $count{$key};
$count{$key} = 0;
$changed = 1;
}
}
}
}
# Remove the values for which the count is zero
foreach $key (keys(%count))
{
if ($count{$key} == 0) {
delete $count{$key};
}
}
dbmclose(%count);
}
# This regenerates all statistics from the database.
sub regenerate_stats {
my $dir = shift;
my $product = shift;
my $when = localtime(time());
my $tstart = time();
# NB: Need to mangle the product for the filename, but use the real
# product name in the query
my $file_product = $product;
$file_product =~ s/\//-/gs;
my $file = join '/', $dir, $file_product;
my @bugs;
my $and_product = "";
my $from_product = "";
if ($product ne '-All-') {
$and_product = "AND bugs.product_id = products.id " .
"AND products.name = " . SqlQuote($product) . " ";
$from_product = ", products";
}
# Determine the start date from the date the first bug in the
# database was created, and the end date from the current day.
# If there were no bugs in the search, return early.
SendSQL("SELECT to_days(creation_ts) AS start, " .
"to_days(current_date) AS end, " .
"to_days('1970-01-01') " .
"FROM bugs $from_product WHERE to_days(creation_ts) != 'NULL' " .
$and_product .
"ORDER BY start LIMIT 1");
my ($start, $end, $base) = FetchSQLData();
if (!defined $start) {
return;
}
if (open DATA, ">$file") {
DATA->autoflush(1);
print DATA <<FIN;
# Bugzilla Daily Bug Stats
#
# Do not edit me! This file is generated.
#
# fields: DATE|NEW|ASSIGNED|REOPENED|UNCONFIRMED|RESOLVED|VERIFIED|CLOSED|FIXED|INVALID|WONTFIX|LATER|REMIND|DUPLICATE|WORKSFORME|MOVED
# Product: $product
# Created: $when
FIN
# For each day, generate a line of statistics.
my $total_days = $end - $start;
for (my $day = $start + 1; $day <= $end; $day++) {
# Some output feedback
my $percent_done = ($day - $start - 1) * 100 / $total_days;
printf "\rRegenerating $product \[\%.1f\%\%]", $percent_done;
# Get a list of bugs that were created the previous day, and
# add those bugs to the list of bugs for this product.
SendSQL("SELECT bug_id FROM bugs $from_product " .
"WHERE bugs.creation_ts < from_days(" . ($day - 1) . ") " .
"AND bugs.creation_ts >= from_days(" . ($day - 2) . ") " .
$and_product .
" ORDER BY bug_id");
my @row;
while (@row = FetchSQLData()) {
push @bugs, $row[0];
}
# For each bug that existed on that day, determine its status
# at the beginning of the day. If there were no status
# changes on or after that day, the status was the same as it
# is today, which can be found in the bugs table. Otherwise,
# the status was equal to the first "previous value" entry in
# the bugs_activity table for that bug made on or after that
# day.
my %bugcount;
my @logstates = qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
VERIFIED CLOSED);
my @logresolutions = qw(FIXED INVALID WONTFIX LATER REMIND
DUPLICATE WORKSFORME MOVED);
foreach (@logstates) {
$bugcount{$_} = 0;
}
foreach (@logresolutions) {
$bugcount{$_} = 0;
}
for my $bug (@bugs) {
# First, get information on various bug states.
SendSQL("SELECT bugs_activity.removed " .
"FROM bugs_activity,fielddefs " .
"WHERE bugs_activity.fieldid = fielddefs.fieldid " .
"AND fielddefs.name = 'bug_status' " .
"AND bugs_activity.bug_id = $bug " .
"AND bugs_activity.bug_when >= from_days($day) " .
"ORDER BY bugs_activity.bug_when LIMIT 1");
my $status;
if (@row = FetchSQLData()) {
$status = $row[0];
} else {
SendSQL("SELECT bug_status FROM bugs WHERE bug_id = $bug");
$status = FetchOneColumn();
}
if (defined $bugcount{$status}) {
$bugcount{$status}++;
}
# Next, get information on various bug resolutions.
SendSQL("SELECT bugs_activity.removed " .
"FROM bugs_activity,fielddefs " .
"WHERE bugs_activity.fieldid = fielddefs.fieldid " .
"AND fielddefs.name = 'resolution' " .
"AND bugs_activity.bug_id = $bug " .
"AND bugs_activity.bug_when >= from_days($day) " .
"ORDER BY bugs_activity.bug_when LIMIT 1");
if (@row = FetchSQLData()) {
$status = $row[0];
} else {
SendSQL("SELECT resolution FROM bugs WHERE bug_id = $bug");
$status = FetchOneColumn();
}
if (defined $bugcount{$status}) {
$bugcount{$status}++;
}
}
# Generate a line of output containing the date and counts
# of bugs in each state.
my $date = sqlday($day, $base);
print DATA "$date";
foreach (@logstates) {
print DATA "|$bugcount{$_}";
}
foreach (@logresolutions) {
print DATA "|$bugcount{$_}";
}
print DATA "\n";
}
# Finish up output feedback for this product.
my $tend = time;
print "\rRegenerating $product \[100.0\%] - " .
delta_time($tstart, $tend) . "\n";
close DATA;
chmod 0640, $file;
}
}
sub today {
my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
}
sub today_dash {
my ($dom, $mon, $year) = (localtime(time))[3, 4, 5];
return sprintf "%04d-%02d-%02d", 1900 + $year, ++$mon, $dom;
}
sub sqlday {
my ($day, $base) = @_;
$day = ($day - $base) * 86400;
my ($dom, $mon, $year) = (gmtime($day))[3, 4, 5];
return sprintf "%04d%02d%02d", 1900 + $year, ++$mon, $dom;
}
sub delta_time {
my $tstart = shift;
my $tend = shift;
my $delta = $tend - $tstart;
my $hours = int($delta/3600);
my $minutes = int($delta/60) - ($hours * 60);
my $seconds = $delta - ($minutes * 60) - ($hours * 3600);
return sprintf("%02d:%02d:%02d" , $hours, $minutes, $seconds);
}
sub CollectSeriesData {
# We need some way of randomising the distribution of series, such that
# all of the series which are to be run every 7 days don't run on the same
# day. This is because this might put the server under severe load if a
# particular frequency, such as once a week, is very common. We achieve
# this by only running queries when:
# (days_since_epoch + series_id) % frequency = 0. So they'll run every
# <frequency> days, but the start date depends on the series_id.
my $days_since_epoch = int(time() / (60 * 60 * 24));
my $today = $ARGV[0] || today_dash();
# We save a copy of the main $dbh and then switch to the shadow and get
# that one too. Remember, these may be the same.
Bugzilla->switch_to_main_db();
my $dbh = Bugzilla->dbh;
Bugzilla->switch_to_shadow_db();
my $shadow_dbh = Bugzilla->dbh;
my $serieses = $dbh->selectall_hashref("SELECT series_id, query, creator " .
"FROM series " .
"WHERE frequency != 0 AND " .
"($days_since_epoch + series_id) % frequency = 0",
"series_id");
# We prepare the insertion into the data table, for efficiency.
my $sth = $dbh->prepare("INSERT INTO series_data " .
"(series_id, series_date, series_value) " .
"VALUES (?, " . $dbh->quote($today) . ", ?)");
# We delete from the table beforehand, to avoid SQL errors if people run
# collectstats.pl twice on the same day.
my $deletesth = $dbh->prepare("DELETE FROM series_data
WHERE series_id = ? AND series_date = " .
$dbh->quote($today));
foreach my $series_id (keys %$serieses) {
# We set up the user for Search.pm's permission checking - each series
# runs with the permissions of its creator.
my $user = new Bugzilla::User($serieses->{$series_id}->{'creator'});
my $cgi = new Bugzilla::CGI($serieses->{$series_id}->{'query'});
my $search = new Bugzilla::Search('params' => $cgi,
'fields' => ["bugs.bug_id"],
'user' => $user);
my $sql = $search->getSQL();
my $data;
# We can't die if we get dodgy SQL back for whatever reason, so we
# eval() this and, if it fails, just ignore it and carry on.
# One day we might even log an error.
eval {
$data = $shadow_dbh->selectall_arrayref($sql);
};
if (!$@) {
# We need to count the returned rows. Without subselects, we can't
# do this directly in the SQL for all queries. So we do it by hand.
my $count = scalar(@$data) || 0;
$deletesth->execute($series_id);
$sth->execute($series_id, $count);
}
}
}

View File

@@ -1,98 +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>
################################################################################
# Script Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use diagnostics;
use strict;
# Include the Bugzilla CGI and general utility library.
use lib qw(.);
require "CGI.pl";
# Retrieve this installation's configuration.
GetVersionTable();
# Suppress "used only once" warnings.
use vars
qw(
@legal_priority
@legal_severity
@legal_platform
@legal_opsys
@legal_resolution
@legal_components
@legal_target_milestone
@legal_versions
@legal_keywords
);
# Use the global template variables defined in globals.pl
# to generate the output.
use vars qw($template $vars);
# Pass a bunch of Bugzilla configuration to the templates.
$vars->{'priority'} = \@::legal_priority;
$vars->{'severity'} = \@::legal_severity;
$vars->{'platform'} = \@::legal_platform;
$vars->{'op_sys'} = \@::legal_opsys;
$vars->{'keyword'} = \@::legal_keywords;
$vars->{'resolution'} = \@::legal_resolution;
$vars->{'status'} = \@::legal_bug_status;
# Include lists of products, components, versions, and target milestones.
my $selectables = GetSelectableProductHash();
foreach my $selectable (keys %$selectables) {
$vars->{$selectable} = $selectables->{$selectable};
}
# Create separate lists of open versus resolved statuses. This should really
# be made part of the configuration.
my @open_status;
my @closed_status;
foreach my $status (@::legal_bug_status) {
IsOpenedState($status) ? push(@open_status, $status)
: push(@closed_status, $status);
}
$vars->{'open_status'} = \@open_status;
$vars->{'closed_status'} = \@closed_status;
# Generate a list of fields that can be queried.
$vars->{'field'} = [GetFieldDefs()];
# Determine how the user would like to receive the output;
# default is JavaScript.
my $cgi = Bugzilla->cgi;
my $format = GetFormat("config", scalar($cgi->param('format')),
scalar($cgi->param('ctype')) || "js");
# Return HTTP headers.
print "Content-Type: $format->{'ctype'}\n\n";
# Generate the configuration file and return it to the user.
$template->process($format->{'template'}, $vars)
|| ThrowTemplateError($template->error());

View File

@@ -1,78 +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.
#
# This code is based on code found in bug_email.pl from the bugzilla
# email tracker. Initial contributors are ::
# Terry Weissman <terry@mozilla.org>
# Gregor Fischer <fischer@suse.de>
# Klaas Freitag <freitag@suse.de>
# Seth Landsman <seth@dworkin.net>
# The purpose of this module is to abstract out a bunch of the code
# that is central to email interfaces to bugzilla and its database
# Contributor : Seth Landsman <seth@dworkin.net>
# Initial checkin : 03/15/00 (SML)
# findUser() function moved from bug_email.pl to here
push @INC, "../."; # this script now lives in contrib
require "globals.pl";
use strict;
my $EMAIL_TRANSFORM_NONE = "email_transform_none";
my $EMAIL_TRANSFORM_BASE_DOMAIN = "email_transform_base_domain";
my $EMAIL_TRANSFORM_NAME_ONLY = "email_transform_name_only";
# change to do incoming email address fuzzy matching
my $email_transform = $EMAIL_TRANSFORM_NAME_ONLY;
# findUser()
# This function takes an email address and returns the user email.
# matching is sloppy based on the $email_transform parameter
sub findUser($) {
my ($address) = @_;
# if $email_transform is $EMAIL_TRANSFORM_NONE, return the address, otherwise, return undef
if ($email_transform eq $EMAIL_TRANSFORM_NONE) {
my $stmt = "SELECT login_name FROM profiles WHERE profiles.login_name = \'$address\';";
SendSQL($stmt);
my $found_address = FetchOneColumn();
return $found_address;
} elsif ($email_transform eq $EMAIL_TRANSFORM_BASE_DOMAIN) {
my ($username) = ($address =~ /(.+)@/);
my $stmt = "SELECT login_name FROM profiles WHERE profiles.login_name RLIKE \'$username\';";
SendSQL($stmt);
my $domain;
my $found = undef;
my $found_address;
my $new_address = undef;
while ((!$found) && ($found_address = FetchOneColumn())) {
($domain) = ($found_address =~ /.+@(.+)/);
if ($address =~ /$domain/) {
$found = 1;
$new_address = $found_address;
}
}
return $new_address;
} elsif ($email_transform eq $EMAIL_TRANSFORM_NAME_ONLY) {
my ($username) = ($address =~ /(.+)@/);
my $stmt = "SELECT login_name FROM profiles WHERE profiles.login_name RLIKE \'$username\';";
SendSQL($stmt);
my $found_address = FetchOneColumn();
return $found_address;
}
}
1;

View File

@@ -1,54 +0,0 @@
This directory contains contributed software related to Bugzilla.
Things in here have not necessarily been tested or tried by anyone
except the original contributor, so tred carefully. But it may still
be useful to you.
This file is encoded in UTF8 for purposes of contributor names.
This directory includes:
bugzilla-submit/ -- A standalone bug submission program.
mysqld-watcher.pl -- This script can be installed as a frequent cron
job to clean up stalled/dead queries.
sendbugmail.pl -- This script is a drop-in replacement for the
'processmail' script which used to be shipped
with Bugzilla, but was replaced by the
Bugzilla/BugMail.pm Perl module. You can use
this script if you were previously calling
processmail from other scripts external to
Bugzilla. See the comments at the top of
the file for usage information. Contributed
by Nick Barnes of Ravenbrook Limited.
gnatsparse/ -- A Python script used to import a GNATS database
into Bugzilla.
gnats2bz.pl -- A perl script to help import bugs from a GNATS
database into a Bugzilla database. Contributed by
Tom Schutter <tom@platte.com>
bug_email.pl -- A perl script that can receive email containing
bug reports (email-interface). Contributed by
Klaas Freitag <freitag@SuSE.de>
README.Mailif -- Readme describing the mail interface.
bugmail_help.html -- User help page for the mail interface.
yp_nomail.sh -- Script you can run via cron that regularly updates
the nomail file for terminated employees
bugzilla_ldapsync.rb -- Script you can run via cron that queries an LDAP
server for e-mail addresses to add Bugzilla users
for. Will optionally disable Bugzilla users with
no matching LDAP record. Contributed by Thomas
Stromberg <thomas+bugzilla@stromberg.org>
syncLDAP.pl -- Script you can run via cron that queries an LDAP
server for users and e-mail addresses and adds
missing users to Bugzilla. Can disable/update
non-existing/changed information. Contributed by
Andreas Höfler <andreas.hoefler@bearingpoint.com>

View File

@@ -1,80 +0,0 @@
The Bugzilla Mail interface
===========================
(UPDATE 03/14/00 to better reflect reality by SML)
The Bugzilla Mail interface allows to submit bugs to Bugzilla by email.
The Mail Interface Contribution consists of three files:
README.Mailif - this readme.
bug_email.pl - the script
bugmail_help.html - a user help html site
Installation:
Next is to add a user who receives the bugmails, e. g. bugmail. Create a
mail account and a home directory for the user.
The mailinterface script bug_email.pl needs to get the mail through stdin.
I use procmail for that, with the following line in the .procmailrc:
BUGZILLA_HOME=/usr/local/httpd/htdocs/bugzilla
:0 c
|(cd $BUGZILLA_HOME/contrib; ./bug_email.pl)
This defines the Bugzilla directory as the variable BUGZILLA_HOME and passes
all incoming mail to the script after cd'ing into the bugzilla home.
In some cases, it is necessary to alter the headers of incoming email. The
additional line to procmail :
:0 fhw
| formail -I "From " -a "From "
fixes many problems.
See bugzilla.procmailrc for a sample procmailrc that works for me (SML) and
also deals with bugzilla_email_append.pl
Customation:
There are some values inside the script which need to be customized for your
needs:
1. In sub-routine Reply (search 'sub Reply':
there is the line
print MAIL "From: Bugzilla Mailinterface<yourmail\@here.com>\n";
^^^^^^^^^^^^^^^^^^^^
Fill in your correct mail here. That will make it easy for people to reply
to the mail.
2. check, if your sendmail resides in /usr/sbin/sendmail, change the path if neccessary.
Search the script after 'default' - you find some default-Settings for bug
reports, which are used, if the sender did not send a field for it. The defaults
should be checked and changed.
Thats hopefully all, we will come up with any configuration file or something.
If your mail works, your script will insert mails from now on.
The mailinterface supports two commandline switches:
There are two command line switches :
-t: Testmode
The mailinterface does not really insert the bug into the database, but
writes some debug output to stdout and writes the mail into the file
bug_email_test.log in the data-dir.
-r: restricted mode
All lines before the first line with a keyword character are skipped.
In not restricted, default mode, these lines are added to the long
description of the bug.
02/2000 - Klaas Freitag, SuSE GmbH <freitag@suse.de>
03/2000 - Seth M. Landsman <seth@cs.brandeis.edu>
bug_email.pl now lives out of bugzilla/contrib
added line about formail

File diff suppressed because it is too large Load Diff

View File

@@ -1,223 +0,0 @@
<HTML>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<!--
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): Klaas Freitag <Freitag@SuSE.de>
-->
<HEAD> <TITLE>Bugzilla Mail Interface</TITLE> </HEAD>
<BODY BGCOLOR="#FFFFFF">
<CENTER><H1>The Bugzilla Mail Interface</H1>
Contributor: <A HREF="mailto:freitag@suse.de">Klaas Freitag</A>, SuSE GmbH
</CENTER>
<P>
The bugzilla Mail interface allows the registered bugzilla users to submit bugs by
sending email with a bug description. This is usefull for people, who do not work
inhouse and want to submitt bugs to the bugzilla system.
<p>
I know, show me the <A HREF="#examplemail">example-mail !</A>
<H2>What do you need to do to submitt a bug by mail ?</H2>
You need to send a email in the described format to the bugmail-user of the
bugzilla-system. This is <A HREF="mailto:our_bugzilla@xyz.com">yourbugzilla@here.com</A>
You receive a reply mail with the new bug-ID if your request was ok.
If not, you get a mail with
some help on the bugmail system and a specific analysis of your request.
<P>
Please dont refuse to send one or two wrong mails, you will get all the information
you need in the replies, and <I>only</I> in the mail replies. The information on this
page, concerning available products, versions and so on, is not dynamicly generated and
may be old therefore.
<H1>The Mail Format</H1>
The bugmail needs a special format , which consists of some keywords and suitable
values for them and a description text. Note that the keyword block needs to be
above of the description text.
<H2>Keywords</H2>
You need to tell bugzilla some properties of the bugs. This is done by keywords, which
start on a new line with a @, followed by the keyword and and equal-sign, followed by a
hopefully valid value.
<TABLE BORDER=4 FRAME=box CELLSPACING="5" width=95%> <COLGROUP> <col width="2*">
<col width="5*"> <col width="1*"> </COLGROUP>
<TR>
<TH>Keyword</TH>
<TH>Value description</TH>
<TH>required and default value</TH>
</TR>
<TR>
<TD>@product</TD>
<TD>The product which has a bug</TD>
<TD>yes. <br> This is the most important information. Many other
fields depend on the product.</TD>
</TR>
<TR>
<TD>@component</TD>
<TD>the desired component which is affected by the bug</TD>
<TD>yes. <br> As the @product, this is a very important
field.</TD>
</TR>
<TR>
<TD>@version</TD>
<TD>The version of the product</TD>
<TD>yes. <br>See @product and @component</TD>
</TR>
<TR>
<TD>@short_desc</TD>
<TD>A summary of your bug report</TD>
<TD>yes. <br>This summary of the error you want to report
describes what happen. You may skip the long description,
but not this summary.<br>
<b>Note:</b>The short description may be given in the mail subject
instead of using the keyword !</TD>
</TR>
<TR>
<TD>@rep_platform</TD>
<TD>The desired platform</TD>
<TD>no.<br>If you dont give a value, this field is set to <I>All</I>.</TD>
</TR>
<TR>
<TD>@bug_severity</TD>
<TD>The severity of the bug</TD>
<TD>no. <br> If you dont give a value, this field is set to
<I>normal</I></TD>
</TR>
<TR>
<TD>@priority</TD>
<TD>The priority of the bug</TD>
<TD>no.<br>If you dont give a value, this field is set to <I>P3</I></TD>
</TR>
<TR>
<TD>@op_sys</TD>
<TD>The operating system</TD>
<TD>no.<br>If you dont give a value, this field is set to <I>Linux</I>.</TD>
</TR>
<TR>
<TD>@assigned_to</TD>
<TD>The one to whom the bug is assigned to</TD>
<TD>no. <br>There is an initial owner for every product/version/component.
He owns the bug by default. The initial owner can only be found if
product, version and component are valid.</TD>
</TR>
<TR>
<TD>@bug_file_loc</TD>
<TD>?</TD>
<TD>no.</TD>
</TR>
<TR>
<TD>@status_whiteboard</TD>
<TD>?</TD>
<TD>no.</TD>
</TR>
<TR>
<TD>@target_milestone</TD>
<TD>?</TD>
<TD>no.</TD>
</TR>
<TR>
<TD>@groupset</TD>
<TD>rules the visibility of the bug.</TD>
<TD>no.<br>This value defaults to the smallest of the available groups,
which is <I>readInternal</I>.</TD>
</TR>
<TR>
<TD>@qa_contact</TD>
<TD>the quality manager for the product</TD>
<TD>no.<br>This value can be retrieved from product, component and
version</TD>
</TR>
</TABLE>
<H2>Valid values</H2>
Give string values for the most keys above. Some keywords require special values:<br>
<ol>
<li>E-Mail adresses: If you want to set the qa-contact, specify a email-adress for @qa_contact. The email must be known by bugzilla of course.</li>
<li>Listvalues: Most of the values have to be one of a list of valid values. Try by sending
a mail and read the reply. Skip fields if you dont get help for them unless you dont know
which values you may choose.</li>
<li>free Text: The descriptions may be free text. </li>
<li>Special: The field groupset may be specified in different in three different kinds:
<ol>
<li> A plain numeric way, which is one usually huge number, e. g. <I>65536</I></li>
<li> a string with added numbers e.g. <I>65536+131072</I></li>
<li> a string list, e.g. <I>ReadInternal, ReadBeta </I></li>
</ol>
</li>
</ol>
<p>
But most of them need <b>valid</b> values.
<p>
Sorry, you will not find lists of valid products, components and the other stuff
here. Send a mail to with any text, and you will get a list of valid keywords in the reply.
<p>
Some of the values must be choosen from a list:<br>
<ol>
<li>bug_severity: blocker, critical, major, normal, minor, trivial, enhancement</li>
<li>op_sys: Linux </li>
<li>priority: P1, P2, P3, P4, P5</li>
<li>rep_platform: All, i386, AXP, i686, Other</li></ol>
<p>
After you have specified the required keywords and maybe some other value, you may
describe your bug. You dont need a keyword for starting your bug description. All
text which follows the keyword block is handled as long description of the bug.
<p>
The bugmail interface is able to find required information by itself. E.g. if you specify
a product which has exactly one component, this component will be found by the interface
automatically.
<H1>Attachments</H1>
The mail interface is able to cope with MIME-attachments.
People could for example add a logfile as a mail attachment, and it will appear in
bugzilla as attachment. A comment for the attachment should be added, it will describe
the attachment in bugzilla.
<H1><A NAME="examplemail">Example Mail</A></H1>
See the example of the mail <b>body</b> (Dont forget to specify the short description
in the mail subject):<hr><pre>
@product = Bugzilla
@component = general
@version = All
@groupset = ReadWorld ReadPartners
@op_sys = Linux
@priority = P3
@rep_platform = i386
This is the description of the bug I found. It is not neccessary to start
it with a keyword.
Note: The short_description is neccessary and may be given with the keyword
@short_description or will be retrieved from the mail subject.
</pre><hr>
</BODY>
</HTML>

View File

@@ -1,46 +0,0 @@
bugzilla-submit
===============
Authors: Christian Reis <kiko@async.com.br>
Eric Raymond <esr@thyrsus.com>
bugzilla-submit is a simple Python program that creates bugs in a Bugzilla
instance. It takes as input text resembling message headers (RFC-822
formatted) via standard input, or optionally a number of commandline
parameters. It communicates using HTTP, which allows it to work over a
network.
Requirements
------------
Its only requirement is Python 2.3 or higher; you should have the
"python" executable in your path.
Usage Notes
-----------
* Please constrain testing to your own installation of Bugzilla, or use
* http://landfill.bugzilla.org/ for testing purposes -- opening test
* bugs on production instances of Bugzilla is definitely not a good idea
Run "bugzilla-submit --help" for a description of the possible options.
An example input file, named bugdata.txt, is provided. You can pipe it
in as standard input to bugzilla-submit, providing a Bugzilla URI through
the command-line.
Note that you must create a ~/.netrc entry to authenticate against the
Bugzilla instance. The entry's machine field is a *quoted* Bugzilla URI,
the login field is your ID on that host, and the password field is the
your password password. An example entry follows:
machine "http://bugzilla.mozilla.org/"
login foo@bar.loo
password snarf
Documentation
-------------
Documentation for bugzilla-submit is provided in Docbook format; see
bugzilla-submit.xml.

View File

@@ -1,13 +0,0 @@
Product: FoodReplicator
Component: Salt
Version: 1.0
Priority: P2
Hardware: PC
OS: Linux
Severity: critical
Summary: Impending electron shortage
Depends-on: 8
URL: http://www.google.com/
We need an emergency supply of electrons.

View File

@@ -1,290 +0,0 @@
#!/usr/bin/env python
#
# bugzilla-submit: a command-line script to post bugs to a Bugzilla instance
#
# Authors: Christian Reis <kiko@async.com.br>
# Eric S. Raymond <esr@thyrsus.com>
#
# This is version 0.5.
#
# For a usage hint run bugzilla-submit --help
#
# TODO: use RDF output to pick up valid options, as in
# http://www.async.com.br/~kiko/mybugzilla/config.cgi?ctype=rdf
import sys, string
def error(m):
sys.stderr.write("bugzilla-submit: %s\n" % m)
sys.stderr.flush()
sys.exit(1)
version = string.split(string.split(sys.version)[0], ".")[:2]
if map(int, version) < [2, 3]:
error("you must upgrade to Python 2.3 or higher to use this script.")
import urllib, re, os, netrc, email.Parser, optparse
# Set up some aliases -- partly to hide the less friendly fieldnames
# behind the names actually used for them in the stock web page presentation,
# and partly to provide a place for mappings if the Bugzilla fieldnames
# ever change.
field_aliases = (('hardware', 'rep_platform'),
('os', 'op_sys'),
('summary', 'short_desc'),
('description', 'comment'),
('depends_on', 'dependson'),
('status', 'bug_status'),
('severity', 'bug_severity'),
('url', 'bug_file_loc'),)
def header_to_field(hdr):
hdr = hdr.lower().replace("-", "_")
for (alias, name) in field_aliases:
if hdr == alias:
hdr = name
break
return hdr
def field_to_header(hdr):
hdr = "-".join(map(lambda x: x.capitalize(), hdr.split("_")))
for (alias, name) in field_aliases:
if hdr == name:
hdr = alias
break
return hdr
def setup_parser():
# Take override values from the command line
parser = optparse.OptionParser(usage="usage: %prog [options] bugzilla-url")
parser.add_option('-b', '--status', dest='bug_status',
help='Set the Status field.')
parser.add_option('-u', '--url', dest='bug_file_loc',
help='Set the URL field.')
parser.add_option('-p', '--product', dest='product',
help='Set the Product field.')
parser.add_option('-v', '--version', dest='version',
help='Set the Version field.')
parser.add_option('-c', '--component', dest='component',
help='Set the Component field.')
parser.add_option('-s', '--summary', dest='short_desc',
help='Set the Summary field.')
parser.add_option('-H', '--hardware', dest='rep_platform',
help='Set the Hardware field.')
parser.add_option('-o', '--os', dest='op_sys',
help='Set the Operating System field.')
parser.add_option('-r', '--priority', dest='priority',
help='Set the Priority field.')
parser.add_option('-x', '--severity', dest='bug_severity',
help='Set the Severity field.')
parser.add_option('-d', '--description', dest='comment',
help='Set the Description field.')
parser.add_option('-a', '--assigned-to', dest='assigned_to',
help='Set the Assigned-To field.')
parser.add_option('-C', '--cc', dest='cc',
help='Set the Cc field.')
parser.add_option('-k', '--keywords', dest='keywords',
help='Set the Keywords field.')
parser.add_option('-D', '--depends-on', dest='dependson',
help='Set the Depends-On field.')
parser.add_option('-B', '--blocked', dest='blocked',
help='Set the Blocked field.')
parser.add_option('-n', '--no-stdin', dest='read',
default=True, action='store_false',
help='Suppress reading fields from stdin.')
return parser
# Fetch user's credential for access to this Bugzilla instance.
def get_credentials(bugzilla):
# Work around a quirk in the Python implementation.
# The URL has to be quoted, otherwise the parser barfs on the colon.
# But the parser doesn't strip the quotes.
authenticate_on = '"' + bugzilla + '"'
try:
credentials = netrc.netrc()
except netrc.NetrcParseError, e:
error("ill-formed .netrc: %s:%s %s" % (e.filename, e.lineno, e.msg))
except IOError, e:
error("missing .netrc file %s" % str(e).split()[-1])
ret = credentials.authenticators(authenticate_on)
if not ret:
# Okay, the literal string passed in failed. Just to make sure,
# try adding/removing a slash after the address and looking
# again. We don't know what format was used in .netrc, which is
# why this rather hackish approach is necessary.
if bugzilla[-1] == "/":
authenticate_on = '"' + bugzilla[:-1] + '"'
else:
authenticate_on = '"' + bugzilla + '/"'
ret = credentials.authenticators(authenticate_on)
if not ret:
# Apparently, an invalid machine URL will cause credentials == None
error("no credentials for Bugzilla instance at %s" % bugzilla)
return ret
def process_options(options):
data = {}
# Initialize bug report fields from message on standard input
if options.read:
message_parser = email.Parser.Parser()
message = message_parser.parse(sys.stdin)
for (key, value) in message.items():
data.update({header_to_field(key) : value})
if not 'comment' in data:
data['comment'] = message.get_payload()
# Merge in options from the command line; they override what's on stdin.
for (key, value) in options.__dict__.items():
if key != 'read' and value != None:
data[key] = value
return data
def ensure_defaults(data):
# Provide some defaults if the user did not supply them.
if 'op_sys' not in data:
if sys.platform.startswith('linux'):
data['op_sys'] = 'Linux'
if 'rep_platform' not in data:
data['rep_platform'] = 'PC'
if 'bug_status' not in data:
data['bug_status'] = 'NEW'
if 'bug_severity' not in data:
data['bug_severity'] = 'normal'
if 'bug_file_loc' not in data:
data['bug_file_loc'] = 'http://' # Yes, Bugzilla needs this
if 'priority' not in data:
data['priority'] = 'P2'
def validate_fields(data):
# Fields for validation
required_fields = (
"bug_status", "bug_file_loc", "product", "version", "component",
"short_desc", "rep_platform", "op_sys", "priority", "bug_severity",
"comment",
)
legal_fields = required_fields + (
"assigned_to", "cc", "keywords", "dependson", "blocked",
)
my_fields = data.keys()
for field in my_fields:
if field not in legal_fields:
error("invalid field: %s" % field_to_header(field))
for field in required_fields:
if field not in my_fields:
error("required field missing: %s" % field_to_header(field))
if not data['short_desc']:
error("summary for bug submission must not be empty")
if not data['comment']:
error("comment for bug submission must not be empty")
#
# POST-specific functions
#
def submit_bug_POST(bugzilla, data):
# Move the request over the wire
postdata = urllib.urlencode(data)
url = urllib.urlopen("%s/post_bug.cgi" % bugzilla, postdata)
ret = url.read()
check_result_POST(ret, data)
def check_result_POST(ret, data):
# XXX We can move pre-validation out of here as soon as we pick up
# the valid options from config.cgi -- it will become a simple
# assertion and ID-grabbing step.
#
# XXX: We use get() here which may return None, but since the user
# might not have provided these options, we don't want to die on
# them.
version = data.get('version')
product = data.get('product')
component = data.get('component')
priority = data.get('priority')
severity = data.get('bug_severity')
status = data.get('bug_status')
assignee = data.get('assigned_to')
platform = data.get('rep_platform')
opsys = data.get('op_sys')
keywords = data.get('keywords')
deps = data.get('dependson', '') + " " + data.get('blocked', '')
deps = deps.replace(" ", ", ")
# XXX: we should really not be using plain find() here, as it can
# match the bug content inadvertedly
if ret.find("A legal Version was not") != -1:
error("version %r does not exist for component %s:%s" %
(version, product, component))
if ret.find("A legal Priority was not") != -1:
error("priority %r does not exist in "
"this Bugzilla instance" % priority)
if ret.find("A legal Severity was not") != -1:
error("severity %r does not exist in "
"this Bugzilla instance" % severity)
if ret.find("A legal Status was not") != -1:
error("status %r is not a valid creation status in "
"this Bugzilla instance" % status)
if ret.find("A legal Platform was not") != -1:
error("platform %r is not a valid platform in "
"this Bugzilla instance" % platform)
if ret.find("A legal OS/Version was not") != -1:
error("%r is not a valid OS in "
"this Bugzilla instance" % opsys)
if ret.find("Invalid Username") != -1:
error("invalid credentials submitted")
if ret.find("Component Needed") != -1:
error("the component %r does not exist in "
"this Bugzilla instance" % component)
if ret.find("Unknown Keyword") != -1:
error("keyword(s) %r not registered in "
"this Bugzilla instance" % keywords)
if ret.find("The product name") != -1:
error("Product %r does not exist in this "
"Bugzilla instance" % product)
# XXX: this should be smarter
if ret.find("does not exist") != -1:
error("Could not mark dependencies for bugs %s: one or "
"more bugs didn't exist in this Bugzilla instance" % deps)
if ret.find("Match Failed") != -1:
# XXX: invalid CC hits on this error too
error("the bug assignee %r isn't registered in "
"this Bugzilla instance" % assignee)
# If all is well, return bug number posted
m = re.search("Bug ([0-9]+) Submitted", ret)
if not m:
print ret
error("Internal error: bug id not found; please report a bug")
id = m.group(1)
print "Bug %s posted." % id
#
#
#
if __name__ == "__main__":
parser = setup_parser()
# Parser will print help and exit here if we specified --help
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error("missing Bugzilla host URL")
bugzilla = args[0]
data = process_options(options)
login, account, password = get_credentials(bugzilla)
if "@" not in login: # no use even trying to submit
error("login %r is invalid (it should be an email address)" % login)
ensure_defaults(data)
validate_fields(data)
# Attach authentication information
data.update({'Bugzilla_login' : login,
'Bugzilla_password' : password,
'GoAheadAndLogIn' : 1,
'form_name' : 'enter_bug'})
submit_bug_POST(bugzilla, data)

View File

@@ -1,200 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE refentry PUBLIC
"-//OASIS//DTD DocBook XML V4.1.2//EN"
"docbook/docbookx.dtd">
<refentry id='bugzilla-submit.1'>
<refmeta>
<refentrytitle>bugzilla-submit</refentrytitle>
<manvolnum>1</manvolnum>
<refmiscinfo class='date'>Oct 30, 2003</refmiscinfo>
</refmeta>
<refnamediv id='name'>
<refname>bugzilla-submit</refname>
<refpurpose>post bugs to a Bugzilla instance</refpurpose>
</refnamediv>
<refsynopsisdiv id='synopsis'>
<cmdsynopsis>
<command>bugzilla-submit</command>
<arg choice='opt'>--status <replaceable>bug_status</replaceable></arg>
<arg choice='opt'>--url <replaceable>bug_file_loc</replaceable></arg>
<arg choice='opt'>--product <replaceable>product</replaceable></arg>
<arg choice='opt'>--version <replaceable>version</replaceable></arg>
<arg choice='opt'>--component <replaceable>component</replaceable></arg>
<arg choice='opt'>--summary <replaceable>short_desc</replaceable></arg>
<arg choice='opt'>--hardware <replaceable>rep_platform</replaceable></arg>
<arg choice='opt'>--os <replaceable>op_sys</replaceable></arg>
<arg choice='opt'>--priority <replaceable>priority</replaceable></arg>
<arg choice='opt'>--severity <replaceable>bug_severity</replaceable></arg>
<arg choice='opt'>--assigned-to <replaceable>assigned-to</replaceable></arg>
<arg choice='opt'>--cc <replaceable>cc</replaceable></arg>
<arg choice='opt'>--keywords <replaceable>keywords</replaceable></arg>
<arg choice='opt'>--depends-on <replaceable>dependson</replaceable></arg>
<arg choice='opt'>--blocked <replaceable>blocked</replaceable></arg>
<arg choice='opt'>--description <replaceable>comment</replaceable></arg>
<arg choice='opt'>--no-read </arg>
<arg choice='plain'><replaceable>bugzilla-url</replaceable></arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1 id='description'><title>DESCRIPTION</title>
<para><application>bugzilla-submit</application> is a command-line tool
for posting bug reports to any instance of Bugzilla. It accepts on
standard input text resembling an RFC-822 message. The headers of
that message, and its body, are used to set error-report field values.
More field values are merged in from command-line options. If required
fields have not been set, <application>bugzilla-submit</application>
tries to compute them. Finally, the resulting error report is
validated. If all required fields are present, and there are no
illegal fields or values, the report is shipped off to the Mozilla
instance specified by the single positional argument. Login/password
credentials are read from the calling user's <filename>~/.netrc</filename>
file.</para>
<para>The program accepts the following options to set or override fields:</para>
<variablelist>
<varlistentry>
<term>-b. --bug-status</term>
<listitem>
<para>Set the bug_status field, overriding the Status header from
standard input if present. (The stock Bugzilla web presentation
identifies this field as <quote>Status</quote>.)</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-u, --url</term>
<listitem>
<para>Set the bug_file_loc field, overriding the URL header from
standard input if present. (The stock Bugzilla web presentation
identifies this field as <quote>URL</quote>.)</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-p, --product</term>
<listitem>
<para>Set the product field, overriding the Product header from
standard input if necessary.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-v, --version</term>
<listitem><para>Set the version field, overriding the Version header
from standard input if necessary.</para></listitem>
</varlistentry>
<varlistentry>
<term>-c, --component</term>
<listitem><para>Set the component field, overriding the Component header
from standard input if necessary.</para></listitem>
</varlistentry>
<varlistentry>
<term>-s, --summary</term>
<listitem><para>Set the short_desc field, overriding the Summary header
from standard input if necessary. (The stock Bugzilla web presentation
identifies this field as <quote>Summary</quote>.)</para></listitem>
</varlistentry>
<varlistentry>
<term>-H, --hardware</term>
<listitem><para>Set the rep_platform field, overriding the Hardware header
from standard input if necessary. (The stock Bugzilla web presentation
identifies this field as <quote>Hardware</quote>.)</para></listitem>
</varlistentry>
<varlistentry>
<term>-o, --os</term>
<listitem><para>Set the op_sys field, overriding the OS (Operating
System) header from standard input if necessary. (The stock Bugzilla web
presentation also identifies this field as
<quote>OS</quote>.)</para></listitem>
</varlistentry>
<varlistentry>
<term>-r, --priority</term>
<listitem><para>Set the priority field, overriding the Priority header
from standard input if necessary.</para></listitem>
</varlistentry>
<varlistentry>
<term>-x, --severity</term>
<listitem><para>Set the severity field, overriding the Severity header
from standard input if necessary.</para></listitem>
</varlistentry>
<varlistentry>
<term>-d, --description</term>
<listitem><para>Set the comment field, overriding the Description header
from standard input if necessary. (The stock Bugzilla web presentation
identifies this field as <quote>Description</quote>.) If there is a
message body and no Description field and this option is not
specified, the message body is used as a description.
</para></listitem>
</varlistentry>
<varlistentry>
<term>-a, --assigned-to</term>
<listitem>
<para>Set the optional assigned_to field, overriding the Assigned-To
header from standard input if necessary.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-C, --cc</term>
<listitem>
<para>Set the optional cc field, overriding the Cc
header from standard input if necessary.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-k, --keywords</term>
<listitem>
<para>Set the optional keywords field, overriding the Keywords
header from standard input if necessary.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-D, --depends-on</term>
<listitem>
<para>Set the optional dependson field, overriding the Depends-On
header from standard input if necessary.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-B, --assigned-to</term>
<listitem>
<para>Set the optional blocked field, overriding the Blocked
header from standard input if necessary.</para>
</listitem>
</varlistentry>
<varlistentry>
<term>-n, --no-stdin</term>
<listitem><para>Suppress reading fields from standard input.</para></listitem>
</varlistentry>
<varlistentry>
<term>-h, --help</term>
<listitem><para>Print usage help and exit.</para></listitem>
</varlistentry>
</variablelist>
<para>This program will try to deduce OS and Hardware if those are not
specified. If it fails, validation will fail before shipping the
report.</para>
<para>There is expected to be a single positional argument following
any options. It should be the URL of the Bugzilla instance to which
the bug is to be submitted.</para>
</refsect1>
<refsect1 id='files'><title>FILES</title>
<variablelist>
<varlistentry>
<term><filename>~/.netrc</filename></term>
<listitem><para>Must contain an entry in which the machine field is
the Bugzilla instance URL, the login field is your ID on that host, and the
password field is the right password. The URL in the machine field
must be enclosed in double quotes.</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id='author'><title>AUTHORS</title>
<para>Christian Reis &lt;kiko@async.com.br&gt;, Eric S. Raymond
&lt;esr@thyrsus.com&gt;.</para>
</refsect1>
</refentry>

View File

@@ -1,30 +0,0 @@
:0 fhw
| formail -I "From " -a "From "
BUGZILLA_HOME=/home/bugzilla/WEB/bugzilla/contrib
:0
* ^Subject: .*\[Bug .*\]
RESULT=|(cd $BUGZILLA_HOME && ./bugzilla_email_append.pl)
# Feed mail to stdin of bug_email.pl
:0 Ec
#* !^Subject: .*[Bug .*]
RESULT=|(cd $BUGZILLA_HOME && ./bug_email.pl )
# write result to a logfile
:0 c
|echo `date '+%d.%m.%y %H:%M: '` $RESULT >> $HOME/bug_email.log
:0 c
|echo "----------------------------------" >> $HOME/bug_email.log
:0 c
$HOME/bug_email.log
# Move mail to the inbox
:0
$HOME/Mail/INBOX

View File

@@ -1,194 +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 purpose of this script is to take an email message, which
# specifies a bugid and append it to the bug as part of the longdesc
# table
# Contributor : Seth M. Landsman <seth@dworkin.net>
# 03/15/00 : Initial version by SML
# 03/15/00 : processmail gets called
# Email subject must be of format :
# .* Bug ### .*
# replying to a typical bugzilla email should be valid
# TODO :
# 1. better way to get the body text (I don't know what dump_entity() is
# actually doing
use strict;
use MIME::Parser;
BEGIN {
chdir ".."; # this script lives in contrib, change to main
push @INC, "contrib";
push @INC, "."; # this script lives in contrib
}
require "globals.pl";
use BugzillaEmail;
use Bugzilla::Config qw(:DEFAULT $datadir);
use Bugzilla::BugMail;
# Create a new MIME parser:
my $parser = new MIME::Parser;
my $Comment = "";
# Create and set the output directory:
# FIXME: There should be a $BUGZILLA_HOME variable (SML)
(-d "$datadir/mimedump-tmp") or mkdir "$datadir/mimedump-tmp",0755 or die "mkdir: $!";
(-w "$datadir/mimedump-tmp") or die "can't write to directory";
$parser->output_dir("$datadir/mimedump-tmp");
# Read the MIME message:
my $entity = $parser->read(\*STDIN) or die "couldn't parse MIME stream";
$entity->remove_sig(10); # Removes the signature in the last 10 lines
# Getting values from parsed mail
my $Sender = $entity->get( 'From' );
$Sender ||= $entity->get( 'Reply-To' );
my $Message_ID = $entity->get( 'Message-Id' );
die (" *** Cant find Sender-adress in sent mail ! ***\n" ) unless defined( $Sender );
chomp( $Sender );
chomp( $Message_ID );
print "Dealing with the sender $Sender\n";
my $SenderShort = $Sender;
$SenderShort =~ s/^.*?([a-zA-Z0-9_.-]+?\@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+).*$/$1/;
$SenderShort = findUser($SenderShort);
print "SenderShort is $SenderShort\n";
if (!defined($SenderShort)) {
$SenderShort = $Sender;
$SenderShort =~ s/^.*?([a-zA-Z0-9_.-]+?\@[a-zA-Z0-9_.-]+\.[a-zA-Z0-9_.-]+).*$/$1/;
}
print "The sendershort is now $SenderShort\n";
if (!defined($SenderShort)) {
DealWithError("No such user $SenderShort exists.");
}
my $Subject = $entity->get('Subject');
print "The subject is $Subject\n";
my ($bugid) = ($Subject =~ /\[Bug ([\d]+)\]/);
print "The bugid is $bugid\n";
# make sure the bug exists
SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $bugid;");
my $found_id = FetchOneColumn();
print "Did we find the bug? $found_id-\n";
if (!defined($found_id)) {
DealWithError("Bug $bugid does not exist");
}
# get the user id
SendSQL("SELECT userid FROM profiles WHERE login_name = \'$SenderShort\';");
my $userid = FetchOneColumn();
if (!defined($userid)) {
DealWithError("Userid not found for $SenderShort");
}
# parse out the text of the message
dump_entity($entity);
# Get rid of the bug id
$Subject =~ s/\[Bug [\d]+\]//;
#my $Comment = "This is only a test ...";
my $Body = "Subject: " . $Subject . "\n" . $Comment;
# shove it in the table
my $long_desc_query = "INSERT INTO longdescs SET bug_id=$found_id, who=$userid, bug_when=NOW(), thetext=" . SqlQuote($Body) . ";";
SendSQL($long_desc_query);
Bugzilla::BugMail::Send( $found_id, { changer => $SenderShort } );
sub DealWithError {
my ($reason) = @_;
print $reason . "\n";
exit 100;
}
# Yanking this wholesale from bug_email, 'cause I know this works. I'll
# figure out what it really does later
#------------------------------
#
# dump_entity ENTITY, NAME
#
# Recursive routine for parsing a mime coded mail.
# One mail may contain more than one mime blocks, which need to be
# handled. Therefore, this function is called recursively.
#
# It gets the for bugzilla important information from the mailbody and
# stores them into the global attachment-list @attachments. The attachment-list
# is needed in storeAttachments.
#
sub dump_entity {
my ($entity, $name) = @_;
defined($name) or $name = "'anonymous'";
my $IO;
# Output the body:
my @parts = $entity->parts;
if (@parts) { # multipart...
my $i;
foreach $i (0 .. $#parts) { # dump each part...
dump_entity($parts[$i], ("$name, part ".(1+$i)));
}
} else { # single part...
# Get MIME type, and display accordingly...
my $msg_part = $entity->head->get( 'Content-Disposition' );
$msg_part ||= "";
my ($type, $subtype) = split('/', $entity->head->mime_type);
my $body = $entity->bodyhandle;
my ($data, $on_disk );
if( $msg_part =~ /^attachment/ ) {
# Attached File
my $des = $entity->head->get('Content-Description');
$des ||= "";
if( defined( $body->path )) { # Data is on disk
$on_disk = 1;
$data = $body->path;
} else { # Data is in core
$on_disk = 0;
$data = $body->as_string;
}
# push ( @attachments, [ $data, $entity->head->mime_type, $on_disk, $des ] );
} else {
# Real Message
if ($type =~ /^(text|message)$/) { # text: display it...
if ($IO = $body->open("r")) {
$Comment .= $_ while (defined($_ = $IO->getline));
$IO->close;
} else { # d'oh!
print "$0: couldn't find/open '$name': $!";
}
} else { print "Oooops - no Body !\n"; }
}
}
}

View File

@@ -1,185 +0,0 @@
#!/usr/local/bin/ruby
# Queries an LDAP server for all email addresses (tested against Exchange 5.5),
# and makes nice bugzilla user entries out of them. Also disables Bugzilla users
# that are not found in LDAP.
# $Id: bugzilla_ldapsync.rb,v 1.2 2003-04-26 16:35:04 jake%bugzilla.org Exp $
require 'ldap'
require 'dbi'
require 'getoptlong'
opts = GetoptLong.new(
['--dbname', '-d', GetoptLong::OPTIONAL_ARGUMENT],
['--dbpassword', '-p', GetoptLong::OPTIONAL_ARGUMENT],
['--dbuser', '-u', GetoptLong::OPTIONAL_ARGUMENT],
['--dbpassfile', '-P', GetoptLong::OPTIONAL_ARGUMENT],
['--ldaphost', '-h', GetoptLong::REQUIRED_ARGUMENT],
['--ldapbase', '-b', GetoptLong::OPTIONAL_ARGUMENT],
['--ldapquery', '-q', GetoptLong::OPTIONAL_ARGUMENT],
['--maildomain', '-m', GetoptLong::OPTIONAL_ARGUMENT],
['--noremove', '-n', GetoptLong::OPTIONAL_ARGUMENT],
['--defaultpass', '-D', GetoptLong::OPTIONAL_ARGUMENT],
['--checkmode', '-c', GetoptLong::OPTIONAL_ARGUMENT]
)
# in hash to make it easy
optHash = Hash.new
opts.each do |opt, arg|
optHash[opt]=arg
end
# grab password from file if it's an option
if optHash['--dbpassfile']
dbPassword=File.open(optHash['--dbpassfile'], 'r').readlines[0].chomp!
else
dbPassword=optHash['--dbpassword'] || nil
end
# make bad assumptions.
dbName = optHash['--dbname'] || 'bugzilla'
dbUser = optHash['--dbuser'] || 'bugzilla'
ldapHost = optHash['--ldaphost'] || 'ldap'
ldapBase = optHash['--ldapbase'] || ''
mailDomain = optHash['--maildomain'] || `domainname`.chomp!
ldapQuery = optHash['--ldapquery'] || "(&(objectclass=person)(rfc822Mailbox=*@#{mailDomain}))"
checkMode = optHash['--checkmode'] || nil
noRemove = optHash['--noremove'] || nil
defaultPass = optHash['--defaultpass'] || 'bugzilla'
if (! dbPassword)
puts "bugzilla_ldapsync v1.3 (c) 2003 Thomas Stromberg <thomas+bugzilla@stromberg.org>"
puts ""
puts " -d | --dbname name of MySQL database [#{dbName}]"
puts " -u | --dbuser username for MySQL database [#{dbUser}]"
puts " -p | --dbpassword password for MySQL user [#{dbPassword}]"
puts " -P | --dbpassfile filename containing password for MySQL user"
puts " -h | --ldaphost hostname for LDAP server [#{ldapHost}]"
puts " -b | --ldapbase Base of LDAP query, for instance, o=Bugzilla.com"
puts " -q | --ldapquery LDAP query, uses maildomain [#{ldapQuery}]"
puts " -m | --maildomain e-mail domain to use records from"
puts " -n | --noremove do not remove Bugzilla users that are not in LDAP"
puts " -c | --checkmode checkmode, does not perform any SQL changes"
puts " -D | --defaultpass default password for new users [#{defaultPass}]"
puts
puts "example:"
puts
puts " bugzilla_ldapsync.rb -c -u taskzilla -P /tmp/test -d taskzilla -h bhncmail -m \"bowebellhowell.com\""
exit
end
if (checkMode)
puts '(checkmode enabled, no SQL writes will actually happen)'
puts "ldapquery is #{ldapQuery}"
puts
end
bugzillaUsers = Hash.new
ldapUsers = Hash.new
encPassword = defaultPass.crypt('xx')
sqlNewUser = "INSERT INTO profiles VALUES ('', ?, '#{encPassword}', ?, '', 1, NULL, '0000-00-00 00:00:00');"
# presumes that the MySQL database is local.
dbh = DBI.connect("DBI:Mysql:#{dbName}", dbUser, dbPassword)
# select all e-mail addresses where there is no disabledtext defined. Only valid users, please!
dbh.select_all('select login_name, realname, disabledtext from profiles') { |row|
login = row[0].downcase
bugzillaUsers[login] = Hash.new
bugzillaUsers[login]['desc'] = row[1]
bugzillaUsers[login]['disabled'] = row[2]
#puts "bugzilla has #{login} - \"#{bugzillaUsers[login]['desc']}\" (#{bugzillaUsers[login]['disabled']})"
}
LDAP::Conn.new(ldapHost, 389).bind{|conn|
sub = nil
# perform the query, but only get the e-mail address, location, and name returned to us.
conn.search(ldapBase, LDAP::LDAP_SCOPE_SUBTREE, ldapQuery,
['rfc822Mailbox', 'physicalDeliveryOfficeName', 'cn']) { |entry|
# Get the users first (primary) e-mail address, but I only want what's before the @ sign.
entry.vals("rfc822Mailbox")[0] =~ /([\w\.-]+)\@/
email = $1
# We put the officename in the users description, and nothing otherwise.
if entry.vals("physicalDeliveryOfficeName")
location = entry.vals("physicalDeliveryOfficeName")[0]
else
location = ''
end
# for whatever reason, we get blank entries. Do some double checking here.
if (email && (email.length > 4) && (location !~ /Generic/) && (entry.vals("cn")))
if (location.length > 2)
desc = entry.vals("cn")[0] + " (" + location + ")"
else
desc = entry.vals("cn")[0]
end
# take care of the whitespace.
desc.sub!("\s+$", "")
desc.sub!("^\s+", "")
# dumb hack. should be properly escaped, and apostrophes should never ever ever be in email.
email.sub!("\'", "%")
email.sub!('%', "\'")
email=email.downcase
ldapUsers[email.downcase] = Hash.new
ldapUsers[email.downcase]['desc'] = desc
ldapUsers[email.downcase]['disabled'] = nil
#puts "ldap has #{email} - #{ldapUsers[email.downcase]['desc']}"
end
}
}
# This is the loop that takes the users that we found in Bugzilla originally, and
# checks to see if they are still in the LDAP server. If they are not, away they go!
ldapUsers.each_key { |user|
# user does not exist at all.
#puts "checking ldap user #{user}"
if (! bugzillaUsers[user])
puts "+ Adding #{user} - #{ldapUsers[user]['desc']}"
if (! checkMode)
dbh.do(sqlNewUser, user, ldapUsers[user]['desc'])
end
# short-circuit now.
next
end
if (bugzillaUsers[user]['desc'] != ldapUsers[user]['desc'])
puts "* Changing #{user} from \"#{bugzillaUsers[user]['desc']}\" to \"#{ldapUsers[user]['desc']}\""
if (! checkMode)
# not efficient.
dbh.do("UPDATE profiles SET realname = ? WHERE login_name = ?", ldapUsers[user]['desc'], user)
end
end
if (bugzillaUsers[user]['disabled'].length > 0)
puts "+ Enabling #{user} (was \"#{bugzillaUsers[user]['disabled']}\")"
if (! checkMode)
dbh.do("UPDATE profiles SET disabledtext = NULL WHERE login_name=\"#{user}\"")
end
end
}
if (! noRemove)
bugzillaUsers.each_key { |user|
if ((bugzillaUsers[user]['disabled'].length < 1) && (! ldapUsers[user]))
puts "- Disabling #{user} (#{bugzillaUsers[user]['disabled']})"
if (! checkMode)
dbh.do("UPDATE profiles SET disabledtext = \'auto-disabled by ldap sync\' WHERE login_name=\"#{user}\"")
end
end
}
end
dbh.disconnect

View File

@@ -1,94 +0,0 @@
#!/bin/sh
# 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
# Andreas Franke <afranke@ags.uni-sb.de>.
# Corporation. Portions created by Andreas Franke are
# Copyright (C) 2001 Andreas Franke. All
# Rights Reserved.
#
# Contributor(s):
conf="`dirname $0`/query.conf"
query="http://bugzilla.mozilla.org/buglist.cgi?cmd=doit"
defaultcolumnlist="severity priority platform status resolution target_milestone status_whiteboard keywords summaryfull"
chart=0
and=0
while test "$1" != ""; do
arg=$1
arg_len=`expr length ${arg}`
if test `expr substr "${arg}" 1 2` == "--"; then
eq_pos=`expr match ${arg} '--.*='`
if test "${eq_pos}" == "0"; then
echo 'Missing value for long option '"${arg}"' ("=" not found)' 1>&2
exit 1;
fi
# extract option name
let name_len=${eq_pos}-3
name=`expr substr ${arg} 3 ${name_len}`
# extract option value
let val_start=${eq_pos}+1
let val_len=${arg_len}-${eq_pos}
val=`expr substr ${arg} ${val_start} ${val_len}`
elif test `expr substr ${arg} 1 1` == "-" &&
test "`expr substr ${arg} 2 1`" != ""; then
# extract
name=`expr substr ${arg} 2 1`
let val_len=${arg_len}-2
val=`expr substr ${arg} 3 ${val_len}`
else
name="default"
val="${arg}"
#echo "Unrecognized option ${arg}" 1>&2
#exit 1
fi
# find field and comparison type for option ${name}
field=`grep '"'${name}'"' ${conf} | awk '{printf $1}'`
type=`grep '"'${name}'"' ${conf} | awk '{printf $2}'`
if test "${field}" == "" || test "${type}" == ""; then
echo "Field name & comparison type not found for option ${name}." 1>&2
exit 1;
fi
or=0
while test "${val}" != ""; do
comma_idx=`expr index ${val} ,`
if test ${comma_idx} == "0"; then
val1="${val}"
val=""
else
let val1_len=${comma_idx}-1
val1=`expr substr ${val} 1 ${val1_len}`
val_len=`expr length ${val}`
let rest_start=${comma_idx}+1
let rest_len=${val_len}-${comma_idx}
val=`expr substr ${val} ${rest_start} ${rest_len}`
fi
query="${query}&field${chart}-${and}-${or}=${field}"
query="${query}&type${chart}-${and}-${or}=${type}"
query="${query}&value${chart}-${and}-${or}=${val1}"
#echo "----- ${name} : ${field} : ${type} : ${val1} -----" 1>&2
let or=${or}+1
done
let chart=${chart}+1
shift
done
outputfile="/dev/stdout"
#outputfile="buglist.html"
#\rm -f ${outputfile}
wget -q -O ${outputfile} --header="Cookie: COLUMNLIST=${COLUMNLIST-${defaultcolumnlist}}" "${query}"

View File

@@ -1,31 +0,0 @@
#!/bin/sh
# 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
# Andreas Franke <afranke@ags.uni-sb.de>.
# Corporation. Portions created by Andreas Franke are
# Copyright (C) 2001 Andreas Franke. All
# Rights Reserved.
#
# Contributor(s):
buglist="`dirname $0`/buglist"
htmlfile="`dirname $0`/buglist.html"
${buglist} "$@" 2>&1 1>${htmlfile}
if test ${?} == "0"; then
echo `grep 'TR VALIGN=TOP ALIGN=LEFT CLASS=' ${htmlfile} | sed -e 's/<TR.*id=//' | sed -e 's/".*//'` | sed -e 's/ /\,/g'
else
cat ${htmlfile} 1>&2
exit 1
fi

View File

@@ -1,49 +0,0 @@
# 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
# Andreas Franke <afranke@ags.uni-sb.de>.
# Corporation. Portions created by Andreas Franke are
# Copyright (C) 2001 Andreas Franke. All
# Rights Reserved.
#
# Contributor(s):
#
# This is `query.conf', the config file for `buglist'.
#
# Columns: 1: field_name, 2: comparison_type, 3: cmd-line options
#
bug_status substring "s","status"
resolution substring "r","resolution"
rep_platform substring "p","platform"
op_sys substring "o","os","opsys"
priority substring "p","priority"
bug_severity substring "S","severity"
assigned_to substring "A","O","owner","assignedto"
reporter substring "R","reporter"
qa_contact substring "Q","qa","qacontact"
cc substring "C","cc"
product substring "product"
version substring "V","version"
component substring "c","component"
target_milestone substring "M","milestone"
short_desc substring "default","summary"
longdesc substring "d","description","longdesc"
bug_file_loc substring "u","url"
status_whiteboard substring "w","whiteboard"
keywords substring "k","K","keywords"
attachments.description substring "attachdesc"
attachments.thedata substring "attachdata"
attachments.mimetype substring "attachmime"
dependson substring # bug 30823
blocked substring # bug 30823

View File

@@ -1,51 +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 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): Dawn Endico <endico@mozilla.org>
# Jacob Steenhagen <jake@bugzilla.org>
# Jouni Heikniemi <jouni@heikniemi.net>
# Keep a record of all cvs updates made from a given directory.
#
# Later, if changes need to be backed out, look at the log file
# and run the cvs command with the date that you want to back
# out to. (Probably the second to last entry).
# Because this script lives in contrib, you may want to
# ln -s contrib/cvs-update.pl cvs-update.pl
# from your bugzilla install directory so you can run
# the script easily from there (./cvs-update.pl)
#DATE=`date +%e/%m/%Y\ %k:%M:%S\ %Z`
my ($second, $minute, $hour, $day, $month, $year) = gmtime;
my $date = sprintf("%04d-%02d-%02d %d:%02d:%02dZ",
$year+1900, $month+1, $day, $hour, $minute, $second);
my $cmd = "cvs -q update -dP";
open LOG, ">>cvs-update.log" or die("Couldn't open cvs update log!");
print LOG "$cmd -D \"$date\"\n";
close LOG;
system("$cmd -A");
# sample log file
#cvs update -P -D "11/04/2000 20:22:08 PDT"
#cvs update -P -D "11/05/2000 20:22:22 PDT"
#cvs update -P -D "11/07/2000 20:26:29 PDT"
#cvs update -P -D "11/08/2000 20:27:10 PDT"

File diff suppressed because it is too large Load Diff

View File

@@ -1,62 +0,0 @@
gnatsparse
==========
Author: Daniel Berlin <dan@dberlin.org>
gnatsparse is a simple Python program that imports a GNATS database
into a Bugzilla system. It is based on the gnats2bz.pl Perl script
but it's a rewrite at the same time. Its parser is based on gnatsweb,
which gives a 10 times speed improvement compared to the previous code.
Features
--------
* Chunks audit trail into separate comments, with the right From's, times, etc.
* Handles followup emails that are in the report, with the right From's, times,
etc.
* Properly handles duplicates, adding the standard bugzilla duplicate message.
* Extracts and handles gnatsweb attachments, as well as uuencoded attachments
appearing in either followup emails, the how-to-repeat field, etc. Replaces
them with a message to look at the attachments list, and adds the standard
"Created an attachment" message that bugzilla uses. Handling them includes
giving them the right name and mime-type. "attachments" means multiple
uuencoded things/gnatsweb attachments are handled properly.
* Handles reopened bug reports.
* Builds the cc list from the people who have commented on the report,
and the reporter.
Requirements
------------
It requires python 2.2+, it won't work with 1.5.2 (Linux distributions
ship with 2.2+ these days, so that shouldn't be an issue).
Documentation
-------------
Documentation can be found inside the scripts. The source code is self
documenting.
Issues for someone trying to use it to convert a gnats install
-----------------------------------
1. We have three custom fields bugzilla doesn't ship with,
gcchost, gcctarget, and gccbuild.
We removed two bugzilla fields, rep_platform and op_sys.
If you use the latter instead of the former, you'll need to
update the script to account for this.
2. Because gcc attachments consist of preprocessed source, all attachments
inserted into the attachment database are compressed with zlib.compress.
This requires associated bugzilla changes to decompress before sending to
the browser.
Unless you want to make those changes (it's roughly 3 lines), you'll
need to remove the zlib.compress call.
3. You will need to come up with your own release to version mapping and
install it.
4. Obviously, any extra gnats fields you have added will have to
be handled in some manner.

View File

@@ -1,804 +0,0 @@
try:
# Using Psyco makes it about 25% faster, but there's a bug in psyco in
# handling of eval causing it to use unlimited memory with the magic
# file enabled.
# import psyco
# psyco.full()
# from psyco.classes import *
pass
except:
pass
import re
import base64
import cStringIO
import specialuu
import array
import email.Utils
import zlib
import magic
# Comment out if you don't want magic detection
magicf = magic.MagicFile()
# Open our output file
outfile = open("gnats2bz_data.sql", "w")
# List of GNATS fields
fieldnames = ("Number", "Category", "Synopsis", "Confidential", "Severity",
"Priority", "Responsible", "State", "Quarter", "Keywords",
"Date-Required", "Class", "Submitter-Id", "Arrival-Date",
"Closed-Date", "Last-Modified", "Originator", "Release",
"Organization", "Environment", "Description", "How-To-Repeat",
"Fix", "Release-Note", "Audit-Trail", "Unformatted")
# Dictionary telling us which GNATS fields are multiline
multilinefields = {"Organization":1, "Environment":1, "Description":1,
"How-To-Repeat":1, "Fix":1, "Release-Note":1,
"Audit-Trail":1, "Unformatted":1}
# Mapping of GCC release to version. Our version string is updated every
# so we need to funnel all release's with 3.4 in the string to be version
# 3.4 for bug tracking purposes
# The key is a regex to match, the value is the version it corresponds
# with
releasetovermap = {r"3\.4":"3.4", r"3\.3":"3.3", r"3\.2\.2":"3.2.2",
r"3\.2\.1":"3.2.1", r"3\.2":"3.2", r"3\.1\.2":"3.1.2",
r"3\.1\.1":"3.1.1", r"3\.1":"3.1", r"3\.0\.4":"3.0.4",
r"3\.0\.3":"3.0.3", r"3\.0\.2":"3.0.2", r"3\.0\.1":"3.0.1",
r"3\.0":"3.0", r"2\.95\.4":"2.95.4", r"2\.95\.3":"2.95.3",
r"2\.95\.2":"2.95.2", r"2\.95\.1":"2.95.1",
r"2\.95":"2.95", r"2\.97":"2.97",
r"2\.96.*[rR][eE][dD].*[hH][aA][tT]":"2.96 (redhat)",
r"2\.96":"2.96"}
# These map the field name to the field id bugzilla assigns. We need
# the id when doing bug activity.
fieldids = {"State":8, "Responsible":15}
# These are the keywords we use in gcc bug tracking. They are transformed
# into bugzilla keywords. The format here is <keyword>-><bugzilla keyword id>
keywordids = {"wrong-code":1, "ice-on-legal-code":2, "ice-on-illegal-code":3,
"rejects-legal":4, "accepts-illegal":5, "pessimizes-code":6}
# Map from GNATS states to Bugzilla states. Duplicates and reopened bugs
# are handled when parsing the audit trail, so no need for them here.
state_lookup = {"":"NEW", "open":"ASSIGNED", "analyzed":"ASSIGNED",
"feedback":"WAITING", "closed":"CLOSED",
"suspended":"SUSPENDED"}
# Table of versions that exist in the bugs, built up as we go along
versions_table = {}
# Delimiter gnatsweb uses for attachments
attachment_delimiter = "----gnatsweb-attachment----\n"
# Here starts the various regular expressions we use
# Matches an entire GNATS single line field
gnatfieldre = re.compile(r"""^([>\w\-]+)\s*:\s*(.*)\s*$""")
# Matches the name of a GNATS field
fieldnamere = re.compile(r"""^>(.*)$""")
# Matches the useless part of an envelope
uselessre = re.compile(r"""^(\S*?):\s*""", re.MULTILINE)
# Matches the filename in a content disposition
dispositionre = re.compile("(\\S+);\\s*filename=\"([^\"]+)\"")
# Matches the last changed date in the entire text of a bug
# If you have other editable fields that get audit trail entries, modify this
# The field names are explicitly listed in order to speed up matching
lastdatere = re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-When: )(.+?)$""", re.MULTILINE)
# Matches the From line of an email or the first line of an audit trail entry
# We use this re to find the begin lines of all the audit trail entries
# The field names are explicitly listed in order to speed up matching
fromtore=re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-From-To: |From: )""", re.MULTILINE)
# These re's match the various parts of an audit trail entry
changedfromtore=re.compile(r"""^(\w+?)-Changed-From-To: (.+?)$""", re.MULTILINE)
changedbyre=re.compile(r"""^\w+?-Changed-By: (.+?)$""", re.MULTILINE)
changedwhenre=re.compile(r"""^\w+?-Changed-When: (.+?)$""", re.MULTILINE)
changedwhyre=re.compile(r"""^\w+?-Changed-Why:\s*(.*?)$""", re.MULTILINE)
# This re matches audit trail text saying that the current bug is a duplicate of another
duplicatere=re.compile(r"""(?:")?Dup(?:licate)?(?:d)?(?:")? of .*?(\d+)""", re.IGNORECASE | re.MULTILINE)
# Get the text of a From: line
fromre=re.compile(r"""^From: (.*?)$""", re.MULTILINE)
# Get the text of a Date: Line
datere=re.compile(r"""^Date: (.*?)$""", re.MULTILINE)
# Map of the responsible file to email addresses
responsible_map = {}
# List of records in the responsible file
responsible_list = []
# List of records in the categories file
categories_list = []
# List of pr's in the index
pr_list = []
# Map usernames to user ids
usermapping = {}
# Start with this user id
userid_base = 2
# Name of gnats user
gnats_username = "gnats@gcc.gnu.org"
# Name of unassigned user
unassigned_username = "unassigned@gcc.gnu.org"
gnats_db_dir = "."
product = "gcc"
productdesc = "GNU Compiler Connection"
milestoneurl = "http://gcc/gnu.org"
defaultmilestone = "3.4"
def write_non_bug_tables():
""" Write out the non-bug related tables, such as products, profiles, etc."""
# Set all non-unconfirmed bugs's everconfirmed flag
print >>outfile, "update bugs set everconfirmed=1 where bug_status != 'UNCONFIRMED';"
# Set all bugs assigned to the unassigned user to NEW
print >>outfile, "update bugs set bug_status='NEW',assigned_to='NULL' where bug_status='ASSIGNED' AND assigned_to=3;"
# Insert the products
print >>outfile, "\ninsert into products ("
print >>outfile, " product, description, milestoneurl, disallownew,"
print >>outfile, " defaultmilestone, votestoconfirm) values ("
print >>outfile, " '%s', '%s', '%s', 0, '%s', 1);" % (product,
productdesc,
milestoneurl,
defaultmilestone)
# Insert the components
for category in categories_list:
component = SqlQuote(category[0])
productstr = SqlQuote(product)
description = SqlQuote(category[1])
initialowner = SqlQuote("3")
print >>outfile, "\ninsert into components (";
print >>outfile, " value, program, initialowner, initialqacontact,"
print >>outfile, " description) values ("
print >>outfile, " %s, %s, %s, '', %s);" % (component, productstr,
initialowner, description)
# Insert the versions
for productstr, version_list in versions_table.items():
productstr = SqlQuote(productstr)
for version in version_list:
version = SqlQuote(version)
print >>outfile, "\ninsert into versions (value, program) "
print >>outfile, " values (%s, %s);" % (version, productstr)
# Insert the users
for username, userid in usermapping.items():
realname = map_username_to_realname(username)
username = SqlQuote(username)
realname = SqlQuote(realname)
print >>outfile, "\ninsert into profiles ("
print >>outfile, " userid, login_name, password, cryptpassword, realname, groupset"
print >>outfile, ") values ("
print >>outfile, "%s,%s,'password',encrypt('password'), %s, 0);" % (userid, username, realname)
print >>outfile, "update profiles set groupset=1 << 32 where login_name like '%\@gcc.gnu.org';"
def unixdate2datetime(unixdate):
""" Convert a unix date to a datetime value """
year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate)
return "%d-%02d-%02d %02d:%02d:%02d" % (year,month,day,hour,min,sec)
def unixdate2timestamp(unixdate):
""" Convert a unix date to a timestamp value """
year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate)
return "%d%02d%02d%02d%02d%02d" % (year,month,day,hour,min,sec)
def SqlQuote(str):
""" Perform SQL quoting on a string """
return "'%s'" % str.replace("'", """''""").replace("\\", "\\\\").replace("\0","\\0")
def convert_gccver_to_ver(gccver):
""" Given a gcc version, convert it to a Bugzilla version. """
for k in releasetovermap.keys():
if re.search(".*%s.*" % k, gccver) is not None:
return releasetovermap[k]
result = re.search(r""".*(\d\.\d) \d+ \(experimental\).*""", gccver)
if result is not None:
return result.group(1)
return "unknown"
def load_index(fname):
""" Load in the GNATS index file """
global pr_list
ifp = open(fname)
for record in ifp.xreadlines():
fields = record.split("|")
pr_list.append(fields[0])
ifp.close()
def load_categories(fname):
""" Load in the GNATS categories file """
global categories_list
cfp = open(fname)
for record in cfp.xreadlines():
if re.search("^#", record) is not None:
continue
categories_list.append(record.split(":"))
cfp.close()
def map_username_to_realname(username):
""" Given a username, find the real name """
name = username
name = re.sub("@.*", "", name)
for responsible_record in responsible_list:
if responsible_record[0] == name:
return responsible_record[1]
if len(responsible_record) > 2:
if responsible_record[2] == username:
return responsible_record[1]
return ""
def get_userid(responsible):
""" Given an email address, get the user id """
global responsible_map
global usermapping
global userid_base
if responsible is None:
return -1
responsible = responsible.lower()
responsible = re.sub("sources.redhat.com", "gcc.gnu.org", responsible)
if responsible_map.has_key(responsible):
responsible = responsible_map[responsible]
if usermapping.has_key(responsible):
return usermapping[responsible]
else:
usermapping[responsible] = userid_base
userid_base += 1
return usermapping[responsible]
def load_responsible(fname):
""" Load in the GNATS responsible file """
global responsible_map
global responsible_list
rfp = open(fname)
for record in rfp.xreadlines():
if re.search("^#", record) is not None:
continue
split_record = record.split(":")
responsible_map[split_record[0]] = split_record[2].rstrip()
responsible_list.append(record.split(":"))
rfp.close()
def split_csl(list):
""" Split a comma seperated list """
newlist = re.split(r"""\s*,\s*""", list)
return newlist
def fix_email_addrs(addrs):
""" Perform various fixups and cleaning on an e-mail address """
addrs = split_csl(addrs)
trimmed_addrs = []
for addr in addrs:
addr = re.sub(r"""\(.*\)""","",addr)
addr = re.sub(r""".*<(.*)>.*""","\\1",addr)
addr = addr.rstrip()
addr = addr.lstrip()
trimmed_addrs.append(addr)
addrs = ", ".join(trimmed_addrs)
return addrs
class Bugzillabug(object):
""" Class representing a bugzilla bug """
def __init__(self, gbug):
""" Initialize a bugzilla bug from a GNATS bug. """
self.bug_id = gbug.bug_id
self.long_descs = []
self.bug_ccs = [get_userid("gcc-bugs@gcc.gnu.org")]
self.bug_activity = []
self.attachments = gbug.attachments
self.gnatsfields = gbug.fields
self.need_unformatted = gbug.has_unformatted_attach == 0
self.need_unformatted &= gbug.fields.has_key("Unformatted")
self.translate_pr()
self.update_versions()
if self.fields.has_key("Audit-Trail"):
self.parse_audit_trail()
self.write_bug()
def parse_fromto(type, string):
""" Parses the from and to parts of a changed-from-to line """
fromstr = ""
tostr = ""
# Some slightly messed up changed lines have unassigned-new,
# instead of unassigned->new. So we make the > optional.
result = re.search(r"""(.*)-(?:>?)(.*)""", string)
# Only know how to handle parsing of State and Responsible
# changed-from-to right now
if type == "State":
fromstr = state_lookup[result.group(1)]
tostr = state_lookup[result.group(2)]
elif type == "Responsible":
if result.group(1) != "":
fromstr = result.group(1)
if result.group(2) != "":
tostr = result.group(2)
if responsible_map.has_key(fromstr):
fromstr = responsible_map[fromstr]
if responsible_map.has_key(tostr):
tostr = responsible_map[tostr]
return (fromstr, tostr)
parse_fromto = staticmethod(parse_fromto)
def parse_audit_trail(self):
""" Parse a GNATS audit trail """
trail = self.fields["Audit-Trail"]
# Begin to split the audit trail into pieces
result = fromtore.finditer(trail)
starts = []
ends = []
pieces = []
# Make a list of the pieces
for x in result:
pieces.append (x)
# Find the start and end of each piece
if len(pieces) > 0:
for x in xrange(len(pieces)-1):
starts.append(pieces[x].start())
ends.append(pieces[x+1].start())
starts.append(pieces[-1].start())
ends.append(len(trail))
pieces = []
# Now make the list of actual text of the pieces
for x in xrange(len(starts)):
pieces.append(trail[starts[x]:ends[x]])
# And parse the actual pieces
for piece in pieces:
result = changedfromtore.search(piece)
# See what things we actually have inside this entry, and
# handle them approriately
if result is not None:
type = result.group(1)
changedfromto = result.group(2)
# If the bug was reopened, mark it as such
if changedfromto.find("closed->analyzed") != -1:
if self.fields["bug_status"] == "'NEW'":
self.fields["bug_status"] = "'REOPENED'"
if type == "State" or type == "Responsible":
oldstate, newstate = self.parse_fromto (type, changedfromto)
result = changedbyre.search(piece)
if result is not None:
changedby = result.group(1)
result = changedwhenre.search(piece)
if result is not None:
changedwhen = result.group(1)
changedwhen = unixdate2datetime(changedwhen)
changedwhen = SqlQuote(changedwhen)
result = changedwhyre.search(piece)
changedwhy = piece[result.start(1):]
#changedwhy = changedwhy.lstrip()
changedwhy = changedwhy.rstrip()
changedby = get_userid(changedby)
# Put us on the cc list if we aren't there already
if changedby != self.fields["userid"] \
and changedby not in self.bug_ccs:
self.bug_ccs.append(changedby)
# If it's a duplicate, mark it as such
result = duplicatere.search(changedwhy)
if result is not None:
newtext = "*** This bug has been marked as a duplicate of %s ***" % result.group(1)
newtext = SqlQuote(newtext)
self.long_descs.append((self.bug_id, changedby,
changedwhen, newtext))
self.fields["bug_status"] = "'RESOLVED'"
self.fields["resolution"] = "'DUPLICATE'"
self.fields["userid"] = changedby
else:
newtext = "%s-Changed-From-To: %s\n%s-Changed-Why: %s\n" % (type, changedfromto, type, changedwhy)
newtext = SqlQuote(newtext)
self.long_descs.append((self.bug_id, changedby,
changedwhen, newtext))
if type == "State" or type == "Responsible":
newstate = SqlQuote("%s" % newstate)
oldstate = SqlQuote("%s" % oldstate)
fieldid = fieldids[type]
self.bug_activity.append((newstate, oldstate, fieldid, changedby, changedwhen))
else:
# It's an email
result = fromre.search(piece)
if result is None:
continue
fromstr = result.group(1)
fromstr = fix_email_addrs(fromstr)
fromstr = get_userid(fromstr)
result = datere.search(piece)
if result is None:
continue
datestr = result.group(1)
datestr = SqlQuote(unixdate2timestamp(datestr))
if fromstr != self.fields["userid"] \
and fromstr not in self.bug_ccs:
self.bug_ccs.append(fromstr)
self.long_descs.append((self.bug_id, fromstr, datestr,
SqlQuote(piece)))
def write_bug(self):
""" Output a bug to the data file """
fields = self.fields
print >>outfile, "\ninsert into bugs("
print >>outfile, " bug_id, assigned_to, bug_severity, priority, bug_status, creation_ts, delta_ts,"
print >>outfile, " short_desc,"
print >>outfile, " reporter, version,"
print >>outfile, " product, component, resolution, target_milestone, qa_contact,"
print >>outfile, " gccbuild, gcctarget, gcchost, keywords"
print >>outfile, " ) values ("
print >>outfile, "%s, %s, %s, %s, %s, %s, %s," % (self.bug_id, fields["userid"], fields["bug_severity"], fields["priority"], fields["bug_status"], fields["creation_ts"], fields["delta_ts"])
print >>outfile, "%s," % (fields["short_desc"])
print >>outfile, "%s, %s," % (fields["reporter"], fields["version"])
print >>outfile, "%s, %s, %s, %s, 0," %(fields["product"], fields["component"], fields["resolution"], fields["target_milestone"])
print >>outfile, "%s, %s, %s, %s" % (fields["gccbuild"], fields["gcctarget"], fields["gcchost"], fields["keywords"])
print >>outfile, ");"
if self.fields["keywords"] != 0:
print >>outfile, "\ninsert into keywords (bug_id, keywordid) values ("
print >>outfile, " %s, %s);" % (self.bug_id, fields["keywordid"])
for id, who, when, text in self.long_descs:
print >>outfile, "\ninsert into longdescs ("
print >>outfile, " bug_id, who, bug_when, thetext) values("
print >>outfile, " %s, %s, %s, %s);" % (id, who, when, text)
for name, data, who in self.attachments:
print >>outfile, "\ninsert into attachments ("
print >>outfile, " bug_id, filename, description, mimetype, ispatch, submitter_id, thedata) values ("
ftype = None
# It's *magic*!
if name.endswith(".ii") == 1:
ftype = "text/x-c++"
elif name.endswith(".i") == 1:
ftype = "text/x-c"
else:
ftype = magicf.detect(cStringIO.StringIO(data))
if ftype is None:
ftype = "application/octet-stream"
print >>outfile, "%s,%s,%s, %s,0, %s,%s);" %(self.bug_id, SqlQuote(name), SqlQuote(name), SqlQuote (ftype), who, SqlQuote(zlib.compress(data)))
for newstate, oldstate, fieldid, changedby, changedwhen in self.bug_activity:
print >>outfile, "\ninsert into bugs_activity ("
print >>outfile, " bug_id, who, bug_when, fieldid, added, removed) values ("
print >>outfile, " %s, %s, %s, %s, %s, %s);" % (self.bug_id,
changedby,
changedwhen,
fieldid,
newstate,
oldstate)
for cc in self.bug_ccs:
print >>outfile, "\ninsert into cc(bug_id, who) values (%s, %s);" %(self.bug_id, cc)
def update_versions(self):
""" Update the versions table to account for the version on this bug """
global versions_table
if self.fields.has_key("Release") == 0 \
or self.fields.has_key("Category") == 0:
return
curr_product = "gcc"
curr_version = self.fields["Release"]
if curr_version == "":
return
curr_version = convert_gccver_to_ver (curr_version)
if versions_table.has_key(curr_product) == 0:
versions_table[curr_product] = []
for version in versions_table[curr_product]:
if version == curr_version:
return
versions_table[curr_product].append(curr_version)
def translate_pr(self):
""" Transform a GNATS PR into a Bugzilla bug """
self.fields = self.gnatsfields
if (self.fields.has_key("Organization") == 0) \
or self.fields["Organization"].find("GCC"):
self.fields["Originator"] = ""
self.fields["Organization"] = ""
self.fields["Organization"].lstrip()
if (self.fields.has_key("Release") == 0) \
or self.fields["Release"] == "" \
or self.fields["Release"].find("unknown-1.0") != -1:
self.fields["Release"]="unknown"
if self.fields.has_key("Responsible"):
result = re.search(r"""\w+""", self.fields["Responsible"])
self.fields["Responsible"] = "%s%s" % (result.group(0), "@gcc.gnu.org")
self.fields["gcchost"] = ""
self.fields["gcctarget"] = ""
self.fields["gccbuild"] = ""
if self.fields.has_key("Environment"):
result = re.search("^host: (.+?)$", self.fields["Environment"],
re.MULTILINE)
if result is not None:
self.fields["gcchost"] = result.group(1)
result = re.search("^target: (.+?)$", self.fields["Environment"],
re.MULTILINE)
if result is not None:
self.fields["gcctarget"] = result.group(1)
result = re.search("^build: (.+?)$", self.fields["Environment"],
re.MULTILINE)
if result is not None:
self.fields["gccbuild"] = result.group(1)
self.fields["userid"] = get_userid(self.fields["Responsible"])
self.fields["bug_severity"] = "normal"
if self.fields["Class"] == "change-request":
self.fields["bug_severity"] = "enhancement"
elif self.fields.has_key("Severity"):
if self.fields["Severity"] == "critical":
self.fields["bug_severity"] = "critical"
elif self.fields["Severity"] == "serious":
self.fields["bug_severity"] = "major"
elif self.fields.has_key("Synopsis"):
if re.search("crash|assert", self.fields["Synopsis"]):
self.fields["bug_severity"] = "critical"
elif re.search("wrong|error", self.fields["Synopsis"]):
self.fields["bug_severity"] = "major"
self.fields["bug_severity"] = SqlQuote(self.fields["bug_severity"])
self.fields["keywords"] = 0
if keywordids.has_key(self.fields["Class"]):
self.fields["keywords"] = self.fields["Class"]
self.fields["keywordid"] = keywordids[self.fields["Class"]]
self.fields["keywords"] = SqlQuote(self.fields["keywords"])
self.fields["priority"] = "P1"
if self.fields.has_key("Severity") and self.fields.has_key("Priority"):
severity = self.fields["Severity"]
priority = self.fields["Priority"]
if severity == "critical":
if priority == "high":
self.fields["priority"] = "P1"
else:
self.fields["priority"] = "P2"
elif severity == "serious":
if priority == "low":
self.fields["priority"] = "P4"
else:
self.fields["priority"] = "P3"
else:
if priority == "high":
self.fields["priority"] = "P4"
else:
self.fields["priority"] = "P5"
self.fields["priority"] = SqlQuote(self.fields["priority"])
state = self.fields["State"]
if (state == "open" or state == "analyzed") and self.fields["userid"] != 3:
self.fields["bug_status"] = "ASSIGNED"
self.fields["resolution"] = ""
elif state == "feedback":
self.fields["bug_status"] = "WAITING"
self.fields["resolution"] = ""
elif state == "closed":
self.fields["bug_status"] = "CLOSED"
if self.fields.has_key("Class"):
theclass = self.fields["Class"]
if theclass.find("duplicate") != -1:
self.fields["resolution"]="DUPLICATE"
elif theclass.find("mistaken") != -1:
self.fields["resolution"]="INVALID"
else:
self.fields["resolution"]="FIXED"
else:
self.fields["resolution"]="FIXED"
elif state == "suspended":
self.fields["bug_status"] = "SUSPENDED"
self.fields["resolution"] = ""
elif state == "analyzed" and self.fields["userid"] == 3:
self.fields["bug_status"] = "NEW"
self.fields["resolution"] = ""
else:
self.fields["bug_status"] = "UNCONFIRMED"
self.fields["resolution"] = ""
self.fields["bug_status"] = SqlQuote(self.fields["bug_status"])
self.fields["resolution"] = SqlQuote(self.fields["resolution"])
self.fields["creation_ts"] = ""
if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "":
self.fields["creation_ts"] = unixdate2datetime(self.fields["Arrival-Date"])
self.fields["creation_ts"] = SqlQuote(self.fields["creation_ts"])
self.fields["delta_ts"] = ""
if self.fields.has_key("Audit-Trail"):
result = lastdatere.findall(self.fields["Audit-Trail"])
result.reverse()
if len(result) > 0:
self.fields["delta_ts"] = unixdate2timestamp(result[0])
if self.fields["delta_ts"] == "":
if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "":
self.fields["delta_ts"] = unixdate2timestamp(self.fields["Arrival-Date"])
self.fields["delta_ts"] = SqlQuote(self.fields["delta_ts"])
self.fields["short_desc"] = SqlQuote(self.fields["Synopsis"])
if self.fields.has_key("Reply-To") and self.fields["Reply-To"] != "":
self.fields["reporter"] = get_userid(self.fields["Reply-To"])
elif self.fields.has_key("Mail-Header"):
result = re.search(r"""From .*?([\w.]+@[\w.]+)""", self.fields["Mail-Header"])
if result:
self.fields["reporter"] = get_userid(result.group(1))
else:
self.fields["reporter"] = get_userid(gnats_username)
else:
self.fields["reporter"] = get_userid(gnats_username)
long_desc = self.fields["Description"]
long_desc2 = ""
for field in ["Release", "Environment", "How-To-Repeat"]:
if self.fields.has_key(field) and self.fields[field] != "":
long_desc += ("\n\n%s:\n" % field) + self.fields[field]
if self.fields.has_key("Fix") and self.fields["Fix"] != "":
long_desc2 = "Fix:\n" + self.fields["Fix"]
if self.need_unformatted == 1 and self.fields["Unformatted"] != "":
long_desc += "\n\nUnformatted:\n" + self.fields["Unformatted"]
if long_desc != "":
self.long_descs.append((self.bug_id, self.fields["reporter"],
self.fields["creation_ts"],
SqlQuote(long_desc)))
if long_desc2 != "":
self.long_descs.append((self.bug_id, self.fields["reporter"],
self.fields["creation_ts"],
SqlQuote(long_desc2)))
for field in ["gcchost", "gccbuild", "gcctarget"]:
self.fields[field] = SqlQuote(self.fields[field])
self.fields["version"] = ""
if self.fields["Release"] != "":
self.fields["version"] = convert_gccver_to_ver (self.fields["Release"])
self.fields["version"] = SqlQuote(self.fields["version"])
self.fields["product"] = SqlQuote("gcc")
self.fields["component"] = "invalid"
if self.fields.has_key("Category"):
self.fields["component"] = self.fields["Category"]
self.fields["component"] = SqlQuote(self.fields["component"])
self.fields["target_milestone"] = "---"
if self.fields["version"].find("3.4") != -1:
self.fields["target_milestone"] = "3.4"
self.fields["target_milestone"] = SqlQuote(self.fields["target_milestone"])
if self.fields["userid"] == 2:
self.fields["userid"] = "\'NULL\'"
class GNATSbug(object):
""" Represents a single GNATS PR """
def __init__(self, filename):
self.attachments = []
self.has_unformatted_attach = 0
fp = open (filename)
self.fields = self.parse_pr(fp.xreadlines())
self.bug_id = int(self.fields["Number"])
if self.fields.has_key("Unformatted"):
self.find_gnatsweb_attachments()
if self.fields.has_key("How-To-Repeat"):
self.find_regular_attachments("How-To-Repeat")
if self.fields.has_key("Fix"):
self.find_regular_attachments("Fix")
def get_attacher(fields):
if fields.has_key("Reply-To") and fields["Reply-To"] != "":
return get_userid(fields["Reply-To"])
else:
result = None
if fields.has_key("Mail-Header"):
result = re.search(r"""From .*?([\w.]+\@[\w.]+)""",
fields["Mail-Header"])
if result is not None:
reporter = get_userid(result.group(1))
else:
reporter = get_userid(gnats_username)
get_attacher = staticmethod(get_attacher)
def find_regular_attachments(self, which):
fields = self.fields
while re.search("^begin [0-7]{3}", fields[which],
re.DOTALL | re.MULTILINE):
outfp = cStringIO.StringIO()
infp = cStringIO.StringIO(fields[which])
filename, start, end = specialuu.decode(infp, outfp, quiet=0)
fields[which]=fields[which].replace(fields[which][start:end],
"See attachments for %s\n" % filename)
self.attachments.append((filename, outfp.getvalue(),
self.get_attacher(fields)))
def decode_gnatsweb_attachment(self, attachment):
result = re.split(r"""\n\n""", attachment, 1)
if len(result) == 1:
return -1
envelope, body = result
envelope = uselessre.split(envelope)
envelope.pop(0)
# Turn the list of key, value into a dict of key => value
attachinfo = dict([(envelope[i], envelope[i+1]) for i in xrange(0,len(envelope),2)])
for x in attachinfo.keys():
attachinfo[x] = attachinfo[x].rstrip()
if (attachinfo.has_key("Content-Type") == 0) or \
(attachinfo.has_key("Content-Disposition") == 0):
raise ValueError, "Unable to parse file attachment"
result = dispositionre.search(attachinfo["Content-Disposition"])
filename = result.group(2)
filename = re.sub(".*/","", filename)
filename = re.sub(".*\\\\","", filename)
attachinfo["filename"]=filename
result = re.search("""(\S+);.*""", attachinfo["Content-Type"])
if result is not None:
attachinfo["Content-Type"] = result.group(1)
if attachinfo.has_key("Content-Transfer-Encoding"):
if attachinfo["Content-Transfer-Encoding"] == "base64":
attachinfo["data"] = base64.decodestring(body)
else:
attachinfo["data"]=body
return (attachinfo["filename"], attachinfo["data"],
self.get_attacher(self.fields))
def find_gnatsweb_attachments(self):
fields = self.fields
attachments = re.split(attachment_delimiter, fields["Unformatted"])
fields["Unformatted"] = attachments.pop(0)
for attachment in attachments:
result = self.decode_gnatsweb_attachment (attachment)
if result != -1:
self.attachments.append(result)
self.has_unformatted_attach = 1
def parse_pr(lines):
#fields = {"envelope":[]}
fields = {"envelope":array.array("c")}
hdrmulti = "envelope"
for line in lines:
line = line.rstrip('\n')
line += '\n'
result = gnatfieldre.search(line)
if result is None:
if hdrmulti != "":
if fields.has_key(hdrmulti):
#fields[hdrmulti].append(line)
fields[hdrmulti].fromstring(line)
else:
#fields[hdrmulti] = [line]
fields[hdrmulti] = array.array("c", line)
continue
hdr, arg = result.groups()
ghdr = "*not valid*"
result = fieldnamere.search(hdr)
if result != None:
ghdr = result.groups()[0]
if ghdr in fieldnames:
if multilinefields.has_key(ghdr):
hdrmulti = ghdr
#fields[ghdr] = [""]
fields[ghdr] = array.array("c")
else:
hdrmulti = ""
#fields[ghdr] = [arg]
fields[ghdr] = array.array("c", arg)
elif hdrmulti != "":
#fields[hdrmulti].append(line)
fields[hdrmulti].fromstring(line)
if hdrmulti == "envelope" and \
(hdr == "Reply-To" or hdr == "From" \
or hdr == "X-GNATS-Notify"):
arg = fix_email_addrs(arg)
#fields[hdr] = [arg]
fields[hdr] = array.array("c", arg)
if fields.has_key("Reply-To") and len(fields["Reply-To"]) > 0:
fields["Reply-To"] = fields["Reply-To"]
else:
fields["Reply-To"] = fields["From"]
if fields.has_key("From"):
del fields["From"]
if fields.has_key("X-GNATS-Notify") == 0:
fields["X-GNATS-Notify"] = array.array("c")
#fields["X-GNATS-Notify"] = ""
for x in fields.keys():
fields[x] = fields[x].tostring()
#fields[x] = "".join(fields[x])
for x in fields.keys():
if multilinefields.has_key(x):
fields[x] = fields[x].rstrip()
return fields
parse_pr = staticmethod(parse_pr)
load_index("%s/gnats-adm/index" % gnats_db_dir)
load_categories("%s/gnats-adm/categories" % gnats_db_dir)
load_responsible("%s/gnats-adm/responsible" % gnats_db_dir)
get_userid(gnats_username)
get_userid(unassigned_username)
for x in pr_list:
print "Processing %s..." % x
a = GNATSbug ("%s/%s" % (gnats_db_dir, x))
b = Bugzillabug(a)
write_non_bug_tables()
outfile.close()

View File

@@ -1,712 +0,0 @@
# Found on a russian zope mailing list, and modified to fix bugs in parsing
# the magic file and string making
# -- Daniel Berlin <dberlin@dberlin.org>
import sys, struct, time, re, exceptions, pprint, stat, os, pwd, grp
_mew = 0
# _magic='/tmp/magic'
# _magic='/usr/share/magic.mime'
_magic='/usr/share/magic.mime'
mime = 1
_ldate_adjust = lambda x: time.mktime( time.gmtime(x) )
BUFFER_SIZE = 1024 * 128 # 128K should be enough...
class MagicError(exceptions.Exception): pass
def _handle(fmt='@x',adj=None): return fmt, struct.calcsize(fmt), adj
KnownTypes = {
# 'byte':_handle('@b'),
'byte':_handle('@B'),
'ubyte':_handle('@B'),
'string':('s',0,None),
'pstring':_handle('p'),
# 'short':_handle('@h'),
# 'beshort':_handle('>h'),
# 'leshort':_handle('<h'),
'short':_handle('@H'),
'beshort':_handle('>H'),
'leshort':_handle('<H'),
'ushort':_handle('@H'),
'ubeshort':_handle('>H'),
'uleshort':_handle('<H'),
'long':_handle('@l'),
'belong':_handle('>l'),
'lelong':_handle('<l'),
'ulong':_handle('@L'),
'ubelong':_handle('>L'),
'ulelong':_handle('<L'),
'date':_handle('=l'),
'bedate':_handle('>l'),
'ledate':_handle('<l'),
'ldate':_handle('=l',_ldate_adjust),
'beldate':_handle('>l',_ldate_adjust),
'leldate':_handle('<l',_ldate_adjust),
}
_mew_cnt = 0
def mew(x):
global _mew_cnt
if _mew :
if x=='.' :
_mew_cnt += 1
if _mew_cnt % 64 == 0 : sys.stderr.write( '\n' )
sys.stderr.write( '.' )
else:
sys.stderr.write( '\b'+x )
def has_format(s):
n = 0
l = None
for c in s :
if c == '%' :
if l == '%' : n -= 1
else : n += 1
l = c
return n
def read_asciiz(file,size=None,pos=None):
s = []
if pos :
mew('s')
file.seek( pos, 0 )
mew('z')
if size is not None :
s = [file.read( size ).split('\0')[0]]
else:
while 1 :
c = file.read(1)
if (not c) or (ord(c)==0) or (c=='\n') : break
s.append (c)
mew('Z')
return ''.join(s)
def a2i(v,base=0):
if v[-1:] in 'lL' : v = v[:-1]
return int( v, base )
_cmap = {
'\\' : '\\',
'0' : '\0',
}
for c in range(ord('a'),ord('z')+1) :
try : e = eval('"\\%c"' % chr(c))
except ValueError : pass
else : _cmap[chr(c)] = e
else:
del c
del e
def make_string(s):
return eval( '"'+s.replace('"','\\"')+'"')
class MagicTestError(MagicError): pass
class MagicTest:
def __init__(self,offset,mtype,test,message,line=None,level=None):
self.line, self.level = line, level
self.mtype = mtype
self.mtest = test
self.subtests = []
self.mask = None
self.smod = None
self.nmod = None
self.offset, self.type, self.test, self.message = \
offset,mtype,test,message
if self.mtype == 'true' : return # XXX hack to enable level skips
if test[-1:]=='\\' and test[-2:]!='\\\\' :
self.test += 'n' # looks like someone wanted EOL to match?
if mtype[:6]=='string' :
if '/' in mtype : # for strings
self.type, self.smod = \
mtype[:mtype.find('/')], mtype[mtype.find('/')+1:]
else:
for nm in '&+-' :
if nm in mtype : # for integer-based
self.nmod, self.type, self.mask = (
nm,
mtype[:mtype.find(nm)],
# convert mask to int, autodetect base
int( mtype[mtype.find(nm)+1:], 0 )
)
break
self.struct, self.size, self.cast = KnownTypes[ self.type ]
def __str__(self):
return '%s %s %s %s' % (
self.offset, self.mtype, self.mtest, self.message
)
def __repr__(self):
return 'MagicTest(%s,%s,%s,%s,line=%s,level=%s,subtests=\n%s%s)' % (
`self.offset`, `self.mtype`, `self.mtest`, `self.message`,
`self.line`, `self.level`,
'\t'*self.level, pprint.pformat(self.subtests)
)
def run(self,file):
result = ''
do_close = 0
try:
if type(file) == type('x') :
file = open( file, 'r', BUFFER_SIZE )
do_close = 1
# else:
# saved_pos = file.tell()
if self.mtype != 'true' :
data = self.read(file)
last = file.tell()
else:
data = last = None
if self.check( data ) :
result = self.message+' '
if has_format( result ) : result %= data
for test in self.subtests :
m = test.run(file)
if m is not None : result += m
return make_string( result )
finally:
if do_close :
file.close()
# else:
# file.seek( saved_pos, 0 )
def get_mod_and_value(self):
if self.type[-6:] == 'string' :
# "something like\tthis\n"
if self.test[0] in '=<>' :
mod, value = self.test[0], make_string( self.test[1:] )
else:
mod, value = '=', make_string( self.test )
else:
if self.test[0] in '=<>&^' :
mod, value = self.test[0], a2i(self.test[1:])
elif self.test[0] == 'x':
mod = self.test[0]
value = 0
else:
mod, value = '=', a2i(self.test)
return mod, value
def read(self,file):
mew( 's' )
file.seek( self.offset(file), 0 ) # SEEK_SET
mew( 'r' )
try:
data = rdata = None
# XXX self.size might be 0 here...
if self.size == 0 :
# this is an ASCIIZ string...
size = None
if self.test != '>\\0' : # magic's hack for string read...
value = self.get_mod_and_value()[1]
size = (value=='\0') and None or len(value)
rdata = data = read_asciiz( file, size=size )
else:
rdata = file.read( self.size )
if not rdata or (len(rdata)!=self.size) : return None
data = struct.unpack( self.struct, rdata )[0] # XXX hack??
except:
print >>sys.stderr, self
print >>sys.stderr, '@%s struct=%s size=%d rdata=%s' % (
self.offset, `self.struct`, self.size,`rdata`)
raise
mew( 'R' )
if self.cast : data = self.cast( data )
if self.mask :
try:
if self.nmod == '&' : data &= self.mask
elif self.nmod == '+' : data += self.mask
elif self.nmod == '-' : data -= self.mask
else: raise MagicTestError(self.nmod)
except:
print >>sys.stderr,'data=%s nmod=%s mask=%s' % (
`data`, `self.nmod`, `self.mask`
)
raise
return data
def check(self,data):
mew('.')
if self.mtype == 'true' :
return '' # not None !
mod, value = self.get_mod_and_value()
if self.type[-6:] == 'string' :
# "something like\tthis\n"
if self.smod :
xdata = data
if 'b' in self.smod : # all blanks are optional
xdata = ''.join( data.split() )
value = ''.join( value.split() )
if 'c' in self.smod : # all blanks are optional
xdata = xdata.upper()
value = value.upper()
# if 'B' in self.smod : # compact blanks
### XXX sorry, i don't understand this :-(
# data = ' '.join( data.split() )
# if ' ' not in data : return None
else:
xdata = data
try:
if mod == '=' : result = data == value
elif mod == '<' : result = data < value
elif mod == '>' : result = data > value
elif mod == '&' : result = data & value
elif mod == '^' : result = (data & (~value)) == 0
elif mod == 'x' : result = 1
else : raise MagicTestError(self.test)
if result :
zdata, zval = `data`, `value`
if self.mtype[-6:]!='string' :
try: zdata, zval = hex(data), hex(value)
except: zdata, zval = `data`, `value`
if 0 : print >>sys.stderr, '%s @%s %s:%s %s %s => %s (%s)' % (
'>'*self.level, self.offset,
zdata, self.mtype, `mod`, zval, `result`,
self.message
)
return result
except:
print >>sys.stderr,'mtype=%s data=%s mod=%s value=%s' % (
`self.mtype`, `data`, `mod`, `value`
)
raise
def add(self,mt):
if not isinstance(mt,MagicTest) :
raise MagicTestError((mt,'incorrect subtest type %s'%(type(mt),)))
if mt.level == self.level+1 :
self.subtests.append( mt )
elif self.subtests :
self.subtests[-1].add( mt )
elif mt.level > self.level+1 :
# it's possible to get level 3 just after level 1 !!! :-(
level = self.level + 1
while level < mt.level :
xmt = MagicTest(None,'true','x','',line=self.line,level=level)
self.add( xmt )
level += 1
else:
self.add( mt ) # retry...
else:
raise MagicTestError((mt,'incorrect subtest level %s'%(`mt.level`,)))
def last_test(self):
return self.subtests[-1]
#end class MagicTest
class OffsetError(MagicError): pass
class Offset:
pos_format = {'b':'<B','B':'>B','s':'<H','S':'>H','l':'<I','L':'>I',}
pattern0 = re.compile(r''' # mere offset
^
&? # possible ampersand
( 0 # just zero
| [1-9]{1,1}[0-9]* # decimal
| 0[0-7]+ # octal
| 0x[0-9a-f]+ # hex
)
$
''', re.X|re.I
)
pattern1 = re.compile(r''' # indirect offset
^\(
(?P<base>&?0 # just zero
|&?[1-9]{1,1}[0-9]* # decimal
|&?0[0-7]* # octal
|&?0x[0-9A-F]+ # hex
)
(?P<type>
\. # this dot might be alone
[BSL]? # one of this chars in either case
)?
(?P<sign>
[-+]{0,1}
)?
(?P<off>0 # just zero
|[1-9]{1,1}[0-9]* # decimal
|0[0-7]* # octal
|0x[0-9a-f]+ # hex
)?
\)$''', re.X|re.I
)
def __init__(self,s):
self.source = s
self.value = None
self.relative = 0
self.base = self.type = self.sign = self.offs = None
m = Offset.pattern0.match( s )
if m : # just a number
if s[0] == '&' :
self.relative, self.value = 1, int( s[1:], 0 )
else:
self.value = int( s, 0 )
return
m = Offset.pattern1.match( s )
if m : # real indirect offset
try:
self.base = m.group('base')
if self.base[0] == '&' :
self.relative, self.base = 1, int( self.base[1:], 0 )
else:
self.base = int( self.base, 0 )
if m.group('type') : self.type = m.group('type')[1:]
self.sign = m.group('sign')
if m.group('off') : self.offs = int( m.group('off'), 0 )
if self.sign == '-' : self.offs = 0 - self.offs
except:
print >>sys.stderr, '$$', m.groupdict()
raise
return
raise OffsetError(`s`)
def __call__(self,file=None):
if self.value is not None : return self.value
pos = file.tell()
try:
if not self.relative : file.seek( self.offset, 0 )
frmt = Offset.pos_format.get( self.type, 'I' )
size = struct.calcsize( frmt )
data = struct.unpack( frmt, file.read( size ) )
if self.offs : data += self.offs
return data
finally:
file.seek( pos, 0 )
def __str__(self): return self.source
def __repr__(self): return 'Offset(%s)' % `self.source`
#end class Offset
class MagicFileError(MagicError): pass
class MagicFile:
def __init__(self,filename=_magic):
self.file = None
self.tests = []
self.total_tests = 0
self.load( filename )
self.ack_tests = None
self.nak_tests = None
def __del__(self):
self.close()
def load(self,filename=None):
self.open( filename )
self.parse()
self.close()
def open(self,filename=None):
self.close()
if filename is not None :
self.filename = filename
self.file = open( self.filename, 'r', BUFFER_SIZE )
def close(self):
if self.file :
self.file.close()
self.file = None
def parse(self):
line_no = 0
for line in self.file.xreadlines() :
line_no += 1
if not line or line[0]=='#' : continue
line = line.lstrip().rstrip('\r\n')
if not line or line[0]=='#' : continue
try:
x = self.parse_line( line )
if x is None :
print >>sys.stderr, '#[%04d]#'%line_no, line
continue
except:
print >>sys.stderr, '###[%04d]###'%line_no, line
raise
self.total_tests += 1
level, offset, mtype, test, message = x
new_test = MagicTest(offset,mtype,test,message,
line=line_no,level=level)
try:
if level == 0 :
self.tests.append( new_test )
else:
self.tests[-1].add( new_test )
except:
if 1 :
print >>sys.stderr, 'total tests=%s' % (
`self.total_tests`,
)
print >>sys.stderr, 'level=%s' % (
`level`,
)
print >>sys.stderr, 'tests=%s' % (
pprint.pformat(self.tests),
)
raise
else:
while self.tests[-1].level > 0 :
self.tests.pop()
def parse_line(self,line):
# print >>sys.stderr, 'line=[%s]' % line
if (not line) or line[0]=='#' : return None
level = 0
offset = mtype = test = message = ''
mask = None
# get optional level (count leading '>')
while line and line[0]=='>' :
line, level = line[1:], level+1
# get offset
while line and not line[0].isspace() :
offset, line = offset+line[0], line[1:]
try:
offset = Offset(offset)
except:
print >>sys.stderr, 'line=[%s]' % line
raise
# skip spaces
line = line.lstrip()
# get type
c = None
while line :
last_c, c, line = c, line[0], line[1:]
if last_c!='\\' and c.isspace() :
break # unescaped space - end of field
else:
mtype += c
if last_c == '\\' :
c = None # don't fuck my brain with sequential backslashes
# skip spaces
line = line.lstrip()
# get test
c = None
while line :
last_c, c, line = c, line[0], line[1:]
if last_c!='\\' and c.isspace() :
break # unescaped space - end of field
else:
test += c
if last_c == '\\' :
c = None # don't fuck my brain with sequential backslashes
# skip spaces
line = line.lstrip()
# get message
message = line
if mime and line.find("\t") != -1:
message=line[0:line.find("\t")]
#
# print '>>', level, offset, mtype, test, message
return level, offset, mtype, test, message
def detect(self,file):
self.ack_tests = 0
self.nak_tests = 0
answers = []
for test in self.tests :
message = test.run( file )
if message :
self.ack_tests += 1
answers.append( message )
else:
self.nak_tests += 1
if answers :
return '; '.join( answers )
#end class MagicFile
def username(uid):
try:
return pwd.getpwuid( uid )[0]
except:
return '#%s'%uid
def groupname(gid):
try:
return grp.getgrgid( gid )[0]
except:
return '#%s'%gid
def get_file_type(fname,follow):
t = None
if not follow :
try:
st = os.lstat( fname ) # stat that entry, don't follow links!
except os.error, why :
pass
else:
if stat.S_ISLNK(st[stat.ST_MODE]) :
t = 'symbolic link'
try:
lnk = os.readlink( fname )
except:
t += ' (unreadable)'
else:
t += ' to '+lnk
if t is None :
try:
st = os.stat( fname )
except os.error, why :
return "can't stat `%s' (%s)." % (why.filename,why.strerror)
dmaj, dmin = (st.st_rdev>>8)&0x0FF, st.st_rdev&0x0FF
if 0 : pass
elif stat.S_ISSOCK(st.st_mode) : t = 'socket'
elif stat.S_ISLNK (st.st_mode) : t = follow and 'symbolic link' or t
elif stat.S_ISREG (st.st_mode) : t = 'file'
elif stat.S_ISBLK (st.st_mode) : t = 'block special (%d/%d)'%(dmaj,dmin)
elif stat.S_ISDIR (st.st_mode) : t = 'directory'
elif stat.S_ISCHR (st.st_mode) : t = 'character special (%d/%d)'%(dmaj,dmin)
elif stat.S_ISFIFO(st.st_mode) : t = 'pipe'
else: t = '<unknown>'
if st.st_mode & stat.S_ISUID :
t = 'setuid(%d=%s) %s'%(st.st_uid,username(st.st_uid),t)
if st.st_mode & stat.S_ISGID :
t = 'setgid(%d=%s) %s'%(st.st_gid,groupname(st.st_gid),t)
if st.st_mode & stat.S_ISVTX :
t = 'sticky '+t
return t
HELP = '''%s [options] [files...]
Options:
-?, --help -- this help
-m, --magic=<file> -- use this magic <file> instead of %s
-f, --files=<namefile> -- read filenames for <namefile>
* -C, --compile -- write "compiled" magic file
-b, --brief -- don't prepend filenames to output lines
+ -c, --check -- check the magic file
-i, --mime -- output MIME types
* -k, --keep-going -- don't stop st the first match
-n, --flush -- flush stdout after each line
-v, --verson -- print version and exit
* -z, --compressed -- try to look inside compressed files
-L, --follow -- follow symlinks
-s, --special -- don't skip special files
* -- not implemented so far ;-)
+ -- implemented, but in another way...
'''
def main():
import getopt
global _magic
try:
brief = 0
flush = 0
follow= 0
mime = 0
check = 0
special=0
try:
opts, args = getopt.getopt(
sys.argv[1:],
'?m:f:CbciknvzLs',
( 'help',
'magic=',
'names=',
'compile',
'brief',
'check',
'mime',
'keep-going',
'flush',
'version',
'compressed',
'follow',
'special',
)
)
except getopt.error, why:
print >>sys.stderr, sys.argv[0], why
return 1
else:
files = None
for o,v in opts :
if o in ('-?','--help'):
print HELP % (
sys.argv[0],
_magic,
)
return 0
elif o in ('-f','--files='):
files = v
elif o in ('-m','--magic='):
_magic = v[:]
elif o in ('-C','--compile'):
pass
elif o in ('-b','--brief'):
brief = 1
elif o in ('-c','--check'):
check = 1
elif o in ('-i','--mime'):
mime = 1
if os.path.exists( _magic+'.mime' ) :
_magic += '.mime'
print >>sys.stderr,sys.argv[0]+':',\
"Using regular magic file `%s'" % _magic
elif o in ('-k','--keep-going'):
pass
elif o in ('-n','--flush'):
flush = 1
elif o in ('-v','--version'):
print 'VERSION'
return 0
elif o in ('-z','--compressed'):
pass
elif o in ('-L','--follow'):
follow = 1
elif o in ('-s','--special'):
special = 1
else:
if files :
files = map(lambda x: x.strip(), v.split(','))
if '-' in files and '-' in args :
error( 1, 'cannot use STDIN simultaneously for file list and data' )
for file in files :
for name in (
(file=='-')
and sys.stdin
or open(file,'r',BUFFER_SIZE)
).xreadlines():
name = name.strip()
if name not in args :
args.append( name )
try:
if check : print >>sys.stderr, 'Loading magic database...'
t0 = time.time()
m = MagicFile(_magic)
t1 = time.time()
if check :
print >>sys.stderr, \
m.total_tests, 'tests loaded', \
'for', '%.2f' % (t1-t0), 'seconds'
print >>sys.stderr, len(m.tests), 'tests at top level'
return 0 # XXX "shortened" form ;-)
mlen = max( map(len, args) )+1
for arg in args :
if not brief : print (arg + ':').ljust(mlen),
ftype = get_file_type( arg, follow )
if (special and ftype.find('special')>=0) \
or ftype[-4:] == 'file' :
t0 = time.time()
try:
t = m.detect( arg )
except (IOError,os.error), why:
t = "can't read `%s' (%s)" % (why.filename,why.strerror)
if ftype[-4:] == 'file' : t = ftype[:-4] + t
t1 = time.time()
print t and t or 'data'
if 0 : print \
'#\t%d tests ok, %d tests failed for %.2f seconds'%\
(m.ack_tests, m.nak_tests, t1-t0)
else:
print mime and 'application/x-not-regular-file' or ftype
if flush : sys.stdout.flush()
# print >>sys.stderr, 'DONE'
except:
if check : return 1
raise
else:
return 0
finally:
pass
if __name__ == '__main__' :
sys.exit( main() )
# vim:ai
# EOF #

View File

@@ -1,104 +0,0 @@
#! /usr/bin/env python2.2
# Copyright 1994 by Lance Ellinghouse
# Cathedral City, California Republic, United States of America.
# All Rights Reserved
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies and that
# both that copyright notice and this permission notice appear in
# supporting documentation, and that the name of Lance Ellinghouse
# not be used in advertising or publicity pertaining to distribution
# of the software without specific, written prior permission.
# LANCE ELLINGHOUSE DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
# FITNESS, IN NO EVENT SHALL LANCE ELLINGHOUSE CENTRUM BE LIABLE
# FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#
# Modified by Jack Jansen, CWI, July 1995:
# - Use binascii module to do the actual line-by-line conversion
# between ascii and binary. This results in a 1000-fold speedup. The C
# version is still 5 times faster, though.
# - Arguments more compliant with python standard
"""Implementation of the UUencode and UUdecode functions.
encode(in_file, out_file [,name, mode])
decode(in_file [, out_file, mode])
"""
import binascii
import os
import sys
from types import StringType
__all__ = ["Error", "decode"]
class Error(Exception):
pass
def decode(in_file, out_file=None, mode=None, quiet=0):
"""Decode uuencoded file"""
#
# Open the input file, if needed.
#
if in_file == '-':
in_file = sys.stdin
elif isinstance(in_file, StringType):
in_file = open(in_file)
#
# Read until a begin is encountered or we've exhausted the file
#
while 1:
hdr = in_file.readline()
if not hdr:
raise Error, 'No valid begin line found in input file'
if hdr[:5] != 'begin':
continue
hdrfields = hdr.split(" ", 2)
if len(hdrfields) == 3 and hdrfields[0] == 'begin':
try:
int(hdrfields[1], 8)
start_pos = in_file.tell() - len (hdr)
break
except ValueError:
pass
if out_file is None:
out_file = hdrfields[2].rstrip()
if os.path.exists(out_file):
raise Error, 'Cannot overwrite existing file: %s' % out_file
if mode is None:
mode = int(hdrfields[1], 8)
#
# Open the output file
#
if out_file == '-':
out_file = sys.stdout
elif isinstance(out_file, StringType):
fp = open(out_file, 'wb')
try:
os.path.chmod(out_file, mode)
except AttributeError:
pass
out_file = fp
#
# Main decoding loop
#
s = in_file.readline()
while s and s.strip() != 'end':
try:
data = binascii.a2b_uu(s)
except binascii.Error, v:
# Workaround for broken uuencoders by /Fredrik Lundh
nbytes = (((ord(s[0])-32) & 63) * 4 + 5) / 3
data = binascii.a2b_uu(s[:nbytes])
if not quiet:
sys.stderr.write("Warning: %s\n" % str(v))
out_file.write(data)
s = in_file.readline()
# if not s:
# raise Error, 'Truncated input file'
return (hdrfields[2].rstrip(), start_pos, in_file.tell())

View File

@@ -1,303 +0,0 @@
#!/usr/local/bin/python
# -*- mode: python -*-
"""
jb2bz.py - a nonce script to import bugs from JitterBug to Bugzilla
Written by Tom Emerson, tree@basistech.com
This script is provided in the hopes that it will be useful. No
rights reserved. No guarantees expressed or implied. Use at your own
risk. May be dangerous if swallowed. If it doesn't work for you, don't
blame me. It did what I needed it to do.
This code requires a recent version of Andy Dustman's MySQLdb interface,
http://sourceforge.net/projects/mysql-python
Share and enjoy.
"""
import rfc822, mimetools, multifile, mimetypes
import sys, re, glob, StringIO, os, stat, time
import MySQLdb, getopt
# mimetypes doesn't include everything we might encounter, yet.
if not mimetypes.types_map.has_key('.doc'):
mimetypes.types_map['.doc'] = 'application/msword'
if not mimetypes.encodings_map.has_key('.bz2'):
mimetypes.encodings_map['.bz2'] = "bzip2"
bug_status='NEW'
component="default"
version=""
product="" # this is required, the rest of these are defaulted as above
"""
Each bug in JitterBug is stored as a text file named by the bug number.
Additions to the bug are indicated by suffixes to this:
<bug>
<bug>.followup.*
<bug>.reply.*
<bug>.notes
The dates on the files represent the respective dates they were created/added.
All <bug>s and <bug>.reply.*s include RFC 822 mail headers. These could include
MIME file attachments as well that would need to be extracted.
There are other additions to the file names, such as
<bug>.notify
which are ignored.
Bugs in JitterBug are organized into directories. At Basis we used the following
naming conventions:
<product>-bugs Open bugs
<product>-requests Open Feature Requests
<product>-resolved Bugs/Features marked fixed by engineering, but not verified
<product>-verified Resolved defects that have been verified by QA
where <product> is either:
<product-name>
or
<product-name>-<version>
"""
def process_notes_file(current, fname):
try:
new_note = {}
notes = open(fname, "r")
s = os.fstat(notes.fileno())
new_note['text'] = notes.read()
new_note['timestamp'] = time.gmtime(s[stat.ST_MTIME])
notes.close()
current['notes'].append(new_note)
except IOError:
pass
def process_reply_file(current, fname):
new_note = {}
reply = open(fname, "r")
msg = rfc822.Message(reply)
new_note['text'] = "%s\n%s" % (msg['From'], msg.fp.read())
new_note['timestamp'] = rfc822.parsedate_tz(msg['Date'])
current["notes"].append(new_note)
def add_notes(current):
"""Add any notes that have been recorded for the current bug."""
process_notes_file(current, "%d.notes" % current['number'])
for f in glob.glob("%d.reply.*" % current['number']):
process_reply_file(current, f)
for f in glob.glob("%d.followup.*" % current['number']):
process_reply_file(current, f)
def maybe_add_attachment(current, file, submsg):
"""Adds the attachment to the current record"""
cd = submsg["Content-Disposition"]
m = re.search(r'filename="([^"]+)"', cd)
if m == None:
return
attachment_filename = m.group(1)
if (submsg.gettype() == 'application/octet-stream'):
# try get a more specific content-type for this attachment
type, encoding = mimetypes.guess_type(m.group(1))
if type == None:
type = submsg.gettype()
else:
type = submsg.gettype()
try:
data = StringIO.StringIO()
mimetools.decode(file, data, submsg.getencoding())
except:
return
current['attachments'].append( ( attachment_filename, type, data.getvalue() ) )
def process_mime_body(current, file, submsg):
data = StringIO.StringIO()
mimetools.decode(file, data, submsg.getencoding())
current['description'] = data.getvalue()
def process_text_plain(msg, current):
print "Processing: %d" % current['number']
current['description'] = msg.fp.read()
def process_multi_part(file, msg, current):
print "Processing: %d" % current['number']
mf = multifile.MultiFile(file)
mf.push(msg.getparam("boundary"))
while mf.next():
submsg = mimetools.Message(file)
if submsg.has_key("Content-Disposition"):
maybe_add_attachment(current, mf, submsg)
else:
# This is the message body itself (always?), so process
# accordingly
process_mime_body(current, mf, submsg)
def process_jitterbug(filename):
current = {}
current['number'] = int(filename)
current['notes'] = []
current['attachments'] = []
current['description'] = ''
current['date-reported'] = ()
current['short-description'] = ''
file = open(filename, "r")
msg = mimetools.Message(file)
msgtype = msg.gettype()
add_notes(current)
current['date-reported'] = rfc822.parsedate_tz(msg['Date'])
current['short-description'] = msg['Subject']
if msgtype[:5] == 'text/':
process_text_plain(msg, current)
elif msgtype[:10] == "multipart/":
process_multi_part(file, msg, current)
else:
# Huh? This should never happen.
print "Unknown content-type: %s" % msgtype
sys.exit(1)
# At this point we have processed the message: we have all of the notes and
# attachments stored, so it's time to add things to the database.
# The schema for JitterBug 2.14 can be found at:
#
# http://www.trilobyte.net/barnsons/html/dbschema.html
#
# The following fields need to be provided by the user:
#
# bug_status
# product
# version
# reporter
# component
# resolution
# change this to the user_id of the Bugzilla user who is blessed with the
# imported defects
reporter=6
# the resolution will need to be set manually
resolution=""
db = MySQLdb.connect(db='bugs',user='root',host='localhost')
cursor = db.cursor()
cursor.execute( "INSERT INTO bugs SET " \
"bug_id=%s," \
"bug_severity='normal'," \
"bug_status=%s," \
"creation_ts=%s," \
"short_desc=%s," \
"product=%s," \
"rep_platform='All'," \
"assigned_to=%s,"
"reporter=%s," \
"version=%s," \
"component=%s," \
"resolution=%s",
[ current['number'],
bug_status,
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
current['short-description'],
product,
reporter,
reporter,
version,
component,
resolution] )
# This is the initial long description associated with the bug report
cursor.execute( "INSERT INTO longdescs VALUES (%s,%s,%s,%s)",
[ current['number'],
reporter,
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
current['description'] ] )
# Add whatever notes are associated with this defect
for n in current['notes']:
cursor.execute( "INSERT INTO longdescs VALUES (%s,%s,%s,%s)",
[current['number'],
reporter,
time.strftime("%Y-%m-%d %H:%M:%S", n['timestamp'][:9]),
n['text']])
# add attachments associated with this defect
for a in current['attachments']:
cursor.execute( "INSERT INTO attachments SET " \
"bug_id=%s, creation_ts=%s, description='', mimetype=%s," \
"filename=%s, thedata=%s, submitter_id=%s",
[ current['number'],
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]),
a[1], a[0], a[2], reporter ])
cursor.close()
db.close()
def usage():
print """Usage: jb2bz.py [OPTIONS] Product
Where OPTIONS are one or more of the following:
-h This help information.
-s STATUS One of UNCONFIRMED, NEW, ASSIGNED, REOPENED, RESOLVED, VERIFIED, CLOSED
(default is NEW)
-c COMPONENT The component to attach to each bug as it is important. This should be
valid component for the Product.
-v VERSION Version to assign to these defects.
Product is the Product to assign these defects to.
All of the JitterBugs in the current directory are imported, including replies, notes,
attachments, and similar noise.
"""
sys.exit(1)
def main():
global bug_status, component, version, product
opts, args = getopt.getopt(sys.argv[1:], "hs:c:v:")
for o,a in opts:
if o == "-s":
if a in ('UNCONFIRMED','NEW','ASSIGNED','REOPENED','RESOLVED','VERIFIED','CLOSED'):
bug_status = a
elif o == '-c':
component = a
elif o == '-v':
version = a
elif o == '-h':
usage()
if len(args) != 1:
sys.stderr.write("Must specify the Product.\n")
sys.exit(1)
product = args[0]
for bug in filter(lambda x: re.match(r"\d+$", x), glob.glob("*")):
process_jitterbug(bug)
if __name__ == "__main__":
main()

View File

@@ -1,117 +0,0 @@
#!/usr/bin/perl -w
#
# 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) 2000 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Dan Mosedale <dmose@mozilla.org>
#
# mysqld-watcher.pl - a script that watches the running instance of
# mysqld and kills off any long-running SELECTs against the shadow_db
#
use strict;
# some configurables:
# length of time before a thread is eligible to be killed, in seconds
#
my $long_query_time = 180;
#
# the From header for any messages sent out
#
my $mail_from = "root\@mothra.mozilla.org";
#
# the To header for any messages sent out
#
my $mail_to = "root";
#
# mail transfer agent. this should probably really be converted to a Param().
#
my $mta_program = "/usr/lib/sendmail -t -ODeliveryMode=deferred";
# The array of long-running queries
#
my $long = {};
# Run mysqladmin processlist twice, the first time getting complete queries
# and the second time getting just abbreviated queries. We want complete
# queries so we know which queries are taking too long to run, but complete
# queries with line breaks get missed by this script, so we get abbreviated
# queries as well to make sure we don't miss any.
foreach my $command ("/opt/mysql/bin/mysqladmin --verbose processlist",
"/opt/mysql/bin/mysqladmin processlist")
{
close(STDIN);
open(STDIN, "$command |");
# iterate through the running threads
#
while ( <STDIN> ) {
my @F = split(/\|/);
# if this line is not the correct number of fields, or if the thread-id
# field contains Id, skip this line. both these cases indicate that this
# line contains pretty-printing gunk and not thread info.
#
next if ( $#F != 9 || $F[1] =~ /Id/);
if ( $F[4] =~ /shadow_bugs/ # shadowbugs database in use
&& $F[5] =~ /Query/ # this is actually a query
&& $F[6] > $long_query_time # this query has taken too long
&& $F[8] =~ /(select|SELECT)/ # only kill a select
&& !defined($long->{$F[1]}) ) # haven't seen this one already
{
$long->{$F[1]} = \@F;
system("/opt/mysql/bin/mysqladmin", "kill", $F[1]);
}
}
}
# send an email message
#
# should perhaps be moved to somewhere more global for use in bugzilla as a
# whole; should also do more error-checking
#
sub sendEmail($$$$) {
($#_ == 3) || die("sendEmail: invalid number of arguments");
my ($from, $to, $subject, $body) = @_;
open(MTA, "|$mta_program");
print MTA "From: $from\n";
print MTA "To: $to\n";
print MTA "Subject: $subject\n";
print MTA "\n";
print MTA $body;
print MTA "\n";
close(MTA);
}
# if we found anything, kill the database thread and send mail about it
#
if (scalar(keys(%$long))) {
my $message = "";
foreach my $process_id (keys(%$long)) {
my $qry = $long->{$process_id};
$message .= join(" ", @$qry) . "\n\n";
}
# fire off an email telling the maintainer that we had to kill some threads
#
sendEmail($mail_from, $mail_to, "long running MySQL thread(s) killed", $message);
}

View File

@@ -1,106 +0,0 @@
#!/usr/bin/perl -w
#
# sendbugmail.pl
#
# Nick Barnes, Ravenbrook Limited, 2004-04-01.
#
# $Id: sendbugmail.pl,v 1.1.2.1 2004-11-20 12:35:43 jocuri%softhome.net Exp $
#
# Bugzilla email script for Bugzilla 2.17.4 and later. Invoke this to send
# bugmail for a bug which has been changed directly in the database.
# This uses Bugzilla's own BugMail facility, and will email the
# users associated with the bug. Replaces the old "processmail"
# script.
#
# Usage: perl -T contrib/sendbugmail.pl bug_id user_email
use lib qw(.);
require "globals.pl";
use Bugzilla::BugMail;
sub usage {
print STDERR "Usage: $0 bug_id user_email\n";
exit;
}
if (($#ARGV < 1) || ($#ARGV > 2)) {
usage();
}
# Get the arguments.
my $bugnum = $ARGV[0];
my $changer = $ARGV[1];
# Validate the bug number.
if (!($bugnum =~ /^(\d+)$/)) {
print STDERR "Bug number \"$bugnum\" not numeric.\n";
usage();
}
detaint_natural($bugnum);
SendSQL("SELECT bug_id FROM bugs WHERE bug_id = $bugnum");
if (!FetchOneColumn()) {
print STDERR "Bug number $bugnum does not exist.\n";
usage();
}
# Validate the changer address.
my $match = Param('emailregexp');
if ($changer !~ /$match/) {
print STDERR "Changer \"$changer\" doesn't match email regular expression.\n";
usage();
}
if(!DBname_to_id($changer)) {
print STDERR "\"$changer\" is not a login ID.\n";
usage();
}
# Send the email.
my $outputref = Bugzilla::BugMail::Send($bugnum, {'changer' => $changer });
# Report the results.
my $sent = scalar(@{$outputref->{sent}});
my $excluded = scalar(@{$outputref->{excluded}});
if ($sent) {
print "email sent to $sent recipients:\n";
} else {
print "No email sent.\n";
}
foreach my $sent (@{$outputref->{sent}}) {
print " $sent\n";
}
if ($excluded) {
print "$excluded recipients excluded:\n";
} else {
print "No recipients excluded.\n";
}
foreach my $excluded (@{$outputref->{excluded}}) {
print " $excluded\n";
}
# This document is copyright (C) 2004 Perforce Software, Inc. All rights
# reserved.
#
# Redistribution and use of this document in any form, with or without
# modification, is permitted provided that redistributions of this
# document retain the above copyright notice, this condition and the
# following disclaimer.
#
# THIS DOCUMENT IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# DOCUMENT, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,51 +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): Dave Miller <justdave@bugzilla.org>
# Myk Melez <myk@mozilla.org>
use strict;
use lib qw(.);
require "CGI.pl";
use Bugzilla::Constants;
use Bugzilla::BugMail;
SendSQL("SELECT bug_id FROM bugs WHERE lastdiffed < delta_ts AND
delta_ts < date_sub(now(), INTERVAL 30 minute) ORDER BY bug_id");
my @list;
while (MoreSQLData()) {
push (@list, FetchOneColumn());
}
if (scalar(@list) > 0) {
print "OK, now attempting to send unsent mail\n";
print scalar(@list) . " bugs found with possibly unsent mail.\n\n";
foreach my $bugid (@list) {
my $start_time = time;
print "Sending mail for bug $bugid...\n";
my $outputref = Bugzilla::BugMail::Send($bugid);
my ($sent, $excluded) = (scalar(@{$outputref->{sent}}),scalar(@{$outputref->{excluded}}));
print "$sent mails sent, $excluded people excluded.\n";
print "Took " . (time - $start_time) . " seconds.\n\n";
}
print "Unsent mail has been sent.\n";
}

View File

@@ -1,285 +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 LDAP to Bugzilla User Sync Tool.
#
# The Initial Developer of the Original Code is Andreas Höfler.
# Portions created by Andreas Höfler are Copyright (C) 2003
# Andreas Höfler. All
# Rights Reserved.
#
# Contributor(s): Andreas Höfler <andreas.hoefler@bearingpoint.com>
#
use strict;
require "CGI.pl";
use lib qw(.);
use Net::LDAP;
my $cgi = Bugzilla->cgi;
my $readonly = 0;
my $nodisable = 0;
my $noupdate = 0;
my $nocreate = 0;
my $quiet = 0;
###
# Do some preparations
###
foreach my $arg (@ARGV)
{
if($arg eq '-r') {
$readonly = 1;
}
elsif($arg eq '-d') {
$nodisable = 1;
}
elsif($arg eq '-u') {
$noupdate = 1;
}
elsif($arg eq '-c') {
$nocreate = 1;
}
elsif($arg eq '-q') {
$quiet = 1;
}
else {
print "LDAP Sync Script\n";
print "Syncronizes the users table from the LDAP server with the Bugzilla users.\n";
print "Takes mail-attribute from preferences and description from 'cn' or,\n";
print "if not available, from the uid-attribute.\n\n";
print "usage:\n syncLDAP.pl [options]\n\n";
print "options:\n";
print " -r Readonly, do not make changes to Bugzilla tables\n";
print " -d No disable, don't disable users, which are not in LDAP\n";
print " -u No update, don't update users, which have different description in LDAP\n";
print " -c No create, don't create users, which are in LDAP but not in Bugzilla\n";
print " -q Quiet mode, give less output\n";
print "\n";
exit;
}
}
my %bugzilla_users;
my %ldap_users;
###
# Get current bugzilla users
###
SendSQL("SELECT login_name, realname, disabledtext " .
"FROM profiles" );
while (MoreSQLData()) {
my ($login_name, $realname, $disabledtext)
= FetchSQLData();
# remove whitespaces
$realname =~ s/^\s+|\s+$//g;
$bugzilla_users{$login_name} = { realname => $realname,
new_login_name => $login_name,
disabledtext => $disabledtext };
}
###
# Get current LDAP users
###
my $LDAPserver = Param("LDAPserver");
if ($LDAPserver eq "") {
print "No LDAP server defined in bugzilla preferences.\n";
exit;
}
my $LDAPport = "389"; # default LDAP port
if($LDAPserver =~ /:/) {
($LDAPserver, $LDAPport) = split(":",$LDAPserver);
}
my $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3);
if(!$LDAPconn) {
print "Connecting to LDAP server failed. Check LDAPserver setting.\n";
exit;
}
my $mesg;
if (Param("LDAPbinddn")) {
my ($LDAPbinddn,$LDAPbindpass) = split(":",Param("LDAPbinddn"));
$mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass);
}
else {
$mesg = $LDAPconn->bind();
}
if($mesg->code) {
print "Binding to LDAP server failed: " . $mesg->error . "\nCheck LDAPbinddn setting.\n";
exit;
}
# We've got our anonymous bind; let's look up the users.
$mesg = $LDAPconn->search( base => Param("LDAPBaseDN"),
scope => "sub",
filter => '(&(' . Param("LDAPuidattribute") . "=*)" . Param("LDAPfilter") . ')',
);
if(! $mesg->count) {
print "LDAP lookup failure. Check LDAPBaseDN setting.\n";
exit;
}
my $val = $mesg->as_struct;
while( my ($key, $value) = each(%$val) ) {
my $login_name = @$value{Param("LDAPmailattribute")};
my $realname = @$value{"cn"};
# no mail entered? go to next
if(! defined $login_name) {
print "$key has no valid mail address\n";
next;
}
# no cn entered? use uid instead
if(! defined $realname) {
$realname = @$value{Param("LDAPuidattribute")};
}
my $login = shift @$login_name;
my $real = shift @$realname;
$ldap_users{$login} = { realname => $real };
}
print "\n" unless $quiet;
###
# Sort the users into disable/update/create-Lists and display everything
###
my %disable_users;
my %update_users;
my %create_users;
print "Bugzilla-Users: \n" unless $quiet;
while( my ($key, $value) = each(%bugzilla_users) ) {
print " " . $key . " '" . @$value{'realname'} . "' " . @$value{'disabledtext'} ."\n" unless $quiet==1;
if(!exists $ldap_users{$key}){
if(@$value{'disabledtext'} eq '') {
$disable_users{$key} = $value;
}
}
}
print "\nLDAP-Users: \n" unless $quiet;
while( my ($key, $value) = each(%ldap_users) ) {
print " " . $key . " '" . @$value{'realname'} . "'\n" unless $quiet==1;
if(!exists $bugzilla_users{$key}){
$create_users{$key} = $value;
}
else {
my $bugzilla_user_value = $bugzilla_users{$key};
if(@$bugzilla_user_value{'realname'} ne @$value{'realname'}) {
$update_users{$key} = $value;
}
}
}
print "\nDetecting email changes: \n" unless $quiet;
while( my ($create_key, $create_value) = each(%create_users) ) {
while( my ($disable_key, $disable_value) = each(%disable_users) ) {
if(@$create_value{'realname'} eq @$disable_value{'realname'}) {
print " " . $disable_key . " => " . $create_key ."'\n" unless $quiet==1;
$update_users{$disable_key} = { realname => @$create_value{'realname'},
new_login_name => $create_key };
delete $create_users{$create_key};
delete $disable_users{$disable_key};
}
}
}
if($quiet == 0) {
print "\nUsers to disable: \n";
while( my ($key, $value) = each(%disable_users) ) {
print " " . $key . " '" . @$value{'realname'} . "'\n";
}
print "\nUsers to update: \n";
while( my ($key, $value) = each(%update_users) ) {
print " " . $key . " '" . @$value{'realname'} . "' ";
if(defined @$value{'new_login_name'}) {
print "has changed email to " . @$value{'new_login_name'};
}
print "\n";
}
print "\nUsers to create: \n";
while( my ($key, $value) = each(%create_users) ) {
print " " . $key . " '" . @$value{'realname'} . "'\n";
}
print "\n\n";
}
###
# now do the DB-Update
###
if($readonly == 0) {
print "Performing DB update:\nPhase 1: disabling not-existing users... " unless $quiet;
if($nodisable == 0) {
while( my ($key, $value) = each(%disable_users) ) {
SendSQL("UPDATE profiles SET disabledtext = 'auto-disabled by ldap sync' WHERE login_name='$key'" );
}
print "done!\n" unless $quiet;
}
else {
print "disabled!\n" unless $quiet;
}
print "Phase 2: updating existing users... " unless $quiet;
if($noupdate == 0) {
while( my ($key, $value) = each(%update_users) ) {
if(defined @$value{'new_login_name'}) {
SendSQL("UPDATE profiles SET login_name = '" . @$value{'new_login_name'} . "' WHERE login_name='$key'" );
} else {
SendSQL("UPDATE profiles SET realname = '" . @$value{'realname'} . "' WHERE login_name='$key'" );
}
}
print "done!\n" unless $quiet;
}
else {
print "disabled!\n" unless $quiet;
}
print "Phase 3: creating new users... " unless $quiet;
if($nocreate == 0) {
while( my ($key, $value) = each(%create_users) ) {
SendSQL("INSERT INTO profiles VALUES ('',
'$key',
'xxKFIy4WR66mA',
'" . @$value{'realname'} . "',
'',
1,
'ExcludeSelf~on~emailOwnerRemoveme~on~emailOwnerComments~on~emailOwnerAttachments~on~emailOwnerStatus~on~emailOwnerResolved~on~emailOwnerKeywords~on~emailOwnerCC~on~emailOwnerOther~on~emailOwnerUnconfirmed~on~emailReporterRemoveme~on~emailReporterComments~on~emailReporterAttachments~on~emailReporterStatus~on~emailReporterResolved~on~emailReporterKeywords~on~emailReporterCC~on~emailReporterOther~on~emailReporterUnconfirmed~on~emailQAcontactRemoveme~on~emailQAcontactComments~on~emailQAcontactAttachments~on~emailQAcontactStatus~on~emailQAcontactResolved~on~emailQAcontactKeywords~on~emailQAcontactCC~on~emailQAcontactOther~on~emailQAcontactUnconfirmed~on~emailCClistRemoveme~on~emailCClistComments~on~emailCClistAttachments~on~emailCClistStatus~on~emailCClistResolved~on~emailCClistKeywords~on~emailCClistCC~on~emailCClistOther~on~emailCClistUnconfirmed~on~emailVoterRemoveme~on~emailVoterComments~on~emailVoterAttachments~on~emailVoterStatus~on~emailVoterResolved~on~emailVoterKeywords~on~emailVoterCC~on~emailVoterOther~on~emailVoterUnconfirmed~on',
sysdate())");
}
print "done!\n" unless $quiet;
}
else {
print "disabled!\n" unless $quiet;
}
}
else
{
print "No changes to DB because readonly mode\n" unless $quiet;
}

View File

@@ -1,78 +0,0 @@
#!/bin/sh
# -*- Mode: ksh -*-
##############################################################################
# $Id: yp_nomail.sh,v 1.1 2000-09-12 23:50:31 cyeh%bluemartini.com Exp $
# yp_nomail
#
# Our mail admins got annoyed when bugzilla kept sending email
# to people who'd had bugzilla entries and left the company. They
# were no longer in the list of valid email users so it'd bounce.
# Maintaining the 'data/nomail' file was a pain. Luckily, our UNIX
# admins list all the users that ever were, but the people who've left
# have a distinct marker in their password file. For example:
#
# fired:*LK*:2053:1010:You're Fired Dude:/home/loser:/bin/false
#
# This script takes advantage of the "*LK*" convention seen via
# ypcat passwd and dumps those people into the nomail file. Any
# manual additions are kept in a "nomail.(domainname)" file and
# appended to the list of yp lockouts every night via Cron
#
# 58 23 * * * /export/bugzilla/contrib/yp_nomail.sh > /dev/null 2>&1
#
# Tak ( Mark Takacs ) 08/2000
#
# XXX: Maybe should crosscheck w/bugzilla users?
##############################################################################
####
# Configure this section to suite yer installation
####
DOMAIN=`domainname`
MOZILLA_HOME="/export/mozilla"
BUGZILLA_HOME="${MOZILLA_HOME}/bugzilla"
NOMAIL_DIR="${BUGZILLA_HOME}/data"
NOMAIL="${NOMAIL_DIR}/nomail"
NOMAIL_ETIME="${NOMAIL}.${DOMAIN}"
NOMAIL_YP="${NOMAIL}.yp"
FIRED_FLAG="\*LK\*"
YPCAT="/usr/bin/ypcat"
GREP="/usr/bin/grep"
SORT="/usr/bin/sort"
########################## no more config needed #################
# This dir comes w/Bugzilla. WAY too paranoid
if [ ! -d ${NOMAIL_DIR} ] ; then
echo "Creating $date_dir"
mkdir -p ${NOMAIL_DIR}
fi
#
# Do some (more) paranoid checking
#
touch ${NOMAIL}
if [ ! -w ${NOMAIL} ] ; then
echo "Can't write nomail file: ${NOMAIL} -- exiting"
exit
fi
if [ ! -r ${NOMAIL_ETIME} ] ; then
echo "Can't access custom nomail file: ${NOMAIL_ETIME} -- skipping"
NOMAIL_ETIME=""
fi
#
# add all the people with '*LK*' password to the nomail list
# XXX: maybe I should customize the *LK* string. Doh.
#
LOCKOUT=`$YPCAT passwd | $GREP "${FIRED_FLAG}" | cut -d: -f1 | sort > ${NOMAIL_YP}`
`cat ${NOMAIL_YP} ${NOMAIL_ETIME} > ${NOMAIL}`
exit
# end

View File

@@ -1,87 +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>
# David Gardiner <david.gardiner@unisa.edu.au>
# Joe Robins <jmrobins@tgix.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
use strict;
use lib qw(.);
require "CGI.pl";
# Shut up misguided -w warnings about "used only once":
use vars qw(
$template
$vars
);
# If we're using LDAP for login, then we can't create a new account here.
unless (Bugzilla::Auth->can_edit) {
# Just in case someone already has an account, let them get the correct
# footer on the error message
Bugzilla->login();
ThrowUserError("auth_cant_create_account");
}
# Clear out the login cookies. Make people log in again if they create an
# account; otherwise, they'll probably get confused.
Bugzilla->logout();
my $cgi = Bugzilla->cgi;
print $cgi->header();
my $login = $cgi->param('login');
if (defined($login)) {
# We've been asked to create an account.
my $realname = trim($cgi->param('realname'));
CheckEmailSyntax($login);
$vars->{'login'} = $login;
if (!ValidateNewUser($login)) {
# Account already exists
$template->process("account/exists.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
my $createexp = Param('createemailregexp');
if (!($createexp)
|| ($login !~ /$createexp/)) {
ThrowUserError("account_creation_disabled");
exit;
}
# Create account
my $password = InsertNewUser($login, $realname);
MailPassword($login, $password);
$template->process("account/created.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
# Show the standard "would you like to create an account?" form.
$template->process("account/create.html.tmpl", $vars)
|| ThrowTemplateError($template->error());

View File

@@ -1,54 +0,0 @@
/* 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>
*/
.bz_id_column {
}
/* Style bug rows according to severity. */
.bz_blocker { color: red; font-weight: bold; }
.bz_critical { color: red; }
.bz_enhancement { color: #888; background-color: white; }
/* Align columns in the "change multiple bugs" form to the right. */
table#form tr th { text-align: right; }
table.bz_buglist td, table.bz_buglist th {
}
/* For styling rows; by default striped white and gray */
tr.bz_odd {
background-color: #F7F7F7;}
tr.bz_even {
}
/* we use a first-child class and not the pseudo-class because IE
* doesn't support it :-( */
tr.bz_secure td.first-child {
background-image: url("../padlock.png");
background-position: center left;
background-repeat: no-repeat;
background-color: inherit;
}
th.first-child, td.first-child {
padding-left: 20px;
}

View File

@@ -1,34 +0,0 @@
/* 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>
*/
tree#results-tree {
margin-right: 0px;
border-right-width: 0px;
margin-left: 0px;
border-left-width: 0px;
}
treechildren:-moz-tree-cell-text(resolution-FIXED) {
text-decoration: line-through;
}
treecol#id_column { width: 6em; }
treecol#duplicate_count_column { width: 5em; }
treecol#duplicate_delta_column { width: 5em; }

View File

@@ -1,175 +0,0 @@
/* 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): Byron Jones <bugzilla@glob.com.au>
* Christian Reis <kiko@async.com.br>
* Vitaly Harisov <vitaly@rathedg.com>
* Svetlana Harisova <light@rathedg.com>
*/
/* banner (begin) */
#banner
{
text-align: center;
width: 100%;
background: #000;
/* for Netscape 4 only */
border: 1px solid #fff;
border-bottom-width: 0.65em;
}
/* hide from NN4 */
div#banner
{
border-bottom-width: 0.2em;
}
#banner p
{
margin: 0;
padding: 0;
}
#banner-name
{
font-family: serif;
font-size: 255%;
color: #fff;
}
/* hide from NN4 */
p#banner-name
{
font-size: 300%;
}
#banner-version
{
font-size: 75%;
background: #fff;
}
/* hide from NN4 */
p#banner-version
{
font-size: 85%;
}
/* banner (end) */
/* footer (begin) */
#footer
{
float: left;
}
#footer form
{
background: #FFFFE0;
border: 1px solid #000;
}
#footer span
{
display: block;
}
#footer .btn, #footer .txt
{
font-size: 0.8em;
}
#footer #useful-links
{
padding: 0.3em;
}
/* hide from MSIE and NN4 */
[id]#footer #useful-links
{
margin: 0.3em;
display: table;
}
#footer #links-actions,
#footer #links-edit,
#footer #links-saved
{
display: table-row;
}
#footer .label
{
width: 7.2em;
display: block;
float: left;
vertical-align: baseline;
padding: 0.1em 0.2em;
}
#footer #links-actions .label
{
padding-top: 0.45em;
}
/* hide from MSIE and NN4 */
[id]#footer .label
{
display: table-cell;
float: none;
width: auto;
padding-top: 0;
}
#footer .links
{
display: block;
padding: 0.1em 0.2em;
}
/* hide from MSIE and NN4 */
[id]#footer .links
{
display: table-cell;
padding-top: 0;
vertical-align: baseline;
}
/* hide from MSIE and NN4 */
#footer+*
{
clear: both;
}
/* footer (end) */
.bz_obsolete { text-decoration: line-through; }
.bz_inactive { text-decoration: line-through; }
.bz_closed { text-decoration: line-through; }
.bz_private { color: darkred ; background : #f3eeee ; }
.bz_disabled { color: #a0a0a0 ; }
.bz_comment { background-color: #e0e0e0; }
table#flags th, table#flags td { vertical-align: baseline; text-align: left; }

View File

@@ -1,37 +0,0 @@
body
{
font-family: sans-serif;
font-size: 10pt;
background-color: white;
}
ul
{
padding-left: 12px;
}
radio
{
-moz-user-select: ignore;
}
.text-link
{
margin-left: 3px;
}
.text-link:hover
{
text-decoration: underline;
cursor: pointer;
}
.descriptive-content
{
color: #AAAAAA;
}
.descriptive-content[focused=true]
{
color: black;
}

View File

@@ -1 +0,0 @@
.bz_private { color:darkred }

File diff suppressed because it is too large Load Diff

View File

@@ -1,111 +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>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
use strict;
use lib qw(.);
use Bugzilla;
use Bugzilla::Constants;
require "CGI.pl";
use vars qw($vars @legal_product);
Bugzilla->login();
GetVersionTable();
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
my $product = trim($cgi->param('product') || '');
my $product_id = get_product_id($product);
if (!$product_id || !CanEnterProduct($product)) {
# Reference to a subset of %::proddesc, which the user is allowed to see
my %products;
if (AnyEntryGroups()) {
# OK, now only add products the user can see
Bugzilla->login(LOGIN_REQUIRED) unless Bugzilla->user;
foreach my $p (@::legal_product) {
if (CanEnterProduct($p)) {
$products{$p} = $::proddesc{$p};
}
}
}
else {
%products = %::proddesc;
}
my $prodsize = scalar(keys %products);
if ($prodsize == 0) {
ThrowUserError("no_products");
}
elsif ($prodsize > 1) {
$vars->{'proddesc'} = \%products;
$vars->{'target'} = "describecomponents.cgi";
# If an invalid product name is given, or the user is not
# allowed to access that product, a message is displayed
# with a list of the products the user can choose from.
if ($product) {
$vars->{'message'} = "product_invalid";
$vars->{'product'} = $product;
}
print $cgi->header();
$template->process("global/choose-product.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
}
$product = (keys %products)[0];
}
######################################################################
# End Data/Security Validation
######################################################################
my @components;
SendSQL("SELECT name, initialowner, initialqacontact, description FROM " .
"components WHERE product_id = $product_id ORDER BY name");
while (MoreSQLData()) {
my ($name, $initialowner, $initialqacontact, $description) =
FetchSQLData();
my %component;
$component{'name'} = $name;
$component{'initialowner'} = $initialowner ?
DBID_to_name($initialowner) : '';
$component{'initialqacontact'} = $initialqacontact ?
DBID_to_name($initialqacontact) : '';
$component{'description'} = $description;
push @components, \%component;
}
$vars->{'product'} = $product;
$vars->{'components'} = \@components;
print $cgi->header();
$template->process("reports/components.html.tmpl", $vars)
|| ThrowTemplateError($template->error());

View File

@@ -1,59 +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 Terry Weissman.
# Portions created by Terry Weissman are
# Copyright (C) 2000 Terry Weissman. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Contributor(s): Gervase Markham <gerv@gerv.net>
use strict;
use lib ".";
use Bugzilla;
require "CGI.pl";
# Use the global template variables.
use vars qw($vars $template);
Bugzilla->login();
my $cgi = Bugzilla->cgi;
SendSQL("SELECT keyworddefs.name, keyworddefs.description,
COUNT(keywords.bug_id)
FROM keyworddefs LEFT JOIN keywords ON keyworddefs.id=keywords.keywordid
GROUP BY keyworddefs.id
ORDER BY keyworddefs.name");
my @keywords;
while (MoreSQLData()) {
my ($name, $description, $bugs) = FetchSQLData();
push (@keywords, { name => $name,
description => $description,
bugcount => $bugs });
}
$vars->{'keywords'} = \@keywords;
$vars->{'caneditkeywords'} = UserInGroup("editkeywords");
print Bugzilla->cgi->header();
$template->process("reports/keywords.html.tmpl", $vars)
|| ThrowTemplateError($template->error());

View File

@@ -1,155 +0,0 @@
Welcome to the Bugzilla documentation project!
You'll find these directories and files here:
README.docs # This README file
html/ # The compiled HTML docs from XML sources (do not edit)
txt/ # The compiled text docs from XML sources (do not edit)
xml/ # The original XML doc sources (edit these)
A note about the XML:
The documentation is written in DocBook 4.1.2, and attempts to adhere
to the LinuxDoc standards where applicable (http://www.tldp.org).
Please consult "The LDP Author Guide" at tldp.org for details on how
to set up your personal environment for compiling XML files.
If you need to make corrections to typographical errors, or other minor
editing duties, feel free to use any text editor to make the changes. XML
is not rocket science -- simply make sure your text appears between
appropriate tags (like <para>This is a paragraph</para>) and we'll be fine.
If you are making more extensive changes, please ensure you at least validate
your XML before checking it in with something like:
nsgmls -s $JADE_PUB/xml.dcl Bugzilla-Guide.xml
When you validate, please validate the master document (Bugzilla-Guide.xml)
as well as the document you edited to ensure there are no critical errors.
The following errors are considered "normal" when validating with nsgmls:
DTDDECL catalog entries are not supported
"DOCTYPE" declaration not allowed in instance
The reason these occur is that free sgml/xml validators do not yet support
the DTDDECL catalog entries, and I've included DOCTYPE declarations in
entities referenced from Bugzilla-Guide.xml so these entities can compile
individually, if necessary. I suppose I ought to comment them out at some
point, but for now they are convenient and don't hurt anything.
Thanks for taking the time to read these notes and consulting the
documentation. Please address comments and questions to the newsgroup:
news://news.mozilla.org/netscape/public/mozilla/webtools .
==========
HOW TO SET UP YOUR OWN XML EDITING ENVIRONMENT:
==========
Trying to set up an XML Docbook editing environment the
first time can be a daunting task.
I use Linux-Mandrake, in part, because it has a fully-functional
XML Docbook editing environment included as part of the
distribution CD's. If you have easier instructions for how to
do this for a particular Linux distribution or platform, please
let the team know at the mailing list: mozilla-webtools@mozilla.org.
The following text is taken nearly verbatim from
http://bugzilla.mozilla.org/show_bug.cgi?id=95970, where I gave
these instructions to someone who wanted the greater manageability
maintaining a document in Docbook brings:
This is just off the top of my head, but here goes. Note some of these may
NOT be necessary, but I don't think they hurt anything by being installed.
rpms:
openjade
jadetex
docbook-dtds
docbook-style-dsssl
docbook-style-dsssl-doc
docbook-utils
xemacs
psgml
sgml-tools
sgml-common
If you're getting these from RedHat, make sure you get the ones in the
rawhide area. The ones in the 7.2 distribution are too old and don't
include the XML stuff. The packages distrubuted with RedHat 8.0 and 9
and known to work.
Download "ldp.dsl" from the Resources page on tldp.org. This is the
stylesheet I use to get the HTML and text output. It works well, and has a
nice, consistent look with the rest of the linuxdoc documents. You'll have to
adjust the paths in ldp.dsl at the top of the file to reflect the actual
locations of your docbook catalog files. I created a directory,
/usr/share/sgml/docbook/ldp, and put the ldp.dsl file there. I then edited
ldp.dsl and changed two lines near the top:
<!ENTITY docbook.dsl SYSTEM "../dsssl-stylesheets/html/docbook.dsl" CDATA
dsssl>
...and...
<!ENTITY docbook.dsl SYSTEM "../dsssl-stylesheets/print/docbook.dsl" CDATA
dsssl>
Note the difference is the top one points to the HTML docbook stylesheet,
and the next one points to the PRINT docbook stylesheet.
Also note that modifying ldp.dsl doesn't seem to be needed on RedHat 9.
You know, this sure looks awful involved. Anyway, once you have this in
place, add to your .bashrc:
export SGML_CATALOG_FILES=/etc/sgml/catalog
export LDP_HOME=/usr/share/sgml/docbook/ldp
export JADE_PUB=/usr/share/doc/openjade-1.3.1/pubtext
or in .tcshrc:
setenv SGML_CATALOG_FILES /etc/sgml/catalog
setenv LDP_HOME /usr/share/sgml/docbook/ldp
setenv JADE_PUB /usr/share/doc/openjade-1.3.1/pubtext
If you have root access and want to set this up for anyone on your box,
you can add those lines to /etc/profile for bash users and /etc/csh.login
for tcsh users.
Make sure you edit the paths in the above environment variables if those
folders are anywhere else on your system (for example, the openjade version
might change if you get a new version at some point).
I suggest xemacs for editing your XML Docbook documents. The darn
thing just works, and generally includes PSGML mode by default. Not to
mention you can validate the SGML from right within it without having to
remember the command-line syntax for nsgml (not that it's that hard
anyway). If not, you can download psgml at
http://www.sourceforge.net/projects/psgml.
Another good editor is the latest releases of vim and gvim. Vim will
recognize DocBook tags and give them a different color than unreconized tags.
==========
NOTES:
==========
Here are the commands I use to maintain this documentation.
You MUST have DocBook 4.1.2 set up correctly in order for this to work.
These commands can be run all at once using the ./makedocs.pl script.
To create HTML documentation:
bash$ cd html
bash$ jade -t sgml -i html -d $LDP_HOME/ldp.dsl\#html \
$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml
To create HTML documentation as a single big HTML file:
bash$ cd html
bash$ jade -V nochunks -t sgml -i html -d $LDP_HOME/ldp.dsl\#html \
$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml >Bugzilla-Guide.html
To create TXT documentation as a single big TXT file:
bash$ cd txt
bash$ lynx -dump -nolist ../html/Bugzilla-Guide.html >Bugzilla-Guide.txt
Sincerely,
Matthew P. Barnson
The Bugzilla "Doc Knight"
mbarnson@sisna.com
with major edits by Dave Miller <justdave@syndicomm.com> based on
experience setting this up on the Landfill test server.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 B

View File

@@ -1,107 +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 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): Matthew Tuck <matty@chariot.net.au>
# Jacob Steenhagen <jake@bugzilla.org>
# This script compiles all the documentation.
use diagnostics;
use strict;
use File::Basename;
###############################################################################
# Environment Variable Checking
###############################################################################
my ($JADE_PUB, $LDP_HOME);
if (defined $ENV{JADE_PUB} && $ENV{JADE_PUB} ne '') {
$JADE_PUB = $ENV{JADE_PUB};
}
else {
die "You need to set the JADE_PUB environment variable first.";
}
if (defined $ENV{LDP_HOME} && $ENV{LDP_HOME} ne '') {
$LDP_HOME = $ENV{LDP_HOME};
}
else {
die "You need to set the LDP_HOME environment variable first.";
}
###############################################################################
# Subs
###############################################################################
sub MakeDocs($$) {
my ($name, $cmdline) = @_;
print "Creating $name documentation ...\n" if defined $name;
print "$cmdline\n\n";
system $cmdline;
print "\n";
}
###############################################################################
# Make the docs ...
###############################################################################
chdir dirname($0);
if (!-d 'html') {
unlink 'html';
mkdir 'html', 0755;
}
if (!-d 'txt') {
unlink 'txt';
mkdir 'txt', 0755;
}
if (!-d 'pdf') {
unlink 'pdf';
mkdir 'pdf', 0755;
}
chdir 'html';
MakeDocs('separate HTML', "jade -t sgml -i html -d $LDP_HOME/ldp.dsl\#html " .
"$JADE_PUB/xml.dcl ../xml/Bugzilla-Guide.xml");
MakeDocs('big HTML', "jade -V nochunks -t sgml -i html -d " .
"$LDP_HOME/ldp.dsl\#html $JADE_PUB/xml.dcl " .
"../xml/Bugzilla-Guide.xml > Bugzilla-Guide.html");
MakeDocs('big text', "lynx -dump -justify=off -nolist Bugzilla-Guide.html " .
"> ../txt/Bugzilla-Guide.txt");
if (! grep("--with-pdf", @ARGV)) {
exit;
}
MakeDocs('PDF', "jade -t tex -d $LDP_HOME/ldp.dsl\#print $JADE_PUB/xml.dcl " .
'../xml/Bugzilla-Guide.xml');
chdir '../pdf';
MakeDocs(undef, 'mv ../xml/Bugzilla-Guide.tex .');
MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
MakeDocs(undef, 'pdfjadetex Bugzilla-Guide.tex');
MakeDocs(undef, 'rm Bugzilla-Guide.tex Bugzilla-Guide.log Bugzilla-Guide.aux Bugzilla-Guide.out');

File diff suppressed because it is too large Load Diff

View File

@@ -1,191 +0,0 @@
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!-- Include macros -->
<!ENTITY about SYSTEM "about.xml">
<!ENTITY conventions SYSTEM "conventions.xml">
<!ENTITY doc-index SYSTEM "index.xml">
<!ENTITY faq SYSTEM "faq.xml">
<!ENTITY gfdl SYSTEM "gfdl.xml">
<!ENTITY glossary SYSTEM "glossary.xml">
<!ENTITY installation SYSTEM "installation.xml">
<!ENTITY administration SYSTEM "administration.xml">
<!ENTITY security SYSTEM "security.xml">
<!ENTITY using SYSTEM "using.xml">
<!ENTITY integration SYSTEM "integration.xml">
<!ENTITY index SYSTEM "index.xml">
<!ENTITY customization SYSTEM "customization.xml">
<!ENTITY troubleshooting SYSTEM "troubleshooting.xml">
<!ENTITY patches SYSTEM "patches.xml">
<!ENTITY introduction SYSTEM "introduction.xml">
<!ENTITY modules SYSTEM "modules.xml">
<!-- Things to change for a stable release:
* bz-ver to current stable
* bz-nexver to next stable
* bz-date to the release date
-->
<!ENTITY bz-ver "2.18">
<!ENTITY bz-nextver "2.18.1">
<!ENTITY bz-date "2005-01-14">
<!ENTITY current-year "2005">
<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-2.18-branch/">
<!ENTITY bz "http://www.bugzilla.org/">
<!ENTITY bzg-bugs "<ulink url='http://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&amp;component=Documentation'>Bugzilla Documentation</ulink>">
<!ENTITY mysql "http://www.mysql.com/">
<!ENTITY newest-perl-ver "5.8.3">
<!-- For minimum versions -->
<!ENTITY min-mysql-ver "3.23.41">
<!ENTITY min-perl-ver "5.6.0">
<!ENTITY min-perl-ver-win "5.8.1">
<!ENTITY min-template-ver "2.08">
<!ENTITY min-file-temp-ver "any">
<!ENTITY min-appconfig-ver "1.52">
<!ENTITY min-text-wrap-ver "2001.0131">
<!ENTITY min-file-spec-ver "0.82">
<!ENTITY min-data-dumper-ver "any">
<!ENTITY min-dbd-mysql-ver "2.1010">
<!ENTITY min-dbi-ver "1.36">
<!ENTITY min-date-format-ver "2.21">
<!ENTITY min-cgi-ver "2.93">
<!-- Optional modules -->
<!ENTITY min-gd-ver "1.20">
<!ENTITY min-gd-graph-ver "any">
<!ENTITY min-gd-text-align-ver "any">
<!ENTITY min-chart-base-ver "1.0">
<!ENTITY min-xml-parser-ver "any">
<!ENTITY min-mime-parser-ver "any">
<!ENTITY min-patchreader-ver "0.9.4">
]>
<!-- Coding standards for this document
* Other than the GFDL, please use the "section" tag instead of "sect1",
"sect2", etc.
* Use Entities to include files for new chapters in Bugzilla-Guide.xml.
* Try to use Entities for frequently-used passages of text as well.
* Ensure all documents compile cleanly to HTML after modification.
The warning, "DTDDECL catalog types not supported" is normal.
* Try to index important terms wherever possible.
* Use "glossterm" whenever you introduce a new term.
* Follow coding standards at http://www.tldp.org, and
check out the KDE guidelines (they are nice, too)
http://i18n.kde.org/doc/markup.html
* All tags should be lowercase.
* Please use sensible spacing. The comments at the very end of each
file define reasonable defaults for PSGML mode in EMACS.
* Double-indent tags, use double spacing whenever possible, and
try to avoid clutter and feel free to waste space in the code to make it
more readable.
-->
<book id="index">
<!-- Header -->
<bookinfo>
<title>The Bugzilla Guide - &bz-ver; Release</title>
<authorgroup>
<corpauthor>The Bugzilla Team</corpauthor>
</authorgroup>
<pubdate>&bz-date;</pubdate>
<abstract>
<para>
This is the documentation for Bugzilla, a
bug-tracking system from mozilla.org.
Bugzilla is an enterprise-class piece of software
that tracks millions of bugs and issues for hundreds of
organizations around the world.
</para>
<para>
The most current version of this document can always be found on the
<ulink url="http://www.bugzilla.org/documentation.html">Bugzilla
Documentation Page</ulink>.
</para>
</abstract>
<keywordset>
<keyword>Bugzilla</keyword>
<keyword>Guide</keyword>
<keyword>installation</keyword>
<keyword>FAQ</keyword>
<keyword>administration</keyword>
<keyword>integration</keyword>
<keyword>MySQL</keyword>
<keyword>Mozilla</keyword>
<keyword>webtools</keyword>
</keywordset>
</bookinfo>
<!-- About This Guide -->
&about;
<!-- Installing Bugzilla -->
&installation;
<!-- Administering Bugzilla -->
&administration;
<!-- Securing Bugzilla -->
&security;
<!-- Customizing Bugzilla -->
&customization;
<!-- Using Bugzilla -->
&using;
<!-- Appendix: The Frequently Asked Questions -->
&faq;
<!-- Appendix: Troubleshooting -->
&troubleshooting;
<!-- Appendix: Custom Patches -->
&patches;
<!-- Appendix: Manually Installing Perl Modules -->
&modules;
<!-- Appendix: GNU Free Documentation License -->
&gfdl;
<!-- Glossary -->
&glossary;
<!-- Index -->
&index;
</book>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-always-quote-attributes:t
sgml-auto-insert-required-elements:t
sgml-balanced-tag-edit:t
sgml-exposed-tags:nil
sgml-general-insert-case:lower
sgml-indent-data:t
sgml-indent-step:2
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
sgml-minimize-attributes:nil
sgml-namecase-general:t
sgml-omittag:t
sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
sgml-shorttag:t
sgml-tag-region-if-active:t
End:
-->

View File

@@ -1,233 +0,0 @@
<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
<!ENTITY conventions SYSTEM "conventions.xml"> ] > -->
<!-- $Id: about.xml,v 1.16.2.3 2004-12-21 20:43:12 jake%bugzilla.org Exp $ -->
<chapter id="about">
<title>About This Guide</title>
<section id="copyright">
<title>Copyright Information</title>
<para>This document is copyright (c) 2000-&current-year; by the various
Bugzilla contributors who wrote it.</para>
<blockquote>
<para>
Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation
License, Version 1.1 or any later version published by the
Free Software Foundation; with no Invariant Sections, no
Front-Cover Texts, and with no Back-Cover Texts. A copy of
the license is included in <xref linkend="gfdl"/>.
</para>
</blockquote>
<para>
If you have any questions regarding this document, its
copyright, or publishing this document in non-electronic form,
please contact the Bugzilla Team.
</para>
</section>
<section id="disclaimer">
<title>Disclaimer</title>
<para>
No liability for the contents of this document can be accepted.
Follow the instructions herein at your own risk.
This document may contain errors
and inaccuracies that may damage your system, cause your partner
to leave you, your boss to fire you, your cats to
pee on your furniture and clothing, and global thermonuclear
war. Proceed with caution.
</para>
<para>
Naming of particular products or brands should not be seen as
endorsements, with the exception of the term "GNU/Linux". We
wholeheartedly endorse the use of GNU/Linux; it is an extremely
versatile, stable,
and robust operating system that offers an ideal operating
environment for Bugzilla.
</para>
<para>
Although the Bugzilla development team has taken great care to
ensure that all exploitable bugs have been fixed, security holes surely
exist in any piece of code. Great care should be taken both in
the installation and usage of this software. The Bugzilla development
team members assume no liability for your use of Bugzilla. You have
the source code, and are responsible for auditing it yourself to ensure
your security needs are met.
</para>
</section>
<!-- Section 2: New Versions -->
<section id="newversions">
<title>New Versions</title>
<para>
This is the &bz-ver; version of The Bugzilla Guide. It is so named
to match the current version of Bugzilla.
</para>
<para>
The latest version of this guide can always be found at <ulink
url="http://www.bugzilla.org"/>, or checked out via CVS by
following the <ulink url="http://www.mozilla.org/cvs.html">Mozilla
CVS</ulink> instructions and check out the
<filename>mozilla/webtools/bugzilla/docs/</filename>
subtree. However, you should read the version
which came with the Bugzilla release you are using.
</para>
<para>
The Bugzilla Guide, or a section of it, is also available in
the following languages:
<ulink url="http://bugzilla-de.sourceforge.net/docs/html/">German</ulink>.
</para>
<para>
In addition, there are Bugzilla template localisation projects in
the following languages. They may have translated documentation
available:
<ulink url="http://sourceforge.net/projects/bugzilla-be/">Belarusian</ulink>,
<ulink url="http://sourceforge.net/projects/bugzilla-br/">Brazilian Portuguese</ulink>,
<ulink url="http://sourceforge.net/projects/bugzilla-cn/">Chinese</ulink>,
<ulink url="http://sourceforge.net/projects/bugzilla-fr/">French</ulink>,
<ulink url="http://sourceforge.net/projects/bugzilla-de/">German</ulink>,
<ulink url="http://sourceforge.net/projects/bugzilla-kr/">Korean</ulink>,
<ulink url="http://sourceforge.net/projects/bugzilla-ru/">Russian</ulink> and
<ulink url="http://sourceforge.net/projects/bugzilla-es/">Spanish</ulink>.
</para>
<para>
If you would like to volunteer to translate the Guide into additional
languages, please contact
<ulink url="mailto:justdave@syndicomm.com">Dave Miller</ulink>.
</para>
</section>
<section id="credits">
<title>Credits</title>
<para>
The people listed below have made enormous contributions to the
creation of this Guide, through their writing, dedicated hacking efforts,
numerous e-mail and IRC support sessions, and overall excellent
contribution to the Bugzilla community:
</para>
<!-- TODO: This is evil... there has to be a valid way to get this look -->
<variablelist>
<varlistentry>
<term>Matthew P. Barnson <email>mbarnson@sisna.com</email></term>
<listitem>
<para>for the Herculaean task of pulling together the Bugzilla Guide
and shepherding it to 2.14.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Terry Weissman <email>terry@mozilla.org</email></term>
<listitem>
<para>for initially writing Bugzilla and creating the README upon
which the UNIX installation documentation is largely based.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Tara Hernandez <email>tara@tequilarists.org</email></term>
<listitem>
<para>for keeping Bugzilla development going strong after Terry left
mozilla.org and for running landfill.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Dave Lawrence <email>dkl@redhat.com</email></term>
<listitem>
<para>for providing insight into the key differences between Red
Hat's customized Bugzilla.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Dawn Endico <email>endico@mozilla.org</email></term>
<listitem>
<para>for being a hacker extraordinaire and putting up with Matthew's
incessant questions and arguments on irc.mozilla.org in #mozwebtools
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Jacob Steenhagen <email>jake@bugzilla.org</email></term>
<listitem>
<para>for taking over documentation during the 2.17 development
period.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Dave Miller <email>justdave@bugzilla.org</email></term>
<listitem>
<para>for taking over as project lead when Tara stepped down and
continually pushing for the documentation to be the best it can be.
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Thanks also go to the following people for significant contributions
to this documentation:
<simplelist type="inline">
<member>Kevin Brannen</member>
<member>Vlad Dascalu</member>
<member>Ben FrantzDale</member>
<member>Eric Hanson</member>
<member>Zach Lipton</member>
<member>Gervase Markham</member>
<member>Andrew Pearson</member>
<member>Joe Robins</member>
<member>Spencer Smith</member>
<member>Ron Teitelbaum</member>
<member>Shane Travis</member>
<member>Martin Wulffeld</member>
</simplelist>.
</para>
<para>
Also, thanks are due to the members of the
<ulink url="news://news.mozilla.org/netscape.public.mozilla.webtools">
netscape.public.mozilla.webtools</ulink>
newsgroup. Without your discussions, insight, suggestions, and patches,
this could never have happened.
</para>
</section>
<!-- conventions used here (didn't want to give it a chapter of its own) -->
&conventions;
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-always-quote-attributes:t
sgml-auto-insert-required-elements:t
sgml-balanced-tag-edit:t
sgml-exposed-tags:nil
sgml-general-insert-case:lower
sgml-indent-data:t
sgml-indent-step:2
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
sgml-minimize-attributes:nil
sgml-namecase-general:t
sgml-omittag:t
sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
sgml-shorttag:t
sgml-tag-region-if-active:t
End: -->

File diff suppressed because it is too large Load Diff

View File

@@ -1,164 +0,0 @@
<!-- <!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
<section id="conventions">
<title>Document Conventions</title>
<indexterm zone="conventions">
<primary>conventions</primary>
</indexterm>
<para>This document uses the following conventions:</para>
<informaltable frame="none">
<tgroup cols="2">
<thead>
<row>
<entry>Descriptions</entry>
<entry>Appearance</entry>
</row>
</thead>
<tbody>
<row>
<entry>Warning</entry>
<entry>
<caution>
<para>Don't run with scissors!</para>
</caution>
</entry>
</row>
<row>
<entry>Hint</entry>
<entry>
<tip>
<para>Would you like a breath mint?</para>
</tip>
</entry>
</row>
<row>
<entry>Note</entry>
<entry>
<note>
<para>Dear John...</para>
</note>
</entry>
</row>
<row>
<entry>Information requiring special attention</entry>
<entry>
<warning>
<para>Read this or the cat gets it.</para>
</warning>
</entry>
</row>
<row>
<entry>File or directory name</entry>
<entry>
<filename>filename</filename>
</entry>
</row>
<row>
<entry>Command to be typed</entry>
<entry>
<command>command</command>
</entry>
</row>
<row>
<entry>Application name</entry>
<entry>
<application>application</application>
</entry>
</row>
<row>
<entry>
Normal user's prompt under bash shell</entry>
<entry>bash$</entry>
</row>
<row>
<entry>
Root user's prompt under bash shell</entry>
<entry>bash#</entry>
</row>
<row>
<entry>
Normal user's prompt under tcsh shell</entry>
<entry>tcsh$</entry>
</row>
<row>
<entry>Environment variables</entry>
<entry>
<envar>VARIABLE</envar>
</entry>
</row>
<row>
<entry>Term found in the glossary</entry>
<entry>
<glossterm linkend="gloss-bugzilla">Bugzilla</glossterm>
</entry>
</row>
<row>
<entry>Code example</entry>
<entry>
<programlisting><sgmltag class="starttag">para</sgmltag>
Beginning and end of paragraph
<sgmltag class="endtag">para</sgmltag></programlisting>
</entry>
</row>
</tbody>
</tgroup>
</informaltable>
<para>
This documentation is maintained in DocBook 4.1.2 XML format.
Changes are best submitted as plain text or XML diffs, attached
to a bug filed in the &bzg-bugs; component.
</para>
</section>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-always-quote-attributes:t
sgml-auto-insert-required-elements:t
sgml-balanced-tag-edit:t
sgml-exposed-tags:nil
sgml-general-insert-case:lower
sgml-indent-data:t
sgml-indent-step:2
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
sgml-minimize-attributes:nil
sgml-namecase-general:t
sgml-omittag:t
sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
sgml-shorttag:t
sgml-tag-region-if-active:t
End:
-->

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
--- File/Temp.pm.orig Thu Feb 6 16:26:00 2003
+++ File/Temp.pm Thu Feb 6 16:26:23 2003
@@ -205,6 +205,7 @@
# eg CGI::Carp
local $SIG{__DIE__} = sub {};
local $SIG{__WARN__} = sub {};
+ local *CORE::GLOBAL::die = sub {};
$bit = &$func();
1;
};
@@ -226,6 +227,7 @@
# eg CGI::Carp
local $SIG{__DIE__} = sub {};
local $SIG{__WARN__} = sub {};
+ local *CORE::GLOBAL::die = sub {};
$bit = &$func();
1;
};

View File

@@ -1,445 +0,0 @@
<!-- <!DOCTYPE appendix PUBLIC "-//OASIS//DTD DocBook V4.1//EN"> -->
<appendix id="gfdl">
<title>GNU Free Documentation License</title>
<!-- - GNU Project - Free Software Foundation (FSF) -->
<!-- LINK REV="made" HREF="mailto:webmasters@gnu.org" -->
<!-- section>
<title>GNU Free Documentation License</title -->
<para>Version 1.1, March 2000</para>
<blockquote>
<para>Copyright (C) 2000 Free Software Foundation, Inc. 59 Temple Place,
Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and
distribute verbatim copies of this license document, but changing it is
not allowed.</para>
</blockquote>
<section label="0" id="gfdl-0">
<title>Preamble</title>
<para>The purpose of this License is to make a manual, textbook, or other
written document "free" in the sense of freedom: to assure everyone the
effective freedom to copy and redistribute it, with or without modifying
it, either commercially or noncommercially. Secondarily, this License
preserves for the author and publisher a way to get credit for their
work, while not being considered responsible for modifications made by
others.</para>
<para>This License is a kind of "copyleft", which means that derivative
works of the document must themselves be free in the same sense. It
complements the GNU General Public License, which is a copyleft license
designed for free software.</para>
<para>We have designed this License in order to use it for manuals for
free software, because free software needs free documentation: a free
program should come with manuals providing the same freedoms that the
software does. But this License is not limited to software manuals; it
can be used for any textual work, regardless of subject matter or whether
it is published as a printed book. We recommend this License principally
for works whose purpose is instruction or reference.</para>
</section>
<section label="1" id="gfdl-1">
<title>Applicability and Definition</title>
<para>This License applies to any manual or other work that contains a
notice placed by the copyright holder saying it can be distributed under
the terms of this License. The "Document", below, refers to any such
manual or work. Any member of the public is a licensee, and is addressed
as "you".</para>
<para>A "Modified Version" of the Document means any work containing the
Document or a portion of it, either copied verbatim, or with
modifications and/or translated into another language.</para>
<para>A "Secondary Section" is a named appendix or a front-matter section
of the Document that deals exclusively with the relationship of the
publishers or authors of the Document to the Document's overall subject
(or to related matters) and contains nothing that could fall directly
within that overall subject. (For example, if the Document is in part a
textbook of mathematics, a Secondary Section may not explain any
mathematics.) The relationship could be a matter of historical connection
with the subject or with related matters, or of legal, commercial,
philosophical, ethical or political position regarding them.</para>
<para>The "Invariant Sections" are certain Secondary Sections whose
titles are designated, as being those of Invariant Sections, in the
notice that says that the Document is released under this License.</para>
<para>The "Cover Texts" are certain short passages of text that are
listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says
that the Document is released under this License.</para>
<para>A "Transparent" copy of the Document means a machine-readable copy,
represented in a format whose specification is available to the general
public, whose contents can be viewed and edited directly and
straightforwardly with generic text editors or (for images composed of
pixels) generic paint programs or (for drawings) some widely available
drawing editor, and that is suitable for input to text formatters or for
automatic translation to a variety of formats suitable for input to text
formatters. A copy made in an otherwise Transparent file format whose
markup has been designed to thwart or discourage subsequent modification
by readers is not Transparent. A copy that is not "Transparent" is called
"Opaque".</para>
<para>Examples of suitable formats for Transparent copies include plain
ASCII without markup, Texinfo input format, LaTeX input format, SGML or
XML using a publicly available DTD, and standard-conforming simple HTML
designed for human modification. Opaque formats include PostScript, PDF,
proprietary formats that can be read and edited only by proprietary word
processors, SGML or XML for which the DTD and/or processing tools are not
generally available, and the machine-generated HTML produced by some word
processors for output purposes only.</para>
<para>The "Title Page" means, for a printed book, the title page itself,
plus such following pages as are needed to hold, legibly, the material
this License requires to appear in the title page. For works in formats
which do not have any title page as such, "Title Page" means the text
near the most prominent appearance of the work's title, preceding the
beginning of the body of the text.</para>
</section>
<section label="2" id="gfdl-2">
<title>Verbatim Copying</title>
<para>You may copy and distribute the Document in any medium, either
commercially or noncommercially, provided that this License, the
copyright notices, and the license notice saying this License applies to
the Document are reproduced in all copies, and that you add no other
conditions whatsoever to those of this License. You may not use technical
measures to obstruct or control the reading or further copying of the
copies you make or distribute. However, you may accept compensation in
exchange for copies. If you distribute a large enough number of copies
you must also follow the conditions in section 3.</para>
<para>You may also lend copies, under the same conditions stated above,
and you may publicly display copies.</para>
</section>
<section label="3" id="gfdl-3">
<title>Copying in Quantity</title>
<para>If you publish printed copies of the Document numbering more than
100, and the Document's license notice requires Cover Texts, you must
enclose the copies in covers that carry, clearly and legibly, all these
Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts
on the back cover. Both covers must also clearly and legibly identify you
as the publisher of these copies. The front cover must present the full
title with all words of the title equally prominent and visible. You may
add other material on the covers in addition. Copying with changes
limited to the covers, as long as they preserve the title of the Document
and satisfy these conditions, can be treated as verbatim copying in other
respects.</para>
<para>If the required texts for either cover are too voluminous to fit
legibly, you should put the first ones listed (as many as fit reasonably)
on the actual cover, and continue the rest onto adjacent pages.</para>
<para>If you publish or distribute Opaque copies of the Document
numbering more than 100, you must either include a machine-readable
Transparent copy along with each Opaque copy, or state in or with each
Opaque copy a publicly-accessible computer-network location containing a
complete Transparent copy of the Document, free of added material, which
the general network-using public has access to download anonymously at no
charge using public-standard network protocols. If you use the latter
option, you must take reasonably prudent steps, when you begin
distribution of Opaque copies in quantity, to ensure that this
Transparent copy will remain thus accessible at the stated location until
at least one year after the last time you distribute an Opaque copy
(directly or through your agents or retailers) of that edition to the
public.</para>
<para>It is requested, but not required, that you contact the authors of
the Document well before redistributing any large number of copies, to
give them a chance to provide you with an updated version of the
Document.</para>
</section>
<section label="4" id="gfdl-4">
<title>Modifications</title>
<para>You may copy and distribute a Modified Version of the Document
under the conditions of sections 2 and 3 above, provided that you release
the Modified Version under precisely this License, with the Modified
Version filling the role of the Document, thus licensing distribution and
modification of the Modified Version to whoever possesses a copy of it.
In addition, you must do these things in the Modified Version:</para>
<orderedlist numeration="upperalpha">
<listitem>
<para>Use in the Title Page (and on the covers, if any) a title
distinct from that of the Document, and from those of previous
versions (which should, if there were any, be listed in the History
section of the Document). You may use the same title as a previous
version if the original publisher of that version gives
permission.</para>
</listitem>
<listitem>
<para>List on the Title Page, as authors, one or more persons or
entities responsible for authorship of the modifications in the
Modified Version, together with at least five of the principal
authors of the Document (all of its principal authors, if it has less
than five).</para>
</listitem>
<listitem>
<para>State on the Title page the name of the publisher of the
Modified Version, as the publisher.</para>
</listitem>
<listitem>
<para>Preserve all the copyright notices of the Document.</para>
</listitem>
<listitem>
<para>Add an appropriate copyright notice for your modifications
adjacent to the other copyright notices.</para>
</listitem>
<listitem>
<para>Include, immediately after the copyright notices, a license
notice giving the public permission to use the Modified Version under
the terms of this License, in the form shown in the Addendum
below.</para>
</listitem>
<listitem>
<para>Preserve in that license notice the full lists of Invariant
Sections and required Cover Texts given in the Document's license
notice.</para>
</listitem>
<listitem>
<para>Include an unaltered copy of this License.</para>
</listitem>
<listitem>
<para>Preserve the section entitled "History", and its title, and add
to it an item stating at least the title, year, new authors, and
publisher of the Modified Version as given on the Title Page. If
there is no section entitled "History" in the Document, create one
stating the title, year, authors, and publisher of the Document as
given on its Title Page, then add an item describing the Modified
Version as stated in the previous sentence.</para>
</listitem>
<listitem>
<para>Preserve the network location, if any, given in the Document
for public access to a Transparent copy of the Document, and likewise
the network locations given in the Document for previous versions it
was based on. These may be placed in the "History" section. You may
omit a network location for a work that was published at least four
years before the Document itself, or if the original publisher of the
version it refers to gives permission.</para>
</listitem>
<listitem>
<para>In any section entitled "Acknowledgements" or "Dedications",
preserve the section's title, and preserve in the section all the
substance and tone of each of the contributor acknowledgements and/or
dedications given therein.</para>
</listitem>
<listitem>
<para>Preserve all the Invariant Sections of the Document, unaltered
in their text and in their titles. Section numbers or the equivalent
are not considered part of the section titles.</para>
</listitem>
<listitem>
<para>Delete any section entitled "Endorsements". Such a section may
not be included in the Modified Version.</para>
</listitem>
<listitem>
<para>Do not retitle any existing section as "Endorsements" or to
conflict in title with any Invariant Section.</para>
</listitem>
</orderedlist>
<para>If the Modified Version includes new front-matter sections or
appendices that qualify as Secondary Sections and contain no material
copied from the Document, you may at your option designate some or all of
these sections as invariant. To do this, add their titles to the list of
Invariant Sections in the Modified Version's license notice. These titles
must be distinct from any other section titles.</para>
<para>You may add a section entitled "Endorsements", provided it contains
nothing but endorsements of your Modified Version by various parties--for
example, statements of peer review or that the text has been approved by
an organization as the authoritative definition of a standard.</para>
<para>You may add a passage of up to five words as a Front-Cover Text,
and a passage of up to 25 words as a Back-Cover Text, to the end of the
list of Cover Texts in the Modified Version. Only one passage of
Front-Cover Text and one of Back-Cover Text may be added by (or through
arrangements made by) any one entity. If the Document already includes a
cover text for the same cover, previously added by you or by arrangement
made by the same entity you are acting on behalf of, you may not add
another; but you may replace the old one, on explicit permission from the
previous publisher that added the old one.</para>
<para>The author(s) and publisher(s) of the Document do not by this
License give permission to use their names for publicity for or to assert
or imply endorsement of any Modified Version.</para>
</section>
<section label="5" id="gfdl-5">
<title>Combining Documents</title>
<para>You may combine the Document with other documents released under
this License, under the terms defined in section 4 above for modified
versions, provided that you include in the combination all of the
Invariant Sections of all of the original documents, unmodified, and list
them all as Invariant Sections of your combined work in its license
notice.</para>
<para>The combined work need only contain one copy of this License, and
multiple identical Invariant Sections may be replaced with a single copy.
If there are multiple Invariant Sections with the same name but different
contents, make the title of each such section unique by adding at the end
of it, in parentheses, the name of the original author or publisher of
that section if known, or else a unique number. Make the same adjustment
to the section titles in the list of Invariant Sections in the license
notice of the combined work.</para>
<para>In the combination, you must combine any sections entitled
"History" in the various original documents, forming one section entitled
"History"; likewise combine any sections entitled "Acknowledgements", and
any sections entitled "Dedications". You must delete all sections
entitled "Endorsements."</para>
</section>
<section label="6" id="gfdl-6">
<title>Collections of Documents</title>
<para>You may make a collection consisting of the Document and other
documents released under this License, and replace the individual copies
of this License in the various documents with a single copy that is
included in the collection, provided that you follow the rules of this
License for verbatim copying of each of the documents in all other
respects.</para>
<para>You may extract a single document from such a collection, and
distribute it individually under this License, provided you insert a copy
of this License into the extracted document, and follow this License in
all other respects regarding verbatim copying of that document.</para>
</section>
<section label="7" id="gfdl-7">
<title>Aggregation with Independent Works</title>
<para>A compilation of the Document or its derivatives with other
separate and independent documents or works, in or on a volume of a
storage or distribution medium, does not as a whole count as a Modified
Version of the Document, provided no compilation copyright is claimed for
the compilation. Such a compilation is called an "aggregate", and this
License does not apply to the other self-contained works thus compiled
with the Document, on account of their being thus compiled, if they are
not themselves derivative works of the Document.</para>
<para>If the Cover Text requirement of section 3 is applicable to these
copies of the Document, then if the Document is less than one quarter of
the entire aggregate, the Document's Cover Texts may be placed on covers
that surround only the Document within the aggregate. Otherwise they must
appear on covers around the whole aggregate.</para>
</section>
<section label="8" id="gfdl-8">
<title>Translation</title>
<para>Translation is considered a kind of modification, so you may
distribute translations of the Document under the terms of section 4.
Replacing Invariant Sections with translations requires special
permission from their copyright holders, but you may include translations
of some or all Invariant Sections in addition to the original versions of
these Invariant Sections. You may include a translation of this License
provided that you also include the original English version of this
License. In case of a disagreement between the translation and the
original English version of this License, the original English version
will prevail.</para>
</section>
<section label="9" id="gfdl-9">
<title>Termination</title>
<para>You may not copy, modify, sublicense, or distribute the Document
except as expressly provided for under this License. Any other attempt to
copy, modify, sublicense or distribute the Document is void, and will
automatically terminate your rights under this License. However, parties
who have received copies, or rights, from you under this License will not
have their licenses terminated so long as such parties remain in full
compliance.</para>
</section>
<section label="10" id="gfdl-10">
<title>Future Revisions of this License</title>
<para>The Free Software Foundation may publish new, revised versions of
the GNU Free Documentation License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns. See
<ulink url="http://www.gnu.org/copyleft/"/>.</para>
<para>Each version of the License is given a distinguishing version
number. If the Document specifies that a particular numbered version of
this License "or any later version" applies to it, you have the option of
following the terms and conditions either of that specified version or of
any later version that has been published (not as a draft) by the Free
Software Foundation. If the Document does not specify a version number of
this License, you may choose any version ever published (not as a draft)
by the Free Software Foundation.</para>
</section>
<section label="" id="gfdl-howto">
<title>How to use this License for your documents</title>
<para>To use this License in a document you have written, include a copy
of the License in the document and put the following copyright and
license notices just after the title page:</para>
<blockquote>
<para>Copyright (c) YEAR YOUR NAME. Permission is granted to copy,
distribute and/or modify this document under the terms of the GNU Free
Documentation License, Version 1.1 or any later version published by
the Free Software Foundation; with the Invariant Sections being LIST
THEIR TITLES, with the Front-Cover Texts being LIST, and with the
Back-Cover Texts being LIST. A copy of the license is included in the
section entitled "GNU Free Documentation License".</para>
</blockquote>
<para>If you have no Invariant Sections, write "with no Invariant
Sections" instead of saying which ones are invariant. If you have no
Front-Cover Texts, write "no Front-Cover Texts" instead of "Front-Cover
Texts being LIST"; likewise for Back-Cover Texts.</para>
<para>If your document contains nontrivial examples of program code, we
recommend releasing these examples in parallel under your choice of free
software license, such as the GNU General Public License, to permit their
use in free software.</para>
</section>
</appendix>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-always-quote-attributes:t
sgml-auto-insert-required-elements:t
sgml-balanced-tag-edit:t
sgml-exposed-tags:nil
sgml-general-insert-case:lower
sgml-indent-data:t
sgml-indent-step:2
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
sgml-minimize-attributes:nil
sgml-namecase-general:t
sgml-omittag:t
sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
sgml-shorttag:t
sgml-tag-region-if-active:t
End:
-->

View File

@@ -1,552 +0,0 @@
<!-- <!DOCTYPE glossary PUBLIC "-//OASIS//DTD DocBook V4.1//EN" > -->
<glossary id="glossary">
<glossdiv>
<title>0-9, high ascii</title>
<glossentry id="gloss-htaccess">
<glossterm>.htaccess</glossterm>
<glossdef>
<para>Apache web server, and other NCSA-compliant web servers,
observe the convention of using files in directories called
<filename>.htaccess</filename>
to restrict access to certain files. In Bugzilla, they are used
to keep secret files which would otherwise
compromise your installation - e.g. the
<filename>localconfig</filename>
file contains the password to your database.
curious.</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-a">
<title>A</title>
<glossentry id="gloss-apache">
<glossterm>Apache</glossterm>
<glossdef>
<para>In this context, Apache is the web server most commonly used
for serving up Bugzilla
pages. Contrary to popular belief, the apache web server has nothing
to do with the ancient and noble Native American tribe, but instead
derived its name from the fact that it was
<quote>a patchy</quote>
version of the original
<acronym>NCSA</acronym>
world-wide-web server.</para>
<variablelist>
<title>Useful Directives when configuring Bugzilla</title>
<varlistentry>
<term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#addhandler">AddHandler</ulink></computeroutput></term>
<listitem>
<para>Tell Apache that it's OK to run CGI scripts.</para>
</listitem>
</varlistentry>
<varlistentry>
<term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#allowoverride">AllowOverride</ulink></computeroutput></term>
<term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#options">Options</ulink></computeroutput></term>
<listitem>
<para>These directives are used to tell Apache many things about
the directory they apply to. For Bugzilla's purposes, we need
them to allow script execution and <filename>.htaccess</filename>
overrides.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/mod_dir.html#directoryindex">DirectoryIndex</ulink></computeroutput></term>
<listitem>
<para>Used to tell Apache what files are indexes. If you can
not add <filename>index.cgi</filename> to the list of valid files,
you'll need to set <computeroutput>$index_html</computeroutput> to
1 in <filename>localconfig</filename> so
<command>./checksetup.pl</command> will create an
<filename>index.html</filename> that redirects to
<filename>index.cgi</filename>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><computeroutput><ulink url="http://httpd.apache.org/docs-2.0/mod/core.html#scriptinterpretersource">ScriptInterpreterSource</ulink></computeroutput></term>
<listitem>
<para>Used when running Apache on windows so the shebang line
doesn't have to be changed in every Bugzilla script.
</para>
</listitem>
</varlistentry>
</variablelist>
<para>For more information about how to configure Apache for Bugzilla,
see <xref linkend="http-apache"/>.
</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-b">
<title>B</title>
<glossentry>
<glossterm>Bug</glossterm>
<glossdef>
<para>A
<quote>bug</quote>
in Bugzilla refers to an issue entered into the database which has an
associated number, assignments, comments, etc. Some also refer to a
<quote>tickets</quote>
or
<quote>issues</quote>;
in the context of Bugzilla, they are synonymous.</para>
</glossdef>
</glossentry>
<glossentry>
<glossterm>Bug Number</glossterm>
<glossdef>
<para>Each Bugzilla bug is assigned a number that uniquely identifies
that bug. The bug associated with a bug number can be pulled up via a
query, or easily from the very front page by typing the number in the
"Find" box.</para>
</glossdef>
</glossentry>
<glossentry id="gloss-bugzilla">
<glossterm>Bugzilla</glossterm>
<glossdef>
<para>Bugzilla is the world-leading free software bug tracking system.
</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-c">
<title>C</title>
<glossentry id="gloss-cgi">
<glossterm>Common Gateway Interface</glossterm>
<acronym>CGI</acronym>
<glossdef>
<para><acronym>CGI</acronym> is an acronym for Common Gateway Interface. This is
a standard for interfacing an external application with a web server. Bugzilla
is an example of a <acronym>CGI</acronym> application.
</para>
</glossdef>
</glossentry>
<glossentry id="gloss-component">
<glossterm>Component</glossterm>
<glossdef>
<para>A Component is a subsection of a Product. It should be a narrow
category, tailored to your organization. All Products must contain at
least one Component (and, as a matter of fact, creating a Product
with no Components will create an error in Bugzilla).</para>
</glossdef>
</glossentry>
<glossentry id="gloss-cpan">
<glossterm>Comprehensive Perl Archive Network</glossterm>
<acronym>CPAN</acronym>
<!-- TODO: Rewrite def for CPAN -->
<glossdef>
<para>
<acronym>CPAN</acronym>
stands for the
<quote>Comprehensive Perl Archive Network</quote>.
CPAN maintains a large number of extremely useful
<glossterm>Perl</glossterm>
modules - encapsulated chunks of code for performing a
particular task.</para>
</glossdef>
</glossentry>
<glossentry id="gloss-contrib">
<glossterm><filename class="directory">contrib</filename></glossterm>
<glossdef>
<para>The <filename class="directory">contrib</filename> directory is
a location to put scripts that have been contributed to Bugzilla but
are not a part of the official distribution. These scripts are written
by third parties and may be in languages other than perl. For those
that are in perl, there may be additional modules or other requirements
than those of the offical distribution.
<note>
<para>Scripts in the <filename class="directory">contrib</filename>
directory are not offically supported by the Bugzilla team and may
break in between versions.
</para>
</note>
</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-d">
<title>D</title>
<glossentry id="gloss-daemon">
<glossterm>daemon</glossterm>
<glossdef>
<para>A daemon is a computer program which runs in the background. In
general, most daemons are started at boot time via System V init
scripts, or through RC scripts on BSD-based systems.
<glossterm>mysqld</glossterm>,
the MySQL server, and
<glossterm>apache</glossterm>,
a web server, are generally run as daemons.</para>
</glossdef>
</glossentry>
<glossentry id="gloss-dos">
<glossterm>DOS Attack</glossterm>
<glossdef>
<para>A DOS, or Denial of Service attack, is when a user attempts to
deny access to a web server by repeatadly accessing a page or sending
malformed requests to a webserver. This can be effectively prevented
by using <filename>mod_throttle</filename> as described in
<xref linkend="security-webserver-mod-throttle"/>. A D-DOS, or
Distributed Denial of Service attack, is when these requests come
from multiple sources at the same time. Unfortunately, these are much
more difficult to defend against.
</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-g">
<title>G</title>
<glossentry id="gloss-groups">
<glossterm>Groups</glossterm>
<glossdef>
<para>The word
<quote>Groups</quote>
has a very special meaning to Bugzilla. Bugzilla's main security
mechanism comes by placing users in groups, and assigning those
groups certain privileges to view bugs in particular
<glossterm>Products</glossterm>
in the
<glossterm>Bugzilla</glossterm>
database.</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-j">
<title>J</title>
<glossentry id="gloss-javascript">
<glossterm>JavaScript</glossterm>
<glossdef>
<para>JavaScript is cool, we should talk about it.
</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-m">
<title>M</title>
<glossentry id="gloss-mta">
<glossterm>Message Transport Agent</glossterm>
<acronym>MTA</acronym>
<glossdef>
<para>A Message Transport Agent is used to control the flow of email
on a system. Many unix based systems use
<ulink url="http://www.sendmail.org">sendmail</ulink> which is what
Bugzilla expects to find by default at <filename>/usr/sbin/sendmail</filename>.
Many other MTA's will work, but they all require that the
<option>sendmailnow</option> param be set to <literal>on</literal>.
</para>
</glossdef>
</glossentry>
<glossentry id="gloss-mysql">
<glossterm>MySQL</glossterm>
<glossdef>
<para>MySQL is currently the required
<glossterm linkend="gloss-rdbms">RDBMS</glossterm> for Bugzilla. MySQL
can be downloaded from <ulink url="http://www.mysql.com"/>. While you
should familiarize yourself with all of the documentation, some high
points are:
</para>
<variablelist>
<varlistentry>
<term><ulink url="http://www.mysql.com/doc/en/Backup.html">Backup</ulink></term>
<listitem>
<para>Methods for backing up your Bugzilla database.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><ulink url="http://www.mysql.com/doc/en/Option_files.html">Option Files</ulink></term>
<listitem>
<para>Information about how to configure MySQL using
<filename>my.cnf</filename>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><ulink url="http://www.mysql.com/doc/en/Privilege_system.html">Privilege System</ulink></term>
<listitem>
<para>Much more detailed information about the suggestions in
<xref linkend="security-mysql"/>.
</para>
</listitem>
</varlistentry>
</variablelist>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-p">
<title>P</title>
<glossentry id="gloss-ppm">
<glossterm>Perl Package Manager</glossterm>
<acronym>PPM</acronym>
<glossdef>
<para><ulink url="http://aspn.activestate.com/ASPN/Downloads/ActivePerl/PPM/"/>
</para>
</glossdef>
</glossentry>
<glossentry>
<glossterm id="gloss-product">Product</glossterm>
<glossdef>
<para>A Product is a broad category of types of bugs, normally
representing a single piece of software or entity. In general,
there are several Components to a Product. A Product may define a
group (used for security) for all bugs entered into
its Components.</para>
</glossdef>
</glossentry>
<glossentry>
<glossterm>Perl</glossterm>
<glossdef>
<para>First written by Larry Wall, Perl is a remarkable program
language. It has the benefits of the flexibility of an interpreted
scripting language (such as shell script), combined with the speed
and power of a compiled language, such as C.
<glossterm>Bugzilla</glossterm>
is maintained in Perl.</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-q">
<title>Q</title>
<glossentry>
<glossterm>QA</glossterm>
<glossdef>
<para>
<quote>QA</quote>,
<quote>Q/A</quote>, and
<quote>Q.A.</quote>
are short for
<quote>Quality Assurance</quote>.
In most large software development organizations, there is a team
devoted to ensuring the product meets minimum standards before
shipping. This team will also generally want to track the progress of
bugs over their life cycle, thus the need for the
<quote>QA Contact</quote>
field in a bug.</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-r">
<title>R</title>
<glossentry id="gloss-rdbms">
<glossterm>Relational DataBase Managment System</glossterm>
<acronym>RDBMS</acronym>
<glossdef>
<para>A relational database management system is a database system
that stores information in tables that are related to each other.
</para>
</glossdef>
</glossentry>
<glossentry id="gloss-regexp">
<glossterm>Regular Expression</glossterm>
<acronym>regexp</acronym>
<glossdef>
<para>A regular expression is an expression used for pattern matching.
<ulink url="http://perldoc.com/perl5.6/pod/perlre.html#Regular-Expressions">Documentation</ulink>
</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-s">
<title>S</title>
<glossentry id="gloss-service">
<glossterm>Service</glossterm>
<glossdef>
<para>In Windows NT environment, a boot-time background application
is refered to as a service. These are generally managed through the
control pannel while logged in as an account with
<quote>Administrator</quote> level capabilities. For more
information, consult your Windows manual or the MSKB.
</para>
</glossdef>
</glossentry>
<glossentry>
<glossterm>
<acronym>SGML</acronym>
</glossterm>
<glossdef>
<para>
<acronym>SGML</acronym>
stands for
<quote>Standard Generalized Markup Language</quote>.
Created in the 1980's to provide an extensible means to maintain
documentation based upon content instead of presentation,
<acronym>SGML</acronym>
has withstood the test of time as a robust, powerful language.
<glossterm>
<acronym>XML</acronym>
</glossterm>
is the
<quote>baby brother</quote>
of SGML; any valid
<acronym>XML</acronym>
document it, by definition, a valid
<acronym>SGML</acronym>
document. The document you are reading is written and maintained in
<acronym>SGML</acronym>,
and is also valid
<acronym>XML</acronym>
if you modify the Document Type Definition.</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-t">
<title>T</title>
<glossentry id="gloss-target-milestone" xreflabel="Target Milestone">
<glossterm>Target Milestone</glossterm>
<glossdef>
<para>Target Milestones are Product goals. They are configurable on a
per-Product basis. Most software development houses have a concept of
<quote>milestones</quote>
where the people funding a project expect certain functionality on
certain dates. Bugzilla facilitates meeting these milestones by
giving you the ability to declare by which milestone a bug will be
fixed, or an enhancement will be implemented.</para>
</glossdef>
</glossentry>
<glossentry id="gloss-tcl">
<glossterm>Tool Command Language</glossterm>
<acronym>TCL</acronym>
<glossdef>
<para>TCL is an open source scripting language available for Windows,
Macintosh, and Unix based systems. Bugzilla 1.0 was written in TCL but
never released. The first release of Bugzilla was 2.0, which was when
it was ported to perl.
</para>
</glossdef>
</glossentry>
</glossdiv>
<glossdiv id="gloss-z">
<title>Z</title>
<glossentry id="gloss-zarro">
<glossterm>Zarro Boogs Found</glossterm>
<glossdef>
<para>This is just a goofy way of saying that there were no bugs
found matching your query. When asked to explain this message,
Terry had the following to say:
</para>
<blockquote>
<attribution>Terry Weissman</attribution>
<para>I've been asked to explain this ... way back when, when
Netscape released version 4.0 of its browser, we had a release
party. Naturally, there had been a big push to try and fix every
known bug before the release. Naturally, that hadn't actually
happened. (This is not unique to Netscape or to 4.0; the same thing
has happened with every software project I've ever seen.) Anyway,
at the release party, T-shirts were handed out that said something
like "Netscape 4.0: Zarro Boogs". Just like the software, the
T-shirt had no known bugs. Uh-huh.
</para>
<para>So, when you query for a list of bugs, and it gets no results,
you can think of this as a friendly reminder. Of *course* there are
bugs matching your query, they just aren't in the bugsystem yet...
</para>
</blockquote>
</glossdef>
</glossentry>
</glossdiv>
</glossary>
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-always-quote-attributes:t
sgml-auto-insert-required-elements:t
sgml-balanced-tag-edit:t
sgml-exposed-tags:nil
sgml-general-insert-case:lower
sgml-indent-data:t
sgml-indent-step:2
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
sgml-minimize-attributes:nil
sgml-namecase-general:t
sgml-omittag:t
sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
sgml-shorttag:t
sgml-tag-region-if-active:t
End:
-->

View File

@@ -1,21 +0,0 @@
<!-- Keep this comment at the end of the file
Local variables:
mode: sgml
sgml-always-quote-attributes:t
sgml-auto-insert-required-elements:t
sgml-balanced-tag-edit:t
sgml-exposed-tags:nil
sgml-general-insert-case:lower
sgml-indent-data:t
sgml-indent-step:2
sgml-local-catalogs:nil
sgml-local-ecat-files:nil
sgml-minimize-attributes:nil
sgml-namecase-general:t
sgml-omittag:t
sgml-parent-document:("Bugzilla-Guide.xml" "book" "chapter")
sgml-shorttag:t
sgml-tag-region-if-active:t
End:
-->

Some files were not shown because too many files have changed in this diff Show More