Compare commits
1 Commits
tags/BUGZI
...
src
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
258dc9fead |
|
Before Width: | Height: | Size: 82 B |
@@ -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
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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...
|
||||
# >=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;
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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,"&")
|
||||
.replace(/</g,"<")
|
||||
.replace(/>/g,">") + "</p>");
|
||||
// -->
|
||||
</script>
|
||||
<p>Template->process() failed twice.<br>
|
||||
First error: $error<br>
|
||||
Second error: $error2</p>
|
||||
</tt>
|
||||
END
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Error - Error handling utilities for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Error;
|
||||
|
||||
ThrowUserError("error_tag",
|
||||
{ foo => 'bar' });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Various places throughout the Bugzilla codebase need to report errors to the
|
||||
user. The C<Throw*Error> family of functions allow this to be done in a
|
||||
generic and 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>
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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 
|
||||
# See bugs 4928, 22983 and 32000 for more details
|
||||
html_linebreak => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/\r\n/\
/g;
|
||||
$var =~ s/\n\r/\
/g;
|
||||
$var =~ s/\r/\
/g;
|
||||
$var =~ s/\n/\
/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
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/\@/\@/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>
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::Bugzilla
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the persistent C<Bugzilla>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla>, L<Template::Plugin>
|
||||
|
||||
@@ -1,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>
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
@@ -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/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/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/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/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/\
/g;
|
||||
$var =~ s/\n\r/\
/g;
|
||||
$var =~ s/\r/\
/g;
|
||||
$var =~ s/\n/\
/g;
|
||||
return $var;
|
||||
}
|
||||
|
||||
sub xml_quote {
|
||||
my ($var) = (@_);
|
||||
$var =~ s/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
$var =~ s/\'/\'/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 
, suitable for use in html attributes.
|
||||
|
||||
=item C<xml_quote($val)>
|
||||
|
||||
This is similar to C<html_quote>, except that ' is escaped to '. This
|
||||
is kept separate from html_quote partly for compatibility with previous code
|
||||
(for ') and partly for future handling of non-ASCII characters.
|
||||
|
||||
=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
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
* This README is no longer used to house installation instructions. Instead,
|
||||
it contains pointers to where you may find the information you need.
|
||||
|
||||
* A quick installation guide is provided in the QUICKSTART file.
|
||||
|
||||
* Complete installation instructions are found in docs/, with a
|
||||
variety of document types available. Please refer to these documents
|
||||
when installing, configuring, and maintaining your Bugzilla
|
||||
installation. A helpful starting point is docs/txt/Bugzilla-Guide.txt,
|
||||
or with a web browser at docs/html/index.html.
|
||||
|
||||
* Release notes for people upgrading to a new version of Bugzilla are
|
||||
available at docs/rel_notes.txt.
|
||||
|
||||
* If you wish to contribute to the documentation, please read docs/README.docs.
|
||||
|
||||
* The Bugzilla web site is at "http://www.bugzilla.org/". This site will
|
||||
contain the latest Bugzilla information, including how to report bugs and how
|
||||
to get help with Bugzilla.
|
||||
@@ -1,3 +0,0 @@
|
||||
Please consult The Bugzilla Guide for instructions on how to upgrade
|
||||
Bugzilla from an older version. The Guide can be found with this
|
||||
distribution, in docs/html, docs/txt, and docs/sgml.
|
||||
@@ -1,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
|
||||
|
||||
|
Before Width: | Height: | Size: 8.3 KiB |
@@ -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)>
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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());
|
||||
@@ -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) {
|
||||
®enerate_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 <kiko@async.com.br>, Eric S. Raymond
|
||||
<esr@thyrsus.com>.</para>
|
||||
</refsect1>
|
||||
</refentry>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
@@ -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 #
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
@@ -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; }
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
.bz_private { color:darkred }
|
||||
@@ -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());
|
||||
@@ -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());
|
||||
@@ -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.
|
||||
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 890 B |
|
Before Width: | Height: | Size: 907 B |
|
Before Width: | Height: | Size: 914 B |
|
Before Width: | Height: | Size: 134 B |
|
Before Width: | Height: | Size: 226 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 140 B |
@@ -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');
|
||||
|
||||
@@ -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&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:
|
||||
-->
|
||||
@@ -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-¤t-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: -->
|
||||
|
||||
@@ -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:
|
||||
-->
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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:
|
||||
-->
|
||||
|
||||
@@ -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:
|
||||
-->
|
||||
@@ -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:
|
||||
-->
|
||||
|
||||