Compare commits
1 Commits
tags/BUGZI
...
src
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
258dc9fead |
@@ -1,320 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
#
|
||||
|
||||
package Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Auth;
|
||||
use Bugzilla::Auth::Login::WWW;
|
||||
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;
|
||||
|
||||
if (not defined $_user) {
|
||||
$_user = new Bugzilla::User;
|
||||
}
|
||||
|
||||
return $_user;
|
||||
}
|
||||
|
||||
sub login {
|
||||
my ($class, $type) = @_;
|
||||
$_user = Bugzilla::Auth::Login::WWW->login($type);
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my ($class, $option) = @_;
|
||||
|
||||
# If we're not logged in, go away
|
||||
return unless user->id;
|
||||
|
||||
$option = LOGOUT_CURRENT unless defined $option;
|
||||
Bugzilla::Auth::Login::WWW->logout($_user, $option);
|
||||
}
|
||||
|
||||
sub logout_user {
|
||||
my ($class, $user) = @_;
|
||||
# When we're logging out another user we leave cookies alone, and
|
||||
# therefore avoid calling Bugzilla->logout() directly.
|
||||
Bugzilla::Auth::Login::WWW->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;
|
||||
# XXX clean this up eventually
|
||||
$::userid = 0;
|
||||
# We can't delete from $cgi->cookie, so logincookie data will remain
|
||||
# there. Don't rely on it: use Bugzilla->user->login 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;
|
||||
}
|
||||
|
||||
my $_batch;
|
||||
sub batch {
|
||||
my $class = shift;
|
||||
my $newval = shift;
|
||||
if ($newval) {
|
||||
$_batch = $newval;
|
||||
}
|
||||
return $_batch || 0;
|
||||
}
|
||||
|
||||
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> functionality is method based; use C<Bugzilla-E<gt>dbh>
|
||||
rather than C<Bugzilla::dbh>. Nothing cares about this now, but don't rely on
|
||||
that.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<template>
|
||||
|
||||
The current C<Template> object, to be used for output
|
||||
|
||||
=item C<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-E<gt>user> to return C<undef>. This has the
|
||||
effect of logging out a user for the current request only; cookies and
|
||||
database sessions are left intact.
|
||||
|
||||
=item C<batch>
|
||||
|
||||
Set to true, by calling Bugzilla->batch(1), to indicate that Bugzilla is
|
||||
being called in a non-interactive manner and errors should be passed to
|
||||
die() rather than being sent to a browser and finished with an exit().
|
||||
Bugzilla->batch will return the current state of this flag.
|
||||
|
||||
=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,110 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# 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;
|
||||
use Bugzilla::Config qw(:locations);
|
||||
use Bugzilla::User;
|
||||
|
||||
############################################################################
|
||||
# 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.html.tmpl in an
|
||||
# "attachments" variable.
|
||||
my ($bugid) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Retrieve a list of attachments for this bug and write them into an array
|
||||
# of hashes in which each hash represents a single attachment.
|
||||
my $list = $dbh->selectall_arrayref("SELECT attach_id, " .
|
||||
$dbh->sql_date_format('creation_ts', '%Y.%m.%d %H:%i') .
|
||||
", mimetype, description, ispatch,
|
||||
isobsolete, isprivate, LENGTH(thedata)
|
||||
FROM attachments
|
||||
WHERE bug_id = ? ORDER BY attach_id",
|
||||
undef, $bugid);
|
||||
|
||||
my @attachments = ();
|
||||
foreach my $row (@$list) {
|
||||
my %a;
|
||||
($a{'attachid'}, $a{'date'}, $a{'contenttype'},
|
||||
$a{'description'}, $a{'ispatch'}, $a{'isobsolete'},
|
||||
$a{'isprivate'}, $a{'datasize'}) = @$row;
|
||||
|
||||
# Retrieve a list of flags for this attachment.
|
||||
$a{'flags'} = Bugzilla::Flag::match({ 'attach_id' => $a{'attachid'},
|
||||
'is_active' => 1 });
|
||||
|
||||
# A zero size indicates that the attachment is stored locally.
|
||||
if ($a{'datasize'} == 0) {
|
||||
my $attachid = $a{'attachid'};
|
||||
my $hash = ($attachid % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
if (open(AH, "$attachdir/$hash/attachment.$attachid")) {
|
||||
$a{'datasize'} = (stat(AH))[7];
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
push @attachments, \%a;
|
||||
}
|
||||
|
||||
return \@attachments;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,322 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config;
|
||||
use Bugzilla::Constants;
|
||||
|
||||
# The verification method that was successfully used upon login, if any
|
||||
my $current_verify_class = undef;
|
||||
|
||||
# 'inherit' from the main verify method
|
||||
BEGIN {
|
||||
for my $verifyclass (split /,\s*/, Param("user_verify_class")) {
|
||||
if ($verifyclass =~ /^([A-Za-z0-9_\.\-]+)$/) {
|
||||
$verifyclass = $1;
|
||||
} else {
|
||||
die "Badly-named user_verify_class '$verifyclass'";
|
||||
}
|
||||
require "Bugzilla/Auth/Verify/" . $verifyclass . ".pm";
|
||||
}
|
||||
}
|
||||
|
||||
# PRIVATE
|
||||
|
||||
# A number of features, like password change requests, require the DB
|
||||
# verification method to be on the list.
|
||||
sub has_db {
|
||||
for (split (/[\s,]+/, Param("user_verify_class"))) {
|
||||
if (/^DB$/) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
# 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)));
|
||||
}
|
||||
|
||||
# This is a replacement for the inherited authenticate function
|
||||
# go through each of the available methods for each function
|
||||
sub authenticate {
|
||||
my $class = shift;
|
||||
my @args = @_;
|
||||
my @firstresult = ();
|
||||
my @result = ();
|
||||
my $current_verify_method;
|
||||
for my $method (split /,\s*/, Param("user_verify_class")) {
|
||||
$current_verify_method = $method;
|
||||
$method = "Bugzilla::Auth::Verify::" . $method;
|
||||
@result = $method->authenticate(@args);
|
||||
@firstresult = @result unless @firstresult;
|
||||
|
||||
if (($result[0] != AUTH_NODATA)&&($result[0] != AUTH_LOGINFAILED)) {
|
||||
unshift @result, ($current_verify_method);
|
||||
return @result;
|
||||
}
|
||||
}
|
||||
@result = @firstresult;
|
||||
# no auth match
|
||||
|
||||
# see if we can set $current to the first verify method that
|
||||
# will allow a new login
|
||||
|
||||
my $chosen_verify_method;
|
||||
for my $method (split /,\s*/, Param("user_verify_class")) {
|
||||
$current_verify_method = $method;
|
||||
$method = "Bugzilla::Auth::Verify::" . $method;
|
||||
if ($method->can_edit('new')) {
|
||||
$chosen_verify_method = $method;
|
||||
}
|
||||
}
|
||||
|
||||
unshift @result, $chosen_verify_method;
|
||||
return @result;
|
||||
}
|
||||
|
||||
sub can_edit {
|
||||
my ($class, $type) = @_;
|
||||
if ($current_verify_class) {
|
||||
return $current_verify_class->can_edit($type);
|
||||
}
|
||||
# $current_verify_class will not be set if the user isn't logged in. That
|
||||
# happens when the user is trying to create a new account, which (for now)
|
||||
# is hard-coded to work with DB.
|
||||
elsif (has_db) {
|
||||
return Bugzilla::Auth::Verify::DB->can_edit($type);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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).
|
||||
|
||||
Modules for obtaining the data are located under L<Bugzilla::Auth::Login>, and
|
||||
modules for authenticating are located in L<Bugzilla::Auth::Verify>.
|
||||
|
||||
=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. The methods that C<Bugzilla::Auth> uses for
|
||||
authentication are wrappers that check all configured modules (via the
|
||||
C<Param('user_info_class')> and C<Param('user_verify_class')>) in sequence.
|
||||
|
||||
=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 the name of the class that generated the results
|
||||
constined in the remaining return values. The second 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.
|
||||
|
||||
=item C<AUTH_OK>
|
||||
|
||||
Authentication succeeded. The third 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 third return
|
||||
value may contain the Bugzilla userid, but will probably be C<undef>,
|
||||
signifiying that the userid is unknown. The fourth value is a tag describing
|
||||
the error used by the authentication error templates to print a description
|
||||
to the user. The optional fifth 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 third 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 fourth 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 fifth 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 third argument in the returned array is the userid, and the fourth
|
||||
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.
|
||||
|
||||
=item C<current_verify_class>
|
||||
|
||||
This scalar gets populated with the full name (eg.,
|
||||
C<Bugzilla::Auth::Verify::DB>) of the verification method being used by the
|
||||
current user. If no user is logged in, it will contain the name of the first
|
||||
method that allows new users, if any. Otherwise, it carries an undefined
|
||||
value.
|
||||
|
||||
=item C<can_edit>
|
||||
|
||||
This determines if the user's account details can be modified. It returns a
|
||||
reference to a hash with the keys C<userid>, C<login_name>, and C<realname>,
|
||||
which determine whether their respective profile values may be altered, and
|
||||
C<new>, which determines if new accounts may be created.
|
||||
|
||||
Each user verification method (chosen with C<Param('user_verify_class')> has
|
||||
its own set of can_edit values. Calls to can_edit return the appropriate
|
||||
values for the current user's login method.
|
||||
|
||||
If a user is not logged in, C<can_edit> will contain the values of the first
|
||||
verify method that allows new users to be created, if available. Otherwise it
|
||||
returns an empty hash.
|
||||
|
||||
=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::Login::WWW::CGI|Bugzilla::Auth::Login::WWW::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:
|
||||
|
||||
=over 4
|
||||
|
||||
=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:
|
||||
|
||||
=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.
|
||||
|
||||
=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:
|
||||
|
||||
=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::Login::WWW::CGI>, L<Bugzilla::Auth::Login::WWW::CGI::Cookie>, L<Bugzilla::Auth::Verify::DB>
|
||||
|
||||
@@ -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): Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth::Login::WWW;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Config;
|
||||
|
||||
# $current_login_class stores the name of the login style that succeeded.
|
||||
my $current_login_class = undef;
|
||||
sub login_class {
|
||||
my ($class, $type) = @_;
|
||||
if ($type) {
|
||||
$current_login_class = $type;
|
||||
}
|
||||
return $current_login_class;
|
||||
}
|
||||
|
||||
# can_logout determines if a user may log out
|
||||
sub can_logout {
|
||||
return 1 if (login_class && login_class->can_logout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub login {
|
||||
my ($class, $type) = @_;
|
||||
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
# Avoid double-logins, which may confuse the auth code
|
||||
# (double cookies, odd compat code settings, etc)
|
||||
return $user if $user->id;
|
||||
|
||||
$type = LOGIN_REQUIRED if Bugzilla->cgi->param('GoAheadAndLogIn');
|
||||
$type = LOGIN_NORMAL unless defined $type;
|
||||
|
||||
# Log in using whatever methods are defined in user_info_class.
|
||||
# Please note the particularly strange way require() and the function
|
||||
# calls are being done, because we're calling a module that's named in
|
||||
# a string. I assure you it works, and it avoids the need for an eval().
|
||||
my $userid;
|
||||
for my $login_class (split(/,\s*/, Param('user_info_class'))) {
|
||||
require "Bugzilla/Auth/Login/WWW/" . $login_class . ".pm";
|
||||
$userid = "Bugzilla::Auth::Login::WWW::$login_class"->login($type);
|
||||
if ($userid) {
|
||||
$class->login_class("Bugzilla::Auth::Login::WWW::$login_class");
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if ($userid) {
|
||||
$user = new Bugzilla::User($userid);
|
||||
|
||||
# Redirect to SSL if required
|
||||
if (Param('sslbase') ne '' and Param('ssl') ne 'never') {
|
||||
Bugzilla->cgi->require_https(Param('sslbase'));
|
||||
}
|
||||
|
||||
$user->set_flags('can_logout' => $class->can_logout);
|
||||
|
||||
# Compat stuff
|
||||
$::userid = $userid;
|
||||
} else {
|
||||
Bugzilla->logout_request();
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my ($class, $user, $option) = @_;
|
||||
if (can_logout) {
|
||||
$class->login_class->logout($user, $option);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Login::WWW - WWW login information gathering module
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<login>
|
||||
|
||||
Passes C<login> calls to each class defined in the param C<user_info_class>
|
||||
and returns a C<Bugzilla::User> object from the first one that successfully
|
||||
gathers user login information.
|
||||
|
||||
=back
|
||||
@@ -1,262 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth::Login::WWW::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;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# First, try the actual login method against form variables
|
||||
my $username = $cgi->param("Bugzilla_login");
|
||||
my $passwd = $cgi->param("Bugzilla_password");
|
||||
|
||||
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
|
||||
|
||||
# Perform the actual authentication, get the method name from the class name
|
||||
my ($authmethod, $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);
|
||||
|
||||
$dbh->do("INSERT INTO logincookies (userid, ipaddr, lastused)
|
||||
VALUES (?, ?, NOW())",
|
||||
undef,
|
||||
$userid, $ipaddr);
|
||||
my $logincookie = $dbh->bz_last_key('logincookies', 'cookie');
|
||||
|
||||
# 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::Login::WWW::CGI::Cookie;
|
||||
my $authmethod = "Cookie";
|
||||
|
||||
($authres, $userid, $extra) =
|
||||
Bugzilla::Auth::Login::WWW::CGI::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) {
|
||||
|
||||
# Redirect to SSL if required
|
||||
if (Param('sslbase') ne '' and Param('ssl') ne 'never') {
|
||||
$cgi->require_https(Param('sslbase'));
|
||||
}
|
||||
|
||||
# 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),
|
||||
'caneditaccount' => Bugzilla::Auth->can_edit('new'),
|
||||
'has_db' => Bugzilla::Auth->has_db,
|
||||
}
|
||||
)
|
||||
|| 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.
|
||||
$dbh->do("DELETE FROM logincookies WHERE " .
|
||||
$dbh->sql_to_days('NOW()') . " - " .
|
||||
$dbh->sql_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 });
|
||||
}
|
||||
|
||||
# This auth style allows the user to log out.
|
||||
sub can_logout { return 1; }
|
||||
|
||||
# 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()");
|
||||
}
|
||||
|
||||
if ($option != LOGOUT_KEEP_CURRENT) {
|
||||
clear_browser_cookies();
|
||||
Bugzilla->logout_request();
|
||||
}
|
||||
}
|
||||
|
||||
sub clear_browser_cookies {
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->remove_cookie('Bugzilla_login');
|
||||
$cgi->remove_cookie('Bugzilla_logincookie');
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Login::WWW::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::Login::WWW::CGI::Cookie>.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::Auth>
|
||||
@@ -1,113 +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::Login::WWW::CGI::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=?";
|
||||
my @params = ($login_cookie, $login, $ipaddr);
|
||||
if (defined $netaddr) {
|
||||
trick_taint($netaddr);
|
||||
$query .= " OR logincookies.ipaddr=?";
|
||||
push(@params, $netaddr);
|
||||
}
|
||||
$query .= ")";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my ($userid, $disabledtext) = $dbh->selectrow_array($query, undef, @params);
|
||||
|
||||
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=NOW() 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::Auth::Login::WWW::CGI::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::Login::WWW::CGI> handles this.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::Auth>, L<Bugzilla::Auth::Login::WWW::CGI>
|
||||
@@ -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): Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth::Login::WWW::Env;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
sub login {
|
||||
my ($class, $type) = @_;
|
||||
|
||||
# XXX This does not currently work correctly with Param('requirelogin').
|
||||
# Bug 253636 will hopefully see that param's needs taken care of in a
|
||||
# parent module, but for the time being, this module does not honor
|
||||
# the param in the way that CGI.pm does.
|
||||
|
||||
my $matched_userid = '';
|
||||
my $matched_extern_id = '';
|
||||
my $disabledtext = '';
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth;
|
||||
|
||||
# Gather the environment variables
|
||||
my $env_id = $ENV{Param("auth_env_id")};
|
||||
my $env_email = $ENV{Param("auth_env_email")};
|
||||
my $env_realname = $ENV{Param("auth_env_realname")};
|
||||
|
||||
# allow undefined values to work with trick_taint
|
||||
for ($env_id, $env_email, $env_realname) { $_ ||= '' };
|
||||
# make sure the email field contains only a valid email address
|
||||
my $emailregexp = Param("emailregexp");
|
||||
if ($env_email =~ /($emailregexp)/) {
|
||||
$env_email = $1;
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
# untaint the remaining values
|
||||
trick_taint($env_id);
|
||||
trick_taint($env_realname);
|
||||
|
||||
if ($env_id || $env_email) {
|
||||
# Look in the DB for the extern_id
|
||||
if ($env_id) {
|
||||
|
||||
# Not having the email address defined but having an ID isn't
|
||||
# allowed.
|
||||
return undef unless $env_email;
|
||||
|
||||
$sth = $dbh->prepare("SELECT userid, disabledtext " .
|
||||
"FROM profiles WHERE extern_id=?");
|
||||
$sth->execute($env_id);
|
||||
my $fetched = $sth->fetch;
|
||||
if ($fetched) {
|
||||
$matched_userid = $fetched->[0];
|
||||
$disabledtext = $fetched->[1];
|
||||
}
|
||||
}
|
||||
|
||||
unless ($matched_userid) {
|
||||
# There was either no match for the external ID given, or one was
|
||||
# not present.
|
||||
#
|
||||
# Check to see if the email address is in there and has no
|
||||
# external id assigned. We test for both the login name (which we
|
||||
# also sent), and the id, so that we have a way of telling that we
|
||||
# got something instead of a bunch of NULLs
|
||||
$sth = $dbh->prepare("SELECT extern_id, userid, disabledtext " .
|
||||
"FROM profiles WHERE " .
|
||||
$dbh->sql_istrcmp('login_name', '?'));
|
||||
$sth->execute($env_email);
|
||||
|
||||
$sth->execute();
|
||||
my $fetched = $sth->fetch();
|
||||
if ($fetched) {
|
||||
($matched_extern_id, $matched_userid, $disabledtext) = @{$fetched};
|
||||
}
|
||||
if ($matched_userid) {
|
||||
if ($matched_extern_id) {
|
||||
# someone with a different external ID has that address!
|
||||
ThrowUserError("extern_id_conflict");
|
||||
}
|
||||
else
|
||||
{
|
||||
# someone with no external ID used that address, time to
|
||||
# add the ID!
|
||||
$sth = $dbh->prepare("UPDATE profiles " .
|
||||
"SET extern_id=? WHERE userid=?");
|
||||
$sth->execute($env_id, $matched_userid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# Need to create a new user with that email address. Note
|
||||
# that cryptpassword has been filled in with '*', since the
|
||||
# user has no DB password.
|
||||
$sth = $dbh->prepare("INSERT INTO profiles ( " .
|
||||
"login_name, cryptpassword, " .
|
||||
"realname, disabledtext " .
|
||||
") VALUES ( ?, ?, ?, '' )");
|
||||
$sth->execute($env_email, '*', $env_realname);
|
||||
$matched_userid = $dbh->bz_last_key('profiles', 'userid');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# now that we hopefully have a username, we need to see if the data
|
||||
# has to be updated
|
||||
if ($matched_userid) {
|
||||
$sth = $dbh->prepare("SELECT login_name, realname " .
|
||||
"FROM profiles " .
|
||||
"WHERE userid=?");
|
||||
$sth->execute($matched_userid);
|
||||
my $fetched = $sth->fetch;
|
||||
my $username = $fetched->[0];
|
||||
my $this_realname = $fetched->[1];
|
||||
if ( ($username ne $env_email) ||
|
||||
($this_realname ne $env_realname) ) {
|
||||
|
||||
$sth = $dbh->prepare("UPDATE profiles " .
|
||||
"SET login_name=?, " .
|
||||
"realname=? " .
|
||||
"WHERE userid=?");
|
||||
$sth->execute($env_email,
|
||||
($env_realname || $this_realname),
|
||||
$matched_userid);
|
||||
$sth->execute;
|
||||
}
|
||||
}
|
||||
|
||||
# Now we throw an error if the user has been disabled
|
||||
if ($disabledtext) {
|
||||
ThrowUserError("account_disabled",
|
||||
{'disabled_reason' => $disabledtext});
|
||||
}
|
||||
|
||||
return $matched_userid;
|
||||
|
||||
}
|
||||
|
||||
# This auth style does not allow the user to log out.
|
||||
sub can_logout { return 0; }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Env - Environment Variable Authentication
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Many external user authentication systems supply login information to CGI
|
||||
programs via environment variables. This module checks to see if those
|
||||
variables are populated and, if so, assumes authentication was successful and
|
||||
returns the user's ID, having automatically created a new profile if
|
||||
necessary.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::Auth>
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
How Auth Works
|
||||
==============
|
||||
Christian Reis <kiko@async.com.br>
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Authentication in Bugzilla is handled by a collection of modules that live in
|
||||
the Bugzilla::Auth package. These modules are organized hierarchically based
|
||||
upon their responsibility.
|
||||
|
||||
The authentication scheme is divided in two tasks: Login and Verify. Login
|
||||
involves gathering credentials from a user, while Verify validates them
|
||||
against an authentication service.
|
||||
|
||||
The Bugzilla parameters user_info_class and user_verify_class contain a
|
||||
list of Login and Verify modules, respectively.
|
||||
|
||||
Task: Login
|
||||
-----------
|
||||
|
||||
This task obtains user credentials based on a request. Examples of requests
|
||||
include CGI access from the Bugzilla web interface, email submissions and
|
||||
credentials supplied by standalone scripts.
|
||||
|
||||
Each type of Bugzilla front-end should have its own package. For instance,
|
||||
access via the Bugzilla web pages should go through Bugzilla::Auth::WWW.
|
||||
These packages would contain modules of their own to perform whatever extra
|
||||
functions are needed, like the CGI and Cookie modules in the case of WWW.
|
||||
|
||||
Task: Verify
|
||||
------------
|
||||
|
||||
This task validates user credentials against a user authentication service.
|
||||
|
||||
The default service in Bugzilla has been the database, which stores the
|
||||
login_name and cryptpasswd fields in the profiles table. An alternative means
|
||||
of validation, LDAP, is already supported, and other contributions would be
|
||||
appreciated.
|
||||
|
||||
The module layout is similar to the Login package, but there is no need for a
|
||||
sub-level as there is with Login request types.
|
||||
|
||||
Params
|
||||
------
|
||||
|
||||
There are two params that define behaviour for each authentication task. Each
|
||||
of them defines a comma-separated list of modules to be tried in order.
|
||||
|
||||
- user_info_class determines the module(s) used to obtain user
|
||||
credentials. This param is specific to the requests from Bugzilla web
|
||||
pages, so all of the listed modules live under
|
||||
Bugzilla::Auth::Login::WWW
|
||||
|
||||
- user_verify_class determines the module(s) used to verify credentials.
|
||||
This param is general and concerns the whole Bugzilla instance, since
|
||||
the same back end should be used regardless of what front end is used.
|
||||
|
||||
Responsibilities
|
||||
----------------
|
||||
|
||||
Bugzilla::Auth
|
||||
|
||||
This module is responsible for abstracting away as much as possible the
|
||||
login and logout tasks in Bugzilla.
|
||||
|
||||
It offers login() and logout() methods that are proxied to the selected
|
||||
login and verify packages.
|
||||
|
||||
Bugzilla::Auth::Login
|
||||
|
||||
This is a container to hold the various modules for each request type.
|
||||
|
||||
Bugzilla::Auth::Login::WWW
|
||||
|
||||
This module is responsible for abstracting away details of which web-based
|
||||
login modules exist and are in use. It offers login() and logout() methods
|
||||
that proxy through to whatever specific modules
|
||||
|
||||
Bugzilla::Auth::Verify
|
||||
|
||||
This module is responsible for abstracting away details of which
|
||||
credential verification modules exist, and should proxy calls through to
|
||||
them. There is a method that is particularly important, and which should
|
||||
be proxied through to the specific:
|
||||
|
||||
can_edit($type)
|
||||
|
||||
This method takes an argument that specifies what sort of change
|
||||
is being requested; the specific module should return 1 or 0 based
|
||||
on the fact that it implements or not the required change.
|
||||
|
||||
Current values for $type are "new" for new accounts, and "userid",
|
||||
"login_name", "realname" for their respective fields.
|
||||
|
||||
Specific Login Modules
|
||||
----------------------
|
||||
|
||||
WWW
|
||||
|
||||
The main authentication frontend; regular pages (CGIs) should use only
|
||||
this module. It offers a convenient frontend to the main functionality
|
||||
that CGIs need, using form parameters and cookies.
|
||||
|
||||
- Cookie
|
||||
|
||||
Implements part of the backend code that deals with browser
|
||||
cookies. It's actually tied in to DB.pm, so Cookie logins that use
|
||||
LDAP won't work at all.
|
||||
|
||||
LDAP
|
||||
|
||||
The other authentication module is LDAP-based; it is *only* used for
|
||||
password authentication and not for any other login-related task (it
|
||||
actually relies on the database to handle the profile information).
|
||||
|
||||
Legacy
|
||||
------
|
||||
|
||||
Bugzilla.pm
|
||||
|
||||
There is glue code that currently lives in the top-level module
|
||||
Bugzilla.pm; this module handles backwards-compatibility data that is used
|
||||
in a number of CGIs. This data has been slowly removed from the Bugzilla
|
||||
pages and eventually should go away completely, at which point Bugzilla.pm
|
||||
will be just a wrapper to conveniently offer template, cgi, dbh and user
|
||||
variables.
|
||||
|
||||
This module is meant to be used only by Bugzilla pages, and in the case of
|
||||
a reorganization which moves CGI-specific code to a subdirectory,
|
||||
Bugzilla.pm should go with it.
|
||||
|
||||
@@ -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>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth::Verify::DB;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
my $edit_options = {
|
||||
'new' => 1,
|
||||
'userid' => 0,
|
||||
'login_name' => 1,
|
||||
'realname' => 1,
|
||||
};
|
||||
|
||||
sub can_edit {
|
||||
my ($class, $type) = @_;
|
||||
return $edit_options->{$type};
|
||||
}
|
||||
|
||||
sub authenticate {
|
||||
my ($class, $username, $passwd) = @_;
|
||||
|
||||
return (AUTH_NODATA) unless defined $username && defined $passwd;
|
||||
|
||||
my $userid = Bugzilla::User::login_to_id($username);
|
||||
return (AUTH_LOGINFAILED) unless $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 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 = bz_crypt($password);
|
||||
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
|
||||
undef, $cryptpassword, $userid);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Verify::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,198 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Auth::Verify::LDAP;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::User;
|
||||
|
||||
use Net::LDAP;
|
||||
|
||||
my $edit_options = {
|
||||
'new' => 0,
|
||||
'userid' => 0,
|
||||
'login_name' => 0,
|
||||
'realname' => 0,
|
||||
};
|
||||
|
||||
sub can_edit {
|
||||
my ($class, $type) = @_;
|
||||
return $edit_options->{$type};
|
||||
}
|
||||
|
||||
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 " .
|
||||
$dbh->sql_istrcmp('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");
|
||||
}
|
||||
insert_new_user($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);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Verify::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,674 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>,
|
||||
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Alan Raetz <al_raetz@yahoo.com>
|
||||
# Jacob Steenhagen <jake@actex.net>
|
||||
# Matthew Tuck <matty@chariot.net.au>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::BugMail;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::BugMail::EXPORT = qw(
|
||||
PerformSubsts
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Config qw(:DEFAULT $datadir);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
use Mail::Mailer;
|
||||
use Mail::Header;
|
||||
|
||||
# We need these strings for the X-Bugzilla-Reasons header
|
||||
# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
|
||||
my %rel_names = (REL_ASSIGNEE , "AssignedTo",
|
||||
REL_REPORTER , "Reporter",
|
||||
REL_QA , "QAcontact",
|
||||
REL_CC , "CC",
|
||||
REL_VOTER , "Voter");
|
||||
|
||||
# This code is really ugly. It was a commandline interface, then it was moved.
|
||||
# This really needs to be cleaned at some point.
|
||||
|
||||
my %nomail;
|
||||
|
||||
my $sitespec = '@'.Param('urlbase');
|
||||
$sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
|
||||
$sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
|
||||
if ($2) {
|
||||
$sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
|
||||
}
|
||||
|
||||
# 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
|
||||
# This hash usually comes from the "mailrecipients" var in a template call.
|
||||
sub Send($;$) {
|
||||
my ($id, $forced) = (@_);
|
||||
|
||||
# This only works in a sub. Probably something to do with the
|
||||
# require abuse we do.
|
||||
GetVersionTable();
|
||||
|
||||
return ProcessOneBug($id, $forced);
|
||||
}
|
||||
|
||||
sub ProcessOneBug($$) {
|
||||
my ($id, $forced) = (@_);
|
||||
|
||||
my @headerlist;
|
||||
my %values;
|
||||
my %defmailhead;
|
||||
my %fielddescription;
|
||||
|
||||
my $msg = "";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
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);
|
||||
|
||||
# User IDs of people in various roles. More than one person can 'have' a
|
||||
# role, if the person in that role has changed, or people are watching.
|
||||
my $reporter = $values{'reporter'};
|
||||
my @assignees = ($values{'assigned_to'});
|
||||
my @qa_contacts = ($values{'qa_contact'});
|
||||
my @ccs = @{$dbh->selectcol_arrayref("SELECT who
|
||||
FROM cc WHERE bug_id = $id")};
|
||||
|
||||
# Include the people passed in as being in particular roles.
|
||||
# This can include people who used to hold those roles.
|
||||
# At this point, we don't care if there are duplicates in these arrays.
|
||||
my $changer = $forced->{'changer'};
|
||||
if ($forced->{'owner'}) {
|
||||
push (@assignees, DBNameToIdAndCheck($forced->{'owner'}));
|
||||
}
|
||||
|
||||
if ($forced->{'qacontact'}) {
|
||||
push (@qa_contacts, DBNameToIdAndCheck($forced->{'qacontact'}));
|
||||
}
|
||||
|
||||
if ($forced->{'cc'}) {
|
||||
foreach my $cc (@{$forced->{'cc'}}) {
|
||||
push(@ccs, DBNameToIdAndCheck($cc));
|
||||
}
|
||||
}
|
||||
|
||||
# Convert to names, for later display
|
||||
$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'} = format_time_decimal($values{'estimated_time'});
|
||||
|
||||
if ($values{'deadline'}) {
|
||||
$values{'deadline'} = time2str("%Y-%m-%d", str2time($values{'deadline'}));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
# If lastdiffed is NULL, then we don't limit the search on time.
|
||||
my $when_restriction = $start ?
|
||||
" AND bug_when > '$start' AND bug_when <= '$end'" : '';
|
||||
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 " .
|
||||
$when_restriction .
|
||||
"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 = format_time_decimal($old);
|
||||
$new = format_time_decimal($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') " .
|
||||
$when_restriction .
|
||||
"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
|
||||
###########################################################################
|
||||
|
||||
# A user_id => roles hash to keep track of people.
|
||||
my %recipients;
|
||||
|
||||
# Now we work out all the people involved with this bug, and note all of
|
||||
# the relationships in a hash. The keys are userids, the values are an
|
||||
# array of role constants.
|
||||
|
||||
# Voters
|
||||
my $voters =
|
||||
$dbh->selectcol_arrayref("SELECT who FROM votes WHERE bug_id = $id");
|
||||
|
||||
push(@{$recipients{$_}}, REL_VOTER) foreach (@$voters);
|
||||
|
||||
# CCs
|
||||
push(@{$recipients{$_}}, REL_CC) foreach (@ccs);
|
||||
|
||||
# Reporter (there's only ever one)
|
||||
push(@{$recipients{$reporter}}, REL_REPORTER);
|
||||
|
||||
# QA Contact
|
||||
if (Param('useqacontact')) {
|
||||
foreach (@qa_contacts) {
|
||||
# QA Contact can be blank; ignore it if so.
|
||||
push(@{$recipients{$_}}, REL_QA) if $_;
|
||||
}
|
||||
}
|
||||
|
||||
# Assignee
|
||||
push(@{$recipients{$_}}, REL_ASSIGNEE) foreach (@assignees);
|
||||
|
||||
# The last relevant set of people are those who are being removed from
|
||||
# their roles in this change. We get their names out of the diffs.
|
||||
foreach my $ref (@diffs) {
|
||||
my ($who, $what, $when, $old, $new) = (@$ref);
|
||||
if ($old) {
|
||||
# You can't stop being the reporter, and mail isn't sent if you
|
||||
# remove your vote.
|
||||
# Ignore people whose user account has been deleted or renamed.
|
||||
if ($what eq "CC") {
|
||||
foreach my $cc_user (split(/[\s,]+/, $old)) {
|
||||
my $uid = login_to_id($cc_user);
|
||||
push(@{$recipients{$uid}}, REL_CC) if $uid;
|
||||
}
|
||||
}
|
||||
elsif ($what eq "QAContact") {
|
||||
my $uid = login_to_id($old);
|
||||
push(@{$recipients{$uid}}, REL_QA) if $uid;
|
||||
}
|
||||
elsif ($what eq "AssignedTo") {
|
||||
my $uid = login_to_id($old);
|
||||
push(@{$recipients{$uid}}, REL_ASSIGNEE) if $uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Param("supportwatchers")) {
|
||||
# Find all those user-watching anyone on the current list, who is not
|
||||
# on it already themselves.
|
||||
my $involved = join(",", keys %recipients);
|
||||
|
||||
my $userwatchers =
|
||||
$dbh->selectall_arrayref("SELECT watcher, watched FROM watch
|
||||
WHERE watched IN ($involved)
|
||||
AND watcher NOT IN ($involved)");
|
||||
|
||||
# Mark these people as having the role of the person they are watching
|
||||
foreach my $watch (@$userwatchers) {
|
||||
$recipients{$watch->[0]} = $recipients{$watch->[1]};
|
||||
}
|
||||
}
|
||||
|
||||
# We now have a complete set of all the users, and their relationships to
|
||||
# the bug in question. However, we are not necessarily going to mail them
|
||||
# all - there are preferences, permissions checks and all sorts to do yet.
|
||||
my @sent;
|
||||
my @excluded;
|
||||
|
||||
foreach my $user_id (keys %recipients) {
|
||||
my @rels_which_want;
|
||||
my $sent_mail = 0;
|
||||
|
||||
my $user = new Bugzilla::User($user_id);
|
||||
|
||||
if ($user->can_see_bug($id))
|
||||
{
|
||||
# Go through each role the user has and see if they want mail in
|
||||
# that role.
|
||||
foreach my $relationship (@{$recipients{$user_id}}) {
|
||||
if ($user->wants_bug_mail($id,
|
||||
$relationship,
|
||||
\@diffs,
|
||||
$newcomments,
|
||||
$changer))
|
||||
{
|
||||
push(@rels_which_want, $relationship);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(@rels_which_want)) {
|
||||
# So the user exists, can see the bug, and wants mail in at least
|
||||
# one role. But do we want to send it to them?
|
||||
|
||||
# If we are using insiders, and the comment is private, only send
|
||||
# to insiders
|
||||
my $insider_ok = 1;
|
||||
$insider_ok = 0 if (Param("insidergroup") &&
|
||||
($anyprivate != 0) &&
|
||||
(!$user->groups->{Param("insidergroup")}));
|
||||
|
||||
# We shouldn't send mail if this is a dependency mail (i.e. there
|
||||
# is something in @depbugs), and any of the depending bugs are not
|
||||
# visible to the user. This is to avoid leaking the summaries of
|
||||
# confidential bugs.
|
||||
my $dep_ok = 1;
|
||||
foreach my $dep_id (@depbugs) {
|
||||
if (!$user->can_see_bug($dep_id)) {
|
||||
$dep_ok = 0;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure the user isn't in the nomail list, and the insider and
|
||||
# dep checks passed.
|
||||
if ((!$nomail{$user->login}) &&
|
||||
$insider_ok &&
|
||||
$dep_ok)
|
||||
{
|
||||
# OK, OK, if we must. Email the user.
|
||||
$sent_mail = sendMail($user,
|
||||
\@headerlist,
|
||||
\@rels_which_want,
|
||||
\%values,
|
||||
\%defmailhead,
|
||||
\%fielddescription,
|
||||
\@diffparts,
|
||||
$newcomments,
|
||||
$anyprivate,
|
||||
$start,
|
||||
$id);
|
||||
}
|
||||
}
|
||||
|
||||
if ($sent_mail) {
|
||||
push(@sent, $user->login);
|
||||
}
|
||||
else {
|
||||
push(@excluded, $user->login);
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do("UPDATE bugs SET lastdiffed = '$end' WHERE bug_id = $id");
|
||||
|
||||
return {'sent' => \@sent, 'excluded' => \@excluded};
|
||||
}
|
||||
|
||||
sub sendMail($$$$$$$$$$$$) {
|
||||
my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,
|
||||
$diffRef, $newcomments, $anyprivate, $start,
|
||||
$id) = @_;
|
||||
|
||||
my %values = %$valueRef;
|
||||
my @headerlist = @$hlRef;
|
||||
my %mailhead = %$dmhRef;
|
||||
my %fielddescription = %$fdRef;
|
||||
my @diffparts = @$diffRef;
|
||||
my $head = "";
|
||||
|
||||
foreach my $f (@headerlist) {
|
||||
if ($mailhead{$f}) {
|
||||
my $value = $values{$f};
|
||||
# If there isn't anything to show, don't include this header
|
||||
if (! $value) {
|
||||
next;
|
||||
}
|
||||
# Only send estimated_time if it is enabled and the user is in the group
|
||||
if (($f ne 'estimated_time' && $f ne 'deadline') ||
|
||||
$user->groups->{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' ||
|
||||
$diff->{'fieldname'} eq 'deadline')){
|
||||
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 0;
|
||||
}
|
||||
|
||||
# XXX: This needs making localisable, probably by passing the role to
|
||||
# the email template and letting it choose the text.
|
||||
my $reasonsbody = "------- You are receiving this mail because: -------\n";
|
||||
|
||||
foreach my $relationship (@$relRef) {
|
||||
if ($relationship == REL_ASSIGNEE) {
|
||||
$reasonsbody .= "You are the assignee for the bug, or are watching the assignee.\n";
|
||||
} elsif ($relationship == REL_REPORTER) {
|
||||
$reasonsbody .= "You reported the bug, or are watching the reporter.\n";
|
||||
} elsif ($relationship == REL_QA) {
|
||||
$reasonsbody .= "You are the QA contact for the bug, or are watching the QA contact.\n";
|
||||
} elsif ($relationship == REL_CC) {
|
||||
$reasonsbody .= "You are on the CC list for the bug, or are watching someone who is.\n";
|
||||
} elsif ($relationship == REL_VOTER) {
|
||||
$reasonsbody .= "You are a voter for the bug, or are watching someone who is.\n";
|
||||
}
|
||||
}
|
||||
|
||||
my $isnew = !$start;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$substs{"neworchanged"} = $isnew ? ' New: ' : '';
|
||||
$substs{"to"} = $user->email;
|
||||
$substs{"cc"} = '';
|
||||
$substs{"bugid"} = $id;
|
||||
if ($isnew) {
|
||||
$substs{"diffs"} = $head . "\n\n" . $newcomments;
|
||||
} else {
|
||||
$substs{"diffs"} = $difftext . "\n\n" . $newcomments;
|
||||
}
|
||||
$substs{"product"} = $values{'product'};
|
||||
$substs{"component"} = $values{'component'};
|
||||
$substs{"summary"} = $values{'short_desc'};
|
||||
$substs{"reasonsheader"} = join(" ", map { $rel_names{$_} } @$relRef);
|
||||
$substs{"reasonsbody"} = $reasonsbody;
|
||||
$substs{"space"} = " ";
|
||||
if ($isnew) {
|
||||
$substs{'threadingmarker'} = "Message-ID: <bug-$id-" .
|
||||
$user->id . "$sitespec>";
|
||||
} else {
|
||||
$substs{'threadingmarker'} = "In-Reply-To: <bug-$id-" .
|
||||
$user->id . "$sitespec>";
|
||||
}
|
||||
|
||||
my $template = Param("newchangedmail");
|
||||
|
||||
my $msg = PerformSubsts($template, \%substs);
|
||||
|
||||
MessageToMTA($msg);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub MessageToMTA ($) {
|
||||
my ($msg) = (@_);
|
||||
return if (Param('mail_delivery_method') eq "none");
|
||||
|
||||
if (Param("mail_delivery_method") eq "sendmail" && $^O =~ /MSWin32/i) {
|
||||
open(SENDMAIL, '|' . SENDMAIL_EXE . ' -t -i') ||
|
||||
die "Failed to execute " . SENDMAIL_EXE . ": $!\n";
|
||||
print SENDMAIL $msg;
|
||||
close SENDMAIL;
|
||||
return;
|
||||
}
|
||||
|
||||
my @args;
|
||||
if (Param("mail_delivery_method") eq "sendmail" && !Param("sendmailnow")) {
|
||||
push @args, "-ODeliveryMode=deferred";
|
||||
}
|
||||
if (Param("mail_delivery_method") eq "smtp") {
|
||||
push @args, Server => Param("smtpserver");
|
||||
}
|
||||
my $mailer = new Mail::Mailer Param("mail_delivery_method"), @args;
|
||||
if (Param("mail_delivery_method") eq "testfile") {
|
||||
$Mail::Mailer::testfile::config{outfile} = "$datadir/mailer.testfile";
|
||||
}
|
||||
|
||||
$msg =~ /(.*?)\n\n(.*)/ms;
|
||||
my @header_lines = split(/\n/, $1);
|
||||
my $body = $2;
|
||||
|
||||
my $headers = new Mail::Header \@header_lines, Modify => 0;
|
||||
$mailer->open($headers->header_hashref);
|
||||
print $mailer $body;
|
||||
$mailer->close;
|
||||
}
|
||||
|
||||
# Performs substitutions for sending out email with variables in it,
|
||||
# or for inserting a parameter into some other string.
|
||||
#
|
||||
# Takes a string and a reference to a hash containing substitution
|
||||
# variables and their values.
|
||||
#
|
||||
# If the hash is not specified, or if we need to substitute something
|
||||
# that's not in the hash, then we will use parameters to do the
|
||||
# substitution instead.
|
||||
#
|
||||
# Substitutions are always enclosed with '%' symbols. So they look like:
|
||||
# %some_variable_name%. If "some_variable_name" is a key in the hash, then
|
||||
# its value will be placed into the string. If it's not a key in the hash,
|
||||
# then the value of the parameter called "some_variable_name" will be placed
|
||||
# into the string.
|
||||
sub PerformSubsts ($;$) {
|
||||
my ($str, $substs) = (@_);
|
||||
$str =~ s/%([a-z]*)%/(defined $substs->{$1} ? $substs->{$1} : Param($1))/eg;
|
||||
return $str;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,310 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::CGI;
|
||||
|
||||
use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH);
|
||||
|
||||
use base qw(CGI);
|
||||
|
||||
use Bugzilla::Error;
|
||||
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('');
|
||||
|
||||
# Redirect to SSL if required
|
||||
if (Param('sslbase') ne '' and Param('ssl') eq 'always' and i_am_cgi()) {
|
||||
$self->require_https(Param('sslbase'));
|
||||
}
|
||||
|
||||
# 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;
|
||||
|
||||
# Move the param list into a hash for easier handling.
|
||||
my %paramhash;
|
||||
my @paramlist;
|
||||
my ($key, $value);
|
||||
while ($key = shift) {
|
||||
$value = shift;
|
||||
$paramhash{$key} = $value;
|
||||
}
|
||||
|
||||
# Complain if -value is not given or empty (bug 268146).
|
||||
if (!exists($paramhash{'-value'}) || !$paramhash{'-value'}) {
|
||||
ThrowCodeError('cookies_need_value');
|
||||
}
|
||||
|
||||
# Add the default path and the domain in.
|
||||
$paramhash{'-path'} = Param('cookiepath');
|
||||
$paramhash{'-domain'} = Param('cookiedomain') if Param('cookiedomain');
|
||||
|
||||
# Move the param list back into an array for the call to cookie().
|
||||
foreach (keys(%paramhash)) {
|
||||
unshift(@paramlist, $_ => $paramhash{$_});
|
||||
}
|
||||
|
||||
push(@{$self->{'Bugzilla_cookie_list'}}, $self->cookie(@paramlist));
|
||||
}
|
||||
|
||||
# Cookies are removed by setting an expiry date in the past.
|
||||
# This method is a send_cookie wrapper doing exactly this.
|
||||
sub remove_cookie {
|
||||
my $self = shift;
|
||||
my ($cookiename) = (@_);
|
||||
|
||||
# Expire the cookie, giving a non-empty dummy value (bug 268146).
|
||||
$self->send_cookie('-name' => $cookiename,
|
||||
'-expires' => 'Tue, 15-Sep-1998 21:49:00 GMT',
|
||||
'-value' => 'X');
|
||||
}
|
||||
|
||||
# Redirect to https if required
|
||||
sub require_https {
|
||||
my $self = shift;
|
||||
if ($self->protocol ne 'https') {
|
||||
my $url = shift;
|
||||
if (defined $url) {
|
||||
$url .= $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
|
||||
} else {
|
||||
$url = $self->self_url;
|
||||
$url =~ s/^http:/https:/i;
|
||||
}
|
||||
print $self->redirect(-location => $url);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::CGI - CGI handling for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::CGI;
|
||||
|
||||
my $cgi = new Bugzilla::CGI();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package inherits from the standard CGI module, to provide additional
|
||||
Bugzilla-specific functionality. In general, see L<the CGI.pm docs|CGI> for
|
||||
documention.
|
||||
|
||||
=head1 CHANGES FROM L<CGI.PM|CGI>
|
||||
|
||||
Bugzilla::CGI has some differences from L<CGI.pm|CGI>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<cgi_error> is automatically checked
|
||||
|
||||
After creating the CGI object, C<Bugzilla::CGI> automatically checks
|
||||
I<cgi_error>, and throws a CodeError if a problem is detected.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ADDITIONAL FUNCTIONS
|
||||
|
||||
I<Bugzilla::CGI> also includes additional functions.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<canonicalise_query(@exclude)>
|
||||
|
||||
This returns a sorted string of the parameters, suitable for use in a url.
|
||||
Values in C<@exclude> are not included in the result.
|
||||
|
||||
=item C<send_cookie>
|
||||
|
||||
This routine is identical to the cookie generation part of CGI.pm's C<cookie>
|
||||
routine, except that it knows about Bugzilla's cookie_path and cookie_domain
|
||||
parameters and takes them into account if necessary.
|
||||
This should be used by all Bugzilla code (instead of C<cookie> or the C<-cookie>
|
||||
argument to C<header>), so that under mod_perl the headers can be sent
|
||||
correctly, using C<print> or the mod_perl APIs as appropriate.
|
||||
|
||||
To remove (expire) a cookie, use C<remove_cookie>.
|
||||
|
||||
=item C<remove_cookie>
|
||||
|
||||
This is a wrapper around send_cookie, setting an expiry date in the past,
|
||||
effectively removing the cookie.
|
||||
|
||||
As its only argument, it takes the name of the cookie to expire.
|
||||
|
||||
=item C<require_https($baseurl)>
|
||||
|
||||
This routine checks if the current page is being served over https, and
|
||||
redirects to the https protocol if required, retaining QUERY_STRING.
|
||||
|
||||
It takes an option argument which will be used as the base URL. If $baseurl
|
||||
is not provided, the current URL is used.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<CGI|CGI>, L<CGI::Cookie|CGI::Cookie>
|
||||
@@ -1,372 +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 = @_;
|
||||
|
||||
# Get the current size of the series; required for adding Grand Total later
|
||||
my $current_size = scalar($self->getSeriesIDs());
|
||||
|
||||
# Count the number of added series
|
||||
my $added = 0;
|
||||
# Create new Series and push them on to the list of lines.
|
||||
# Note that new lines have no label; the display template is responsible
|
||||
# for inventing something sensible.
|
||||
foreach my $series_id (@series_ids) {
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
if ($series) {
|
||||
push(@{$self->{'lines'}}, [$series]);
|
||||
push(@{$self->{'labels'}}, "");
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
|
||||
# If we are going from < 2 to >= 2 series, add the Grand Total line.
|
||||
if (!$self->{'gt'}) {
|
||||
if ($current_size < 2 &&
|
||||
$current_size + $added >= 2)
|
||||
{
|
||||
$self->{'gt'} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are removed from it.
|
||||
sub remove {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
foreach my $line_id (@line_ids) {
|
||||
if ($line_id == 65536) {
|
||||
# Magic value - delete Grand Total.
|
||||
$self->{'gt'} = 0;
|
||||
}
|
||||
else {
|
||||
delete($self->{'lines'}->[$line_id]);
|
||||
delete($self->{'labels'}->[$line_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are summed.
|
||||
sub sum {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
# We can't add the Grand Total to things.
|
||||
@line_ids = grep(!/^65536$/, @line_ids);
|
||||
|
||||
# We can't add less than two things.
|
||||
return if scalar(@line_ids) < 2;
|
||||
|
||||
my @series;
|
||||
my $label = "";
|
||||
my $biggestlength = 0;
|
||||
|
||||
# We rescue the Series objects of all the series involved in the sum.
|
||||
foreach my $line_id (@line_ids) {
|
||||
my @line = @{$self->{'lines'}->[$line_id]};
|
||||
|
||||
foreach my $series (@line) {
|
||||
push(@series, $series);
|
||||
}
|
||||
|
||||
# We keep the label that labels the line with the most series.
|
||||
if (scalar(@line) > $biggestlength) {
|
||||
$biggestlength = scalar(@line);
|
||||
$label = $self->{'labels'}->[$line_id];
|
||||
}
|
||||
}
|
||||
|
||||
$self->remove(@line_ids);
|
||||
|
||||
push(@{$self->{'lines'}}, \@series);
|
||||
push(@{$self->{'labels'}}, $label);
|
||||
}
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
$self->{'_data'} ||= $self->readData();
|
||||
return $self->{'_data'};
|
||||
}
|
||||
|
||||
# Convert the Chart's data into a plottable form in $self->{'_data'}.
|
||||
sub readData {
|
||||
my $self = shift;
|
||||
my @data;
|
||||
|
||||
# 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 " . $dbh->sql_to_days('series_date') . " - " .
|
||||
$dbh->sql_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 " .
|
||||
$dbh->sql_group_by('series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name'));
|
||||
foreach my $series (@$serieses) {
|
||||
my ($cat, $subcat, $name, $series_id) = @$series;
|
||||
$cats{$cat}{$subcat}{$name} = $series_id;
|
||||
}
|
||||
|
||||
return \%cats;
|
||||
}
|
||||
|
||||
sub generateDateProgression {
|
||||
my ($datefrom, $dateto) = @_;
|
||||
my @progression;
|
||||
|
||||
$dateto = $dateto || time();
|
||||
my $oneday = 60 * 60 * 24;
|
||||
|
||||
# When the from and to dates are converted by str2time(), you end up with
|
||||
# a time figure representing midnight at the beginning of that day. We
|
||||
# adjust the times by 1/3 and 2/3 of a day respectively to prevent
|
||||
# edge conditions in time2str().
|
||||
$datefrom += $oneday / 3;
|
||||
$dateto += (2 * $oneday) / 3;
|
||||
|
||||
while ($datefrom < $dateto) {
|
||||
push (@progression, &::time2str("%Y-%m-%d", $datefrom));
|
||||
$datefrom += $oneday;
|
||||
}
|
||||
|
||||
return \@progression;
|
||||
}
|
||||
|
||||
sub dump {
|
||||
my $self = shift;
|
||||
|
||||
# Make sure we've read in our data
|
||||
my $data = $self->data;
|
||||
|
||||
require Data::Dumper;
|
||||
print "<pre>Bugzilla::Chart object:\n";
|
||||
print Data::Dumper::Dumper($self);
|
||||
print "</pre>";
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,461 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jake <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
package Bugzilla::Config;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
# 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 showdependencygraph.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 dependency
|
||||
# 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 $attachdir = "$datadir/attachments";
|
||||
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_driver $db_host $db_port $db_name $db_user $db_pass $db_sock)],
|
||||
locations => [qw($libpath $localconfig $attachdir
|
||||
$datadir $templatedir $webdotdir)],
|
||||
);
|
||||
Exporter::export_ok_tags('admin', 'db', 'locations');
|
||||
|
||||
# Bugzilla version
|
||||
$Bugzilla::Config::VERSION = "2.20";
|
||||
|
||||
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 usebrowserinfo to defaultplatform/defaultopsys combo
|
||||
if (exists $param{'usebrowserinfo'}) {
|
||||
if (!$param{'usebrowserinfo'}) {
|
||||
if (!exists $param{'defaultplatform'}) {
|
||||
$param{'defaultplatform'} = 'Other';
|
||||
}
|
||||
if (!exists $param{'defaultopsys'}) {
|
||||
$param{'defaultopsys'} = 'Other';
|
||||
}
|
||||
}
|
||||
delete $param{'usebrowserinfo'};
|
||||
}
|
||||
|
||||
# Change from a boolean for quips to multi-state
|
||||
if (exists $param{'usequip'} && !exists $param{'enablequips'}) {
|
||||
$param{'enablequips'} = $param{'usequip'} ? 'on' : 'off';
|
||||
delete $param{'usequip'};
|
||||
}
|
||||
|
||||
# Change from old product groups to controls for group_control_map
|
||||
# 2002-10-14 bug 147275 bugreport@peshkin.net
|
||||
if (exists $param{'usebuggroups'} && !exists $param{'makeproductgroups'}) {
|
||||
$param{'makeproductgroups'} = $param{'usebuggroups'};
|
||||
}
|
||||
if (exists $param{'usebuggroupsentry'}
|
||||
&& !exists $param{'useentrygroupdefault'}) {
|
||||
$param{'useentrygroupdefault'} = $param{'usebuggroupsentry'};
|
||||
}
|
||||
|
||||
# Modularise auth code
|
||||
if (exists $param{'useLDAP'} && !exists $param{'loginmethod'}) {
|
||||
$param{'loginmethod'} = $param{'useLDAP'} ? "LDAP" : "DB";
|
||||
}
|
||||
|
||||
# set verify method to whatever loginmethod was
|
||||
if (exists $param{'loginmethod'} && !exists $param{'user_verify_class'}) {
|
||||
$param{'user_verify_class'} = $param{'loginmethod'};
|
||||
delete $param{'loginmethod'};
|
||||
}
|
||||
|
||||
# Remove quip-display control from parameters
|
||||
# and give it to users via User Settings (Bug 41972)
|
||||
if ( exists $param{'enablequips'}
|
||||
&& !exists $param{'quip_list_entry_control'})
|
||||
{
|
||||
my $new_value;
|
||||
($param{'enablequips'} eq 'on') && do {$new_value = 'open';};
|
||||
($param{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
|
||||
($param{'enablequips'} eq 'frozen') && do {$new_value = 'closed';};
|
||||
($param{'enablequips'} eq 'off') && do {$new_value = 'closed';};
|
||||
$param{'quip_list_entry_control'} = $new_value;
|
||||
delete $param{'enablequips'};
|
||||
}
|
||||
|
||||
# --- 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 if and only if
|
||||
# we don't have a webserver group. Call this function to do this.
|
||||
# This will become a private function once all the datafile handling stuff
|
||||
# moves into this package
|
||||
|
||||
# This sub is not perldoc'd for that reason - noone should know about it
|
||||
sub ChmodDataFile {
|
||||
my ($file, $mask) = @_;
|
||||
my $perm = 0770;
|
||||
if ((stat($datadir))[2] & 0002) {
|
||||
$perm = 0777;
|
||||
}
|
||||
$perm = $perm & $mask;
|
||||
chmod $perm,$file;
|
||||
}
|
||||
|
||||
sub check_multi {
|
||||
my ($value, $param) = (@_);
|
||||
|
||||
if ($param->{'type'} eq "s") {
|
||||
unless (scalar(grep {$_ eq $value} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$value' for single-select list param '$param->{'name'}'";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
elsif ($param->{'type'} eq "m") {
|
||||
foreach my $chkParam (@$value) {
|
||||
unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return "Invalid param type '$param->{'type'}' for check_multi(); " .
|
||||
"contact your Bugzilla administrator";
|
||||
}
|
||||
}
|
||||
|
||||
sub check_numeric {
|
||||
my ($value) = (@_);
|
||||
if ($value !~ /^[0-9]+$/) {
|
||||
return "must be a numeric value";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_regexp {
|
||||
my ($value) = (@_);
|
||||
eval { qr/$value/ };
|
||||
return $@;
|
||||
}
|
||||
|
||||
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,241 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jake <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
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
|
||||
|
||||
GRANT_DIRECT
|
||||
GRANT_DERIVED
|
||||
GRANT_REGEXP
|
||||
|
||||
GROUP_MEMBERSHIP
|
||||
GROUP_BLESS
|
||||
GROUP_VISIBLE
|
||||
|
||||
MAILTO_USER
|
||||
MAILTO_GROUP
|
||||
|
||||
DEFAULT_COLUMN_LIST
|
||||
DEFAULT_QUERY_NAME
|
||||
|
||||
COMMENT_COLS
|
||||
|
||||
DERIVE_GROUPS_TABLES_ALREADY_LOCKED
|
||||
|
||||
UNLOCK_ABORT
|
||||
|
||||
RELATIONSHIPS
|
||||
REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER
|
||||
REL_ANY
|
||||
|
||||
POS_EVENTS
|
||||
EVT_OTHER EVT_ADDED_REMOVED EVT_COMMENT EVT_ATTACHMENT EVT_ATTACHMENT_DATA
|
||||
EVT_PROJ_MANAGEMENT EVT_OPENED_CLOSED EVT_KEYWORD EVT_CC
|
||||
|
||||
NEG_EVENTS
|
||||
EVT_UNCONFIRMED EVT_CHANGED_BY_ME
|
||||
|
||||
GLOBAL_EVENTS
|
||||
EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
|
||||
|
||||
FULLTEXT_BUGLIST_LIMIT
|
||||
|
||||
SENDMAIL_EXE
|
||||
);
|
||||
|
||||
@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" ,
|
||||
"rss" => "application/rss+xml" ,
|
||||
"xml" => "application/xml" ,
|
||||
"js" => "application/x-javascript" ,
|
||||
"csv" => "text/plain" ,
|
||||
"png" => "image/png" ,
|
||||
"ics" => "text/calendar" ,
|
||||
};
|
||||
|
||||
use constant GRANT_DIRECT => 0;
|
||||
use constant GRANT_DERIVED => 1;
|
||||
use constant GRANT_REGEXP => 2;
|
||||
|
||||
use constant GROUP_MEMBERSHIP => 0;
|
||||
use constant GROUP_BLESS => 1;
|
||||
use constant GROUP_VISIBLE => 2;
|
||||
|
||||
use constant MAILTO_USER => 0;
|
||||
use constant MAILTO_GROUP => 1;
|
||||
|
||||
# The default list of columns for buglist.cgi
|
||||
use constant DEFAULT_COLUMN_LIST => (
|
||||
"bug_severity", "priority", "rep_platform","assigned_to",
|
||||
"bug_status", "resolution", "short_short_desc"
|
||||
);
|
||||
|
||||
# Used by query.cgi and buglist.cgi as the named-query name
|
||||
# for the default settings.
|
||||
use constant DEFAULT_QUERY_NAME => '(Default query)';
|
||||
|
||||
# The column length for displayed (and wrapped) bug comments.
|
||||
use constant COMMENT_COLS => 80;
|
||||
|
||||
# Used to indicate to User::new and User::new_from_login calls
|
||||
# that the derive_groups tables are already locked
|
||||
use constant DERIVE_GROUPS_TABLES_ALREADY_LOCKED => 1;
|
||||
|
||||
# used by Bugzilla::DB to indicate that tables are being unlocked
|
||||
# because of error
|
||||
use constant UNLOCK_ABORT => 1;
|
||||
|
||||
use constant REL_ASSIGNEE => 0;
|
||||
use constant REL_QA => 1;
|
||||
use constant REL_REPORTER => 2;
|
||||
use constant REL_CC => 3;
|
||||
use constant REL_VOTER => 4;
|
||||
|
||||
use constant RELATIONSHIPS => REL_ASSIGNEE, REL_QA, REL_REPORTER, REL_CC,
|
||||
REL_VOTER;
|
||||
|
||||
# Used for global events like EVT_FLAG_REQUESTED
|
||||
use constant REL_ANY => 100;
|
||||
|
||||
# There are two sorts of event - positive and negative. Positive events are
|
||||
# those for which the user says "I want mail if this happens." Negative events
|
||||
# are those for which the user says "I don't want mail if this happens."
|
||||
#
|
||||
# Exactly when each event fires is defined in wants_bug_mail() in User.pm; I'm
|
||||
# not commenting them here in case the comments and the code get out of sync.
|
||||
use constant EVT_OTHER => 0;
|
||||
use constant EVT_ADDED_REMOVED => 1;
|
||||
use constant EVT_COMMENT => 2;
|
||||
use constant EVT_ATTACHMENT => 3;
|
||||
use constant EVT_ATTACHMENT_DATA => 4;
|
||||
use constant EVT_PROJ_MANAGEMENT => 5;
|
||||
use constant EVT_OPENED_CLOSED => 6;
|
||||
use constant EVT_KEYWORD => 7;
|
||||
use constant EVT_CC => 8;
|
||||
|
||||
use constant POS_EVENTS => EVT_OTHER, EVT_ADDED_REMOVED, EVT_COMMENT,
|
||||
EVT_ATTACHMENT, EVT_ATTACHMENT_DATA,
|
||||
EVT_PROJ_MANAGEMENT, EVT_OPENED_CLOSED, EVT_KEYWORD,
|
||||
EVT_CC;
|
||||
|
||||
use constant EVT_UNCONFIRMED => 50;
|
||||
use constant EVT_CHANGED_BY_ME => 51;
|
||||
|
||||
use constant NEG_EVENTS => EVT_UNCONFIRMED, EVT_CHANGED_BY_ME;
|
||||
|
||||
# These are the "global" flags, which aren't tied to a particular relationship.
|
||||
# and so use REL_ANY.
|
||||
use constant EVT_FLAG_REQUESTED => 100; # Flag has been requested of me
|
||||
use constant EVT_REQUESTED_FLAG => 101; # I have requested a flag
|
||||
|
||||
use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
|
||||
|
||||
# Number of bugs to return in a buglist when performing
|
||||
# a fulltext search.
|
||||
use constant FULLTEXT_BUGLIST_LIMIT => 200;
|
||||
|
||||
# Path to sendmail.exe (Windows only)
|
||||
use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
|
||||
|
||||
1;
|
||||
@@ -1,646 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Dave Miller <davem00@aol.com>
|
||||
# Gayathri Swaminath <gayathrik00@aol.com>
|
||||
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Mysql - Bugzilla database compatibility layer for MySQL
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with MySQL specific
|
||||
implementation. It is instantiated by the Bugzilla::DB module and should never
|
||||
be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Mysql;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
use constant REQUIRED_VERSION => '3.23.41';
|
||||
use constant PROGRAM_NAME => 'MySQL';
|
||||
use constant MODULE_NAME => 'Mysql';
|
||||
use constant DBD_VERSION => '2.9003';
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port, $sock) = @_;
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:mysql:host=$host;database=$dbname";
|
||||
$dsn .= ";port=$port" if $port;
|
||||
$dsn .= ";mysql_socket=$sock" if $sock;
|
||||
|
||||
my $self = $class->db_new($dsn, $user, $pass);
|
||||
|
||||
# all class local variables stored in DBI derived class needs to have
|
||||
# a prefix 'private_'. See DBI documentation.
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# when last_insert_id() is supported on MySQL by lowest DBI/DBD version
|
||||
# required by Bugzilla, this implementation can be removed.
|
||||
sub bz_last_key {
|
||||
my ($self) = @_;
|
||||
|
||||
my ($last_insert_id) = $self->selectrow_array('SELECT LAST_INSERT_ID()');
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
return "REGEXP";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
return "NOT REGEXP";
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if (defined($offset)) {
|
||||
return "LIMIT $offset, $limit";
|
||||
} else {
|
||||
return "LIMIT $limit";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
return 'CONCAT(' . join(', ', @params) . ')';
|
||||
}
|
||||
|
||||
sub sql_fulltext_search {
|
||||
my ($self, $column, $text) = @_;
|
||||
|
||||
return "MATCH($column) AGAINST($text)";
|
||||
}
|
||||
|
||||
sub sql_istring {
|
||||
my ($self, $string) = @_;
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return "TO_DAYS($date)";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
return "DATE_FORMAT($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval) = @_;
|
||||
|
||||
return "INTERVAL $interval";
|
||||
}
|
||||
|
||||
sub sql_position {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
|
||||
# mysql 4.0.1 and lower do not support CAST
|
||||
# mysql 3.*.* had a case-sensitive INSTR
|
||||
# (checksetup has a check for unsupported versions)
|
||||
my $server_version = $self->bz_server_version;
|
||||
if ($server_version =~ /^3\./) {
|
||||
return "INSTR($text, $fragment)";
|
||||
} else {
|
||||
return "INSTR(CAST($text AS BINARY), CAST($fragment AS BINARY))";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_group_by {
|
||||
my ($self, $needed_columns, $optional_columns) = @_;
|
||||
|
||||
# MySQL allows to specify all columns as ANSI SQL requires, but also
|
||||
# allow you to specify just minimal subset to get unique result.
|
||||
# According to MySQL documentation, the less columns you specify
|
||||
# the faster the query runs.
|
||||
return "GROUP BY $needed_columns";
|
||||
}
|
||||
|
||||
|
||||
sub bz_lock_tables {
|
||||
my ($self, @tables) = @_;
|
||||
|
||||
my $list = join(', ', @tables);
|
||||
# Check first if there was no lock before
|
||||
if ($self->{private_bz_tables_locked}) {
|
||||
ThrowCodeError("already_locked", { current => $self->{private_bz_tables_locked},
|
||||
new => $list });
|
||||
} else {
|
||||
$self->do('LOCK TABLE ' . $list);
|
||||
|
||||
$self->{private_bz_tables_locked} = $list;
|
||||
}
|
||||
}
|
||||
|
||||
sub bz_unlock_tables {
|
||||
my ($self, $abort) = @_;
|
||||
|
||||
# Check first if there was previous matching lock
|
||||
if (!$self->{private_bz_tables_locked}) {
|
||||
# Abort is allowed even without previous lock for error handling
|
||||
return if $abort;
|
||||
ThrowCodeError("no_matching_lock");
|
||||
} else {
|
||||
$self->do("UNLOCK TABLES");
|
||||
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
}
|
||||
}
|
||||
|
||||
# As Bugzilla currently runs on MyISAM storage, which does not supprt
|
||||
# transactions, these functions die when called.
|
||||
# Maybe we should just ignore these calls for now, but as we are not
|
||||
# using transactions in MySQL yet, this just hints the developers.
|
||||
sub bz_start_transaction {
|
||||
die("Attempt to start transaction on DB without transaction support");
|
||||
}
|
||||
|
||||
sub bz_commit_transaction {
|
||||
die("Attempt to commit transaction on DB without transaction support");
|
||||
}
|
||||
|
||||
sub bz_rollback_transaction {
|
||||
die("Attempt to rollback transaction on DB without transaction support");
|
||||
}
|
||||
|
||||
|
||||
sub _bz_get_initial_schema {
|
||||
my ($self) = @_;
|
||||
return $self->_bz_build_schema_from_disk();
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my ($self) = @_;
|
||||
|
||||
# Figure out if any existing tables are of type ISAM and convert them
|
||||
# to type MyISAM if so. ISAM tables are deprecated in MySQL 3.23,
|
||||
# which Bugzilla now requires, and they don't support more than 16
|
||||
# indexes per table, which Bugzilla needs.
|
||||
my $sth = $self->prepare("SHOW TABLE STATUS");
|
||||
$sth->execute;
|
||||
my @isam_tables = ();
|
||||
while (my ($name, $type) = $sth->fetchrow_array) {
|
||||
push(@isam_tables, $name) if $type eq "ISAM";
|
||||
}
|
||||
|
||||
if(scalar(@isam_tables)) {
|
||||
print "One or more of the tables in your existing MySQL database are\n"
|
||||
. "of type ISAM. ISAM tables are deprecated in MySQL 3.23 and\n"
|
||||
. "don't support more than 16 indexes per table, which \n"
|
||||
. "Bugzilla needs.\n Converting your ISAM tables to type"
|
||||
. " MyISAM:\n\n";
|
||||
foreach my $table (@isam_tables) {
|
||||
print "Converting table $table... ";
|
||||
$self->do("ALTER TABLE $table TYPE = MYISAM");
|
||||
print "done.\n";
|
||||
}
|
||||
print "\nISAM->MyISAM table conversion done.\n\n";
|
||||
}
|
||||
|
||||
# Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
|
||||
# not provide explicit names for the table indexes. This means
|
||||
# that our upgrades will not be reliable, because we look for the name
|
||||
# of the index, not what fields it is on, when doing upgrades.
|
||||
# (using the name is much better for cross-database compatibility
|
||||
# and general reliability). It's also very important that our
|
||||
# Schema object be consistent with what is on the disk.
|
||||
#
|
||||
# While we're at it, we also fix some inconsistent index naming
|
||||
# from the original checkin of Bugzilla::DB::Schema.
|
||||
|
||||
# We check for the existence of a particular "short name" index that
|
||||
# has existed at least since Bugzilla 2.8, and probably earlier.
|
||||
# For fixing the inconsistent naming of Schema indexes,
|
||||
# we also check for one of those inconsistently-named indexes.
|
||||
my @tables = $self->bz_table_list_real();
|
||||
if ( scalar(@tables) &&
|
||||
($self->bz_index_info_real('bugs', 'assigned_to') ||
|
||||
$self->bz_index_info_real('flags', 'flags_bidattid_idx')) )
|
||||
{
|
||||
my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
|
||||
# We estimate one minute for each 3000 bugs, plus 3 minutes just
|
||||
# to handle basic MySQL stuff.
|
||||
my $rename_time = int($bug_count / 3000) + 3;
|
||||
# And 45 minutes for every 15,000 attachments, per some experiments.
|
||||
my ($attachment_count) =
|
||||
$self->selectrow_array("SELECT COUNT(*) FROM attachments");
|
||||
$rename_time += int(($attachment_count * 45) / 15000);
|
||||
# If we're going to take longer than 5 minutes, we let the user know
|
||||
# and allow them to abort.
|
||||
if ($rename_time > 5) {
|
||||
print "\nWe are about to rename old indexes.\n"
|
||||
. "The estimated time to complete renaming is "
|
||||
. "$rename_time minutes.\n"
|
||||
. "You cannot interrupt this action once it has begun.\n"
|
||||
. "If you would like to cancel, press Ctrl-C now..."
|
||||
. " (Waiting 45 seconds...)\n\n";
|
||||
# Wait 45 seconds for them to respond.
|
||||
sleep(45);
|
||||
}
|
||||
print "Renaming indexes...\n";
|
||||
|
||||
# We can't be interrupted, because of how the "if"
|
||||
# works above.
|
||||
local $SIG{INT} = 'IGNORE';
|
||||
local $SIG{TERM} = 'IGNORE';
|
||||
local $SIG{PIPE} = 'IGNORE';
|
||||
|
||||
# Certain indexes had names in Schema that did not easily conform
|
||||
# to a standard. We store those names here, so that they
|
||||
# can be properly renamed.
|
||||
# Also, sometimes an old mysqldump would incorrectly rename
|
||||
# unique indexes to "PRIMARY", so we address that here, also.
|
||||
my $bad_names = {
|
||||
# 'when' is a possible leftover from Bugzillas before 2.8
|
||||
bugs_activity => ['when', 'bugs_activity_bugid_idx',
|
||||
'bugs_activity_bugwhen_idx'],
|
||||
cc => ['PRIMARY'],
|
||||
longdescs => ['longdescs_bugid_idx',
|
||||
'longdescs_bugwhen_idx'],
|
||||
flags => ['flags_bidattid_idx'],
|
||||
flaginclusions => ['flaginclusions_tpcid_idx'],
|
||||
flagexclusions => ['flagexclusions_tpc_id_idx'],
|
||||
keywords => ['PRIMARY'],
|
||||
milestones => ['PRIMARY'],
|
||||
profiles_activity => ['profiles_activity_when_idx'],
|
||||
group_control_map => ['group_control_map_gid_idx', 'PRIMARY'],
|
||||
user_group_map => ['PRIMARY'],
|
||||
group_group_map => ['PRIMARY'],
|
||||
email_setting => ['PRIMARY'],
|
||||
bug_group_map => ['PRIMARY'],
|
||||
category_group_map => ['PRIMARY'],
|
||||
watch => ['PRIMARY'],
|
||||
namedqueries => ['PRIMARY'],
|
||||
series_data => ['PRIMARY'],
|
||||
# series_categories is dealt with below, not here.
|
||||
};
|
||||
|
||||
# The series table is broken and needs to have one index
|
||||
# dropped before we begin the renaming, because it had a
|
||||
# useless index on it that would cause a naming conflict here.
|
||||
if (grep($_ eq 'series', @tables)) {
|
||||
my $dropname;
|
||||
# This is what the bad index was called before Schema.
|
||||
if ($self->bz_index_info_real('series', 'creator_2')) {
|
||||
$dropname = 'creator_2';
|
||||
}
|
||||
# This is what the bad index is called in Schema.
|
||||
elsif ($self->bz_index_info_real('series', 'series_creator_idx')) {
|
||||
$dropname = 'series_creator_idx';
|
||||
}
|
||||
$self->bz_drop_index_raw('series', $dropname) if $dropname;
|
||||
}
|
||||
|
||||
# The email_setting table also had the same problem.
|
||||
if( grep($_ eq 'email_setting', @tables)
|
||||
&& $self->bz_index_info_real('email_setting',
|
||||
'email_settings_user_id_idx') )
|
||||
{
|
||||
$self->bz_drop_index_raw('email_setting',
|
||||
'email_settings_user_id_idx');
|
||||
}
|
||||
|
||||
# Go through all the tables.
|
||||
foreach my $table (@tables) {
|
||||
# Will contain the names of old indexes as keys, and the
|
||||
# definition of the new indexes as a value. The values
|
||||
# include an extra hash key, NAME, with the new name of
|
||||
# the index.
|
||||
my %rename_indexes;
|
||||
# And go through all the columns on each table.
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
|
||||
# We also want to fix the silly naming of unique indexes
|
||||
# that happened when we first checked-in Bugzilla::DB::Schema.
|
||||
if ($table eq 'series_categories') {
|
||||
# The series_categories index had a nonstandard name.
|
||||
push(@columns, 'series_cats_unique_idx');
|
||||
}
|
||||
elsif ($table eq 'email_setting') {
|
||||
# The email_setting table had a similar problem.
|
||||
push(@columns, 'email_settings_unique_idx');
|
||||
}
|
||||
else {
|
||||
push(@columns, "${table}_unique_idx");
|
||||
}
|
||||
# And this is how we fix the other inconsistent Schema naming.
|
||||
push(@columns, @{$bad_names->{$table}})
|
||||
if (exists $bad_names->{$table});
|
||||
foreach my $column (@columns) {
|
||||
# If we have an index named after this column, it's an
|
||||
# old-style-name index.
|
||||
if (my $index = $self->bz_index_info_real($table, $column)) {
|
||||
# Fix the name to fit in with the new naming scheme.
|
||||
$index->{NAME} = $table . "_" .
|
||||
$index->{FIELDS}->[0] . "_idx";
|
||||
print "Renaming index $column to "
|
||||
. $index->{NAME} . "...\n";
|
||||
$rename_indexes{$column} = $index;
|
||||
} # if
|
||||
} # foreach column
|
||||
|
||||
my @rename_sql = $self->_bz_schema->get_rename_indexes_ddl(
|
||||
$table, %rename_indexes);
|
||||
$self->do($_) foreach (@rename_sql);
|
||||
|
||||
} # foreach table
|
||||
} # if old-name indexes
|
||||
|
||||
|
||||
# And now we create the tables and the Schema object.
|
||||
$self->SUPER::bz_setup_database();
|
||||
|
||||
|
||||
# The old timestamp fields need to be adjusted here instead of in
|
||||
# checksetup. Otherwise the UPDATE statements inside of bz_add_column
|
||||
# will cause accidental timestamp updates.
|
||||
# The code that does this was moved here from checksetup.
|
||||
|
||||
# 2002-08-14 - bbaetz@student.usyd.edu.au - bug 153578
|
||||
# attachments creation time needs to be a datetime, not a timestamp
|
||||
my $attach_creation =
|
||||
$self->bz_column_info("attachments", "creation_ts");
|
||||
if ($attach_creation && $attach_creation->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
print "Fixing creation time on attachments...\n";
|
||||
|
||||
my $sth = $self->prepare("SELECT COUNT(attach_id) FROM attachments");
|
||||
$sth->execute();
|
||||
my ($attach_count) = $sth->fetchrow_array();
|
||||
|
||||
if ($attach_count > 1000) {
|
||||
print "This may take a while...\n";
|
||||
}
|
||||
my $i = 0;
|
||||
|
||||
# This isn't just as simple as changing the field type, because
|
||||
# the creation_ts was previously updated when an attachment was made
|
||||
# obsolete from the attachment creation screen. So we have to go
|
||||
# and recreate these times from the comments..
|
||||
$sth = $self->prepare("SELECT bug_id, attach_id, submitter_id " .
|
||||
"FROM attachments");
|
||||
$sth->execute();
|
||||
|
||||
# Restrict this as much as possible in order to avoid false
|
||||
# positives, and keep the db search time down
|
||||
my $sth2 = $self->prepare("SELECT bug_when FROM longdescs
|
||||
WHERE bug_id=? AND who=?
|
||||
AND thetext LIKE ?
|
||||
ORDER BY bug_when " . $self->sql_limit(1));
|
||||
while (my ($bug_id, $attach_id, $submitter_id)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
$sth2->execute($bug_id, $submitter_id,
|
||||
"Created an attachment (id=$attach_id)%");
|
||||
my ($when) = $sth2->fetchrow_array();
|
||||
if ($when) {
|
||||
$self->do("UPDATE attachments " .
|
||||
"SET creation_ts='$when' " .
|
||||
"WHERE attach_id=$attach_id");
|
||||
} else {
|
||||
print "Warning - could not determine correct creation"
|
||||
. " time for attachment $attach_id on bug $bug_id\n";
|
||||
}
|
||||
++$i;
|
||||
print "Converted $i of $attach_count attachments\n" if !($i % 1000);
|
||||
}
|
||||
print "Done - converted $i attachments\n";
|
||||
|
||||
$self->bz_alter_column("attachments", "creation_ts",
|
||||
{TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2004-08-29 - Tomas.Kopal@altap.cz, bug 257303
|
||||
# Change logincookies.lastused type from timestamp to datetime
|
||||
my $login_lastused = $self->bz_column_info("logincookies", "lastused");
|
||||
if ($login_lastused && $login_lastused->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
$self->bz_alter_column('logincookies', 'lastused',
|
||||
{ TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
# 2005-01-17 - Tomas.Kopal@altap.cz, bug 257315
|
||||
# Change bugs.delta_ts type from timestamp to datetime
|
||||
my $bugs_deltats = $self->bz_column_info("bugs", "delta_ts");
|
||||
if ($bugs_deltats && $bugs_deltats->{TYPE} =~ /^TIMESTAMP/i) {
|
||||
$self->bz_alter_column('bugs', 'delta_ts',
|
||||
{TYPE => 'DATETIME', NOTNULL => 1});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#####################################################################
|
||||
# MySQL-specific Database-Reading Methods
|
||||
#####################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 MYSQL-SPECIFIC DATABASE-READING METHODS
|
||||
|
||||
These methods read information about the database from the disk,
|
||||
instead of from a Schema object. They are only reliable for MySQL
|
||||
(see bug 285111 for the reasons why not all DBs use/have functions
|
||||
like this), but that's OK because we only need them for
|
||||
backwards-compatibility anyway, for versions of Bugzilla before 2.20.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<bz_column_info_real($table, $column)>
|
||||
|
||||
Description: Returns an abstract column definition for a column
|
||||
as it actually exists on disk in the database.
|
||||
Params: $table - The name of the table the column is on.
|
||||
$column - The name of the column you want info about.
|
||||
Returns: An abstract column definition.
|
||||
If the column does not exist, returns undef.
|
||||
|
||||
=cut
|
||||
|
||||
sub bz_column_info_real {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
# DBD::mysql does not support selecting a specific column,
|
||||
# so we have to get all the columns on the table and find
|
||||
# the one we want.
|
||||
my $info_sth = $self->column_info(undef, undef, $table, '%');
|
||||
|
||||
# Don't use fetchall_hashref as there's a Win32 DBI bug (292821)
|
||||
my $col_data;
|
||||
while ($col_data = $info_sth->fetchrow_hashref) {
|
||||
last if $col_data->{'COLUMN_NAME'} eq $column;
|
||||
}
|
||||
|
||||
if (!defined $col_data) {
|
||||
return undef;
|
||||
}
|
||||
return $self->_bz_schema->column_info_to_column($col_data);
|
||||
}
|
||||
|
||||
=item C<bz_index_info_real($table, $index)>
|
||||
|
||||
Description: Returns information about an index on a table in the database.
|
||||
Params: $table = name of table containing the index
|
||||
$index = name of an index
|
||||
Returns: An abstract index definition, always in hashref format.
|
||||
If the index does not exist, the function returns undef.
|
||||
=cut
|
||||
sub bz_index_info_real {
|
||||
my ($self, $table, $index) = @_;
|
||||
|
||||
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
||||
$sth->execute;
|
||||
|
||||
my @fields;
|
||||
my $index_type;
|
||||
# $raw_def will be an arrayref containing the following information:
|
||||
# 0 = name of the table that the index is on
|
||||
# 1 = 0 if unique, 1 if not unique
|
||||
# 2 = name of the index
|
||||
# 3 = seq_in_index (The order of the current field in the index).
|
||||
# 4 = Name of ONE column that the index is on
|
||||
# 5 = 'Collation' of the index. Usually 'A'.
|
||||
# 6 = Cardinality. Either a number or undef.
|
||||
# 7 = sub_part. Usually undef. Sometimes 1.
|
||||
# 8 = "packed". Usually undef.
|
||||
# MySQL 3
|
||||
# -------
|
||||
# 9 = comments. Usually an empty string. Sometimes 'FULLTEXT'.
|
||||
# MySQL 4
|
||||
# -------
|
||||
# 9 = Null. Sometimes undef, sometimes 'YES'.
|
||||
# 10 = Index_type. The type of the index. Usually either 'BTREE' or 'FULLTEXT'
|
||||
# 11 = 'Comment.' Usually undef.
|
||||
my $is_mysql3 = ($self->bz_server_version() =~ /^3/);
|
||||
my $index_type_loc = $is_mysql3 ? 9 : 10;
|
||||
while (my $raw_def = $sth->fetchrow_arrayref) {
|
||||
if ($raw_def->[2] eq $index) {
|
||||
push(@fields, $raw_def->[4]);
|
||||
# No index can be both UNIQUE and FULLTEXT, that's why
|
||||
# this is written this way.
|
||||
$index_type = $raw_def->[1] ? '' : 'UNIQUE';
|
||||
$index_type = $raw_def->[$index_type_loc] eq 'FULLTEXT'
|
||||
? 'FULLTEXT' : $index_type;
|
||||
}
|
||||
}
|
||||
|
||||
my $retval;
|
||||
if (scalar(@fields)) {
|
||||
$retval = {FIELDS => \@fields, TYPE => $index_type};
|
||||
}
|
||||
return $retval;
|
||||
}
|
||||
|
||||
=item C<bz_index_list_real($table)>
|
||||
|
||||
Description: Returns a list of index names on a table in
|
||||
the database, as it actually exists on disk.
|
||||
Params: $table - The name of the table you want info about.
|
||||
Returns: An array of index names.
|
||||
|
||||
=cut
|
||||
|
||||
sub bz_index_list_real {
|
||||
my ($self, $table) = @_;
|
||||
my $sth = $self->prepare("SHOW INDEX FROM $table");
|
||||
# Column 3 of a SHOW INDEX statement contains the name of the index.
|
||||
return @{ $self->selectcol_arrayref($sth, {Columns => [3]}) };
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# MySQL-Specific "Schema Builder"
|
||||
#####################################################################
|
||||
|
||||
=back
|
||||
|
||||
=head1 MYSQL-SPECIFIC "SCHEMA BUILDER"
|
||||
|
||||
MySQL needs to be able to read in a legacy database (from before
|
||||
Schema existed) and create a Schema object out of it. That's what
|
||||
this code does.
|
||||
|
||||
=end private
|
||||
|
||||
=cut
|
||||
|
||||
# This sub itself is actually written generically, but the subroutines
|
||||
# that it depends on are database-specific. In particular, the
|
||||
# bz_column_info_real function would be very difficult to create
|
||||
# properly for any other DB besides MySQL.
|
||||
sub _bz_build_schema_from_disk {
|
||||
my ($self) = @_;
|
||||
|
||||
print "Building Schema object from database...\n";
|
||||
|
||||
my $schema = $self->_bz_schema->get_empty_schema();
|
||||
|
||||
my @tables = $self->bz_table_list_real();
|
||||
foreach my $table (@tables) {
|
||||
$schema->add_table($table);
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
foreach my $column (@columns) {
|
||||
my $type_info = $self->bz_column_info_real($table, $column);
|
||||
$schema->set_column($table, $column, $type_info);
|
||||
}
|
||||
|
||||
my @indexes = $self->bz_index_list_real($table);
|
||||
foreach my $index (@indexes) {
|
||||
unless ($index eq 'PRIMARY') {
|
||||
my $index_info = $self->bz_index_info_real($table, $index);
|
||||
($index_info = $index_info->{FIELDS})
|
||||
if (!$index_info->{TYPE});
|
||||
$schema->set_index($table, $index, $index_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
1;
|
||||
@@ -1,237 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Dave Miller <davem00@aol.com>
|
||||
# Gayathri Swaminath <gayathrik00@aol.com>
|
||||
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with PostgreSQL
|
||||
specific implementation. It is instantiated by the Bugzilla::DB module
|
||||
and should never be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Pg;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use DBD::Pg;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
|
||||
use constant REQUIRED_VERSION => '7.03.0000';
|
||||
use constant PROGRAM_NAME => 'PostgreSQL';
|
||||
use constant MODULE_NAME => 'Pg';
|
||||
use constant DBD_VERSION => '1.31';
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port) = @_;
|
||||
|
||||
# The default database name for PostgreSQL. We have
|
||||
# to connect to SOME database, even if we have
|
||||
# no $dbname parameter.
|
||||
$dbname ||= 'template1';
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "DBI:Pg:host=$host;dbname=$dbname";
|
||||
$dsn .= ";port=$port" if $port;
|
||||
|
||||
my $self = $class->db_new($dsn, $user, $pass);
|
||||
|
||||
# all class local variables stored in DBI derived class needs to have
|
||||
# a prefix 'private_'. See DBI documentation.
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# if last_insert_id is supported on PostgreSQL by lowest DBI/DBD version
|
||||
# supported by Bugzilla, this implementation can be removed.
|
||||
sub bz_last_key {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
my $seq = $table . "_" . $column . "_seq";
|
||||
my ($last_insert_id) = $self->selectrow_array("SELECT CURRVAL('$seq')");
|
||||
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
return "~*";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
return "!~*"
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if (defined($offset)) {
|
||||
return "LIMIT $limit OFFSET $offset";
|
||||
} else {
|
||||
return "LIMIT $limit";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return "TO_CHAR($date, 'J')::int";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
$format =~ s/\%Y/YYYY/g;
|
||||
$format =~ s/\%y/YY/g;
|
||||
$format =~ s/\%m/MM/g;
|
||||
$format =~ s/\%d/DD/g;
|
||||
$format =~ s/\%a/Dy/g;
|
||||
$format =~ s/\%H/HH24/g;
|
||||
$format =~ s/\%i/MI/g;
|
||||
$format =~ s/\%s/SS/g;
|
||||
|
||||
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval) = @_;
|
||||
|
||||
return "INTERVAL '$interval'";
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
# Postgres 7.3 does not support concatenating of different types, so we
|
||||
# need to cast both parameters to text. Version 7.4 seems to handle this
|
||||
# properly, so when we stop support 7.3, this can be removed.
|
||||
return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
|
||||
}
|
||||
|
||||
sub bz_lock_tables {
|
||||
my ($self, @tables) = @_;
|
||||
|
||||
my $list = join(', ', @tables);
|
||||
# Check first if there was no lock before
|
||||
if ($self->{private_bz_tables_locked}) {
|
||||
ThrowCodeError("already_locked", { current => $self->{private_bz_tables_locked},
|
||||
new => $list });
|
||||
} else {
|
||||
my %read_tables;
|
||||
my %write_tables;
|
||||
foreach my $table (@tables) {
|
||||
$table =~ /^([\d\w]+)([\s]+AS[\s]+[\d\w]+)?[\s]+(WRITE|READ)$/i;
|
||||
my $table_name = $1;
|
||||
if ($3 =~ /READ/i) {
|
||||
if (!exists $read_tables{$table_name}) {
|
||||
$read_tables{$table_name} = undef;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!exists $write_tables{$table_name}) {
|
||||
$write_tables{$table_name} = undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Begin Transaction
|
||||
$self->bz_start_transaction();
|
||||
|
||||
Bugzilla->dbh->do('LOCK TABLE ' . join(', ', keys %read_tables) .
|
||||
' IN ROW SHARE MODE') if keys %read_tables;
|
||||
Bugzilla->dbh->do('LOCK TABLE ' . join(', ', keys %write_tables) .
|
||||
' IN ROW EXCLUSIVE MODE') if keys %write_tables;
|
||||
$self->{private_bz_tables_locked} = $list;
|
||||
}
|
||||
}
|
||||
|
||||
sub bz_unlock_tables {
|
||||
my ($self, $abort) = @_;
|
||||
|
||||
# Check first if there was previous matching lock
|
||||
if (!$self->{private_bz_tables_locked}) {
|
||||
# Abort is allowed even without previous lock for error handling
|
||||
return if $abort;
|
||||
ThrowCodeError("no_matching_lock");
|
||||
} else {
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
# End transaction, tables will be unlocked automatically
|
||||
if ($abort) {
|
||||
$self->bz_rollback_transaction();
|
||||
} else {
|
||||
$self->bz_commit_transaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my $self = shift;
|
||||
$self->SUPER::bz_setup_database(@_);
|
||||
|
||||
# PostgreSQL doesn't like having *any* index on the thetext
|
||||
# field, because it can't have index data longer than 2770
|
||||
# characters on that field.
|
||||
$self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
|
||||
|
||||
# PostgreSQL also wants an index for calling LOWER on
|
||||
# login_name, which we do with sql_istrcmp all over the place.
|
||||
$self->bz_add_index('profiles', 'profiles_login_name_lower_idx',
|
||||
{FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'});
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Schema Information Functions
|
||||
#####################################################################
|
||||
|
||||
# Pg includes the PostgreSQL system tables in table_list_real, so
|
||||
# we need to remove those.
|
||||
sub bz_table_list_real {
|
||||
my $self = shift;
|
||||
|
||||
my @full_table_list = $self->SUPER::bz_table_list_real(@_);
|
||||
# All PostgreSQL system tables start with "pg_" or "sql_"
|
||||
my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
|
||||
return @table_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,322 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Mysql;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for MySQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
|
||||
# This is for column_info_to_column, to know when a tinyint is a
|
||||
# boolean and when it's really a tinyint. This only has to be accurate
|
||||
# up to and through 2.19.3, because that's the only time we need
|
||||
# column_info_to_column.
|
||||
#
|
||||
# This is basically a hash of tables/columns, with one entry for each column
|
||||
# that should be interpreted as a BOOLEAN instead of as an INT1 when
|
||||
# reading in the Schema from the disk. The values are discarded; I just
|
||||
# used "1" for simplicity.
|
||||
use constant BOOLEAN_MAP => {
|
||||
bugs => {everconfirmed => 1, reporter_accessible => 1,
|
||||
cclist_accessible => 1, qacontact_accessible => 1,
|
||||
assignee_accessible => 1},
|
||||
longdescs => {isprivate => 1, already_wrapped => 1},
|
||||
attachments => {ispatch => 1, isobsolete => 1, isprivate => 1},
|
||||
flags => {is_active => 1},
|
||||
flagtypes => {is_active => 1, is_requestable => 1,
|
||||
is_requesteeble => 1, is_multiplicable => 1},
|
||||
fielddefs => {mailhead => 1, obsolete => 1},
|
||||
bug_status => {isactive => 1},
|
||||
resolution => {isactive => 1},
|
||||
bug_severity => {isactive => 1},
|
||||
priority => {isactive => 1},
|
||||
rep_platform => {isactive => 1},
|
||||
op_sys => {isactive => 1},
|
||||
profiles => {mybugslink => 1, newemailtech => 1},
|
||||
namedqueries => {linkinfooter => 1, watchfordiffs => 1},
|
||||
groups => {isbuggroup => 1, isactive => 1},
|
||||
group_control_map => {entry => 1, membercontrol => 1, othercontrol => 1,
|
||||
canedit => 1},
|
||||
group_group_map => {isbless => 1},
|
||||
user_group_map => {isbless => 1, isderived => 1},
|
||||
products => {disallownew => 1},
|
||||
series => {public => 1},
|
||||
whine_queries => {onemailperbug => 1},
|
||||
quips => {approved => 1},
|
||||
setting => {is_enabled => 1}
|
||||
};
|
||||
|
||||
# Maps the db_specific hash backwards, for use in column_info_to_column.
|
||||
use constant REVERSE_MAPPING => {
|
||||
# Boolean and the SERIAL fields are handled in column_info_to_column,
|
||||
# and so don't have an entry here.
|
||||
TINYINT => 'INT1',
|
||||
SMALLINT => 'INT2',
|
||||
MEDIUMINT => 'INT3',
|
||||
INTEGER => 'INT4',
|
||||
# All the other types have the same name in their abstract version
|
||||
# as in their db-specific version, so no reverse mapping is needed.
|
||||
};
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'tinyint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'tinyint',
|
||||
INT2 => 'smallint',
|
||||
INT3 => 'mediumint',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'smallint auto_increment',
|
||||
MEDIUMSERIAL => 'mediumint auto_increment',
|
||||
INTSERIAL => 'integer auto_increment',
|
||||
|
||||
TINYTEXT => 'tinytext',
|
||||
MEDIUMTEXT => 'mediumtext',
|
||||
TEXT => 'text',
|
||||
|
||||
LONGBLOB => 'longblob',
|
||||
|
||||
DATETIME => 'datetime',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_table_ddl {
|
||||
# Extend superclass method to specify the MYISAM storage engine.
|
||||
# Returns a "create table" SQL statement.
|
||||
|
||||
my($self, $table) = @_;
|
||||
|
||||
return($self->SUPER::_get_create_table_ddl($table) . ' TYPE = MYISAM');
|
||||
|
||||
} #eosub--_get_create_table_ddl
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_index_ddl {
|
||||
# Extend superclass method to create FULLTEXT indexes on text fields.
|
||||
# Returns a "create index" SQL statement.
|
||||
|
||||
my($self, $table_name, $index_name, $index_fields, $index_type) = @_;
|
||||
|
||||
my $sql = "CREATE ";
|
||||
$sql .= "$index_type " if ($index_type eq 'UNIQUE'
|
||||
|| $index_type eq 'FULLTEXT');
|
||||
$sql .= "INDEX \`$index_name\` ON $table_name \(" .
|
||||
join(", ", @$index_fields) . "\)";
|
||||
|
||||
return($sql);
|
||||
|
||||
} #eosub--_get_create_index_ddl
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# MySQL has a simpler ALTER TABLE syntax than ANSI.
|
||||
sub get_alter_column_ddl {
|
||||
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
|
||||
my $new_ddl = $self->get_type_ddl($new_def);
|
||||
my @statements;
|
||||
push(@statements, "UPDATE $table SET $column = $set_nulls_to
|
||||
WHERE $column IS NULL") if defined $set_nulls_to;
|
||||
push(@statements, "ALTER TABLE $table CHANGE COLUMN
|
||||
$column $column $new_ddl");
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_drop_index_ddl {
|
||||
my ($self, $table, $name) = @_;
|
||||
return ("DROP INDEX \`$name\` ON $table");
|
||||
}
|
||||
|
||||
# A special function for MySQL, for renaming a lot of indexes.
|
||||
# Index renames is a hash, where the key is a string - the
|
||||
# old names of the index, and the value is a hash - the index
|
||||
# definition that we're renaming to, with an extra key of "NAME"
|
||||
# that contains the new index name.
|
||||
# The indexes in %indexes must be in hashref format.
|
||||
sub get_rename_indexes_ddl {
|
||||
my ($self, $table, %indexes) = @_;
|
||||
my @keys = keys %indexes or return ();
|
||||
|
||||
my $sql = "ALTER TABLE $table ";
|
||||
|
||||
foreach my $old_name (@keys) {
|
||||
my $name = $indexes{$old_name}->{NAME};
|
||||
my $type = $indexes{$old_name}->{TYPE};
|
||||
$type ||= 'INDEX';
|
||||
my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
|
||||
# $old_name needs to be escaped, sometimes, because it was
|
||||
# a reserved word.
|
||||
$old_name = '`' . $old_name . '`';
|
||||
$sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
|
||||
}
|
||||
# Remove the last comma.
|
||||
chop($sql);
|
||||
return ($sql);
|
||||
}
|
||||
|
||||
# Converts a DBI column_info output to an abstract column definition.
|
||||
# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk,
|
||||
# although there's a chance that it will also work properly if called
|
||||
# elsewhere.
|
||||
sub column_info_to_column {
|
||||
my ($self, $column_info) = @_;
|
||||
|
||||
# Unfortunately, we have to break Schema's normal "no database"
|
||||
# barrier a few times in this function.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $table = $column_info->{TABLE_NAME};
|
||||
my $col_name = $column_info->{COLUMN_NAME};
|
||||
|
||||
my $column = {};
|
||||
|
||||
($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
|
||||
|
||||
if ($column_info->{mysql_is_pri_key}) {
|
||||
# In MySQL, if a table has no PK, but it has a UNIQUE index,
|
||||
# that index will show up as the PK. So we have to eliminate
|
||||
# that possibility.
|
||||
# Unfortunately, the only way to definitely solve this is
|
||||
# to break Schema's standard of not touching the live database
|
||||
# and check if the index called PRIMARY is on that field.
|
||||
my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
|
||||
if ( $pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}}) ) {
|
||||
$column->{PRIMARYKEY} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# MySQL frequently defines a default for a field even when we
|
||||
# didn't explicitly set one. So we have to have some special
|
||||
# hacks to determine whether or not we should actually put
|
||||
# a default in the abstract schema for this field.
|
||||
if (defined $column_info->{COLUMN_DEF}) {
|
||||
# The defaults that MySQL inputs automatically are usually
|
||||
# something that would be considered "false" by perl, either
|
||||
# a 0 or an empty string. (Except for ddatetime and decimal
|
||||
# fields, which have their own special auto-defaults.)
|
||||
#
|
||||
# Here's how we handle this: If it exists in the schema
|
||||
# without a default, then we don't use the default. If it
|
||||
# doesn't exist in the schema, then we're either going to
|
||||
# be dropping it soon, or it's a custom end-user column, in which
|
||||
# case having a bogus default won't harm anything.
|
||||
my $schema_column = $self->get_column($table, $col_name);
|
||||
unless ( (!$column_info->{COLUMN_DEF}
|
||||
|| $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
|
||||
|| $column_info->{COLUMN_DEF} eq '0.00')
|
||||
&& $schema_column
|
||||
&& !exists $schema_column->{DEFAULT}) {
|
||||
|
||||
my $default = $column_info->{COLUMN_DEF};
|
||||
# Schema uses '0' for the defaults for decimal fields.
|
||||
$default = 0 if $default =~ /^0\.0+$/;
|
||||
# If we're not a number, we're a string and need to be
|
||||
# quoted.
|
||||
$default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
|
||||
$column->{DEFAULT} = $default;
|
||||
}
|
||||
}
|
||||
|
||||
my $type = $column_info->{TYPE_NAME};
|
||||
|
||||
# Certain types of columns need the size/precision appended.
|
||||
if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
|
||||
# This is nicely lowercase and has the size/precision appended.
|
||||
$type = $column_info->{mysql_type_name};
|
||||
}
|
||||
|
||||
# If we're a tinyint, we could be either a BOOLEAN or an INT1.
|
||||
# Only the BOOLEAN_MAP knows the difference.
|
||||
elsif ($type eq 'TINYINT' && exists BOOLEAN_MAP->{$table}
|
||||
&& exists BOOLEAN_MAP->{$table}->{$col_name}) {
|
||||
$type = 'BOOLEAN';
|
||||
if (exists $column->{DEFAULT}) {
|
||||
$column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
}
|
||||
|
||||
# We also need to check if we're an auto_increment field.
|
||||
elsif ($type =~ /INT/) {
|
||||
# Unfortunately, the only way to do this in DBI is to query the
|
||||
# database, so we have to break the rule here that Schema normally
|
||||
# doesn't touch the live DB.
|
||||
my $ref_sth = $dbh->prepare(
|
||||
"SELECT $col_name FROM $table LIMIT 1");
|
||||
$ref_sth->execute;
|
||||
if ($ref_sth->{mysql_is_auto_increment}->[0]) {
|
||||
if ($type eq 'MEDIUMINT') {
|
||||
$type = 'MEDIUMSERIAL';
|
||||
}
|
||||
elsif ($type eq 'SMALLINT') {
|
||||
$type = 'SMALLSERIAL';
|
||||
}
|
||||
else {
|
||||
$type = 'INTSERIAL';
|
||||
}
|
||||
}
|
||||
$ref_sth->finish;
|
||||
|
||||
}
|
||||
|
||||
# For all other db-specific types, check if they exist in
|
||||
# REVERSE_MAPPING and use the type found there.
|
||||
if (exists REVERSE_MAPPING->{$type}) {
|
||||
$type = REVERSE_MAPPING->{$type};
|
||||
}
|
||||
|
||||
$column->{TYPE} = $type;
|
||||
|
||||
#print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
my $def = $self->get_type_ddl($self->get_column($table, $old_name));
|
||||
# MySQL doesn't like having the PRIMARY KEY statement in a rename.
|
||||
$def =~ s/PRIMARY KEY//i;
|
||||
return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,143 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Pg;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for PostgreSQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
# Remove FULLTEXT index types from the schemas.
|
||||
foreach my $table (keys %{ $self->{schema} }) {
|
||||
if ($self->{schema}{$table}{INDEXES}) {
|
||||
foreach my $index (@{ $self->{schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
foreach my $index (@{ $self->{abstract_schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'smallint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'integer',
|
||||
INT2 => 'integer',
|
||||
INT3 => 'integer',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'serial unique',
|
||||
MEDIUMSERIAL => 'serial unique',
|
||||
INTSERIAL => 'serial unique',
|
||||
|
||||
TINYTEXT => 'varchar(255)',
|
||||
MEDIUMTEXT => 'text',
|
||||
TEXT => 'text',
|
||||
|
||||
LONGBLOB => 'bytea',
|
||||
|
||||
DATETIME => 'timestamp(0) without time zone',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
# Overridden because Pg has such weird ALTER TABLE problems.
|
||||
sub get_add_column_ddl {
|
||||
my ($self, $table, $column, $definition, $init_value) = @_;
|
||||
|
||||
my @statements;
|
||||
my $specific = $self->{db_specific};
|
||||
|
||||
my $type = $definition->{TYPE};
|
||||
$type = $specific->{$type} if exists $specific->{$type};
|
||||
push(@statements, "ALTER TABLE $table ADD COLUMN $column $type");
|
||||
|
||||
my $default = $definition->{DEFAULT};
|
||||
if (defined $default) {
|
||||
# Replace any abstract default value (such as 'TRUE' or 'FALSE')
|
||||
# with its database-specific implementation.
|
||||
$default = $specific->{$default} if exists $specific->{$default};
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column "
|
||||
. " SET DEFAULT $default");
|
||||
}
|
||||
|
||||
if (defined $init_value) {
|
||||
push(@statements, "UPDATE $table SET $column = $init_value");
|
||||
}
|
||||
|
||||
if ($definition->{NOTNULL}) {
|
||||
# Handle rows that were NULL when we added the column.
|
||||
# We *must* have a DEFAULT. This check is usually handled
|
||||
# at a higher level than this code, but I figure it can't
|
||||
# hurt to have it here.
|
||||
die "NOT NULL columns must have a DEFAULT or an init_value."
|
||||
unless (exists $definition->{DEFAULT} || defined $init_value);
|
||||
push(@statements, "UPDATE $table SET $column = $default");
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column "
|
||||
. " SET NOT NULL");
|
||||
}
|
||||
|
||||
if ($definition->{PRIMARYKEY}) {
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN "
|
||||
. " ADD PRIMARY KEY ($column)");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
|
||||
return ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,202 +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 qw($datadir);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Date::Format;
|
||||
|
||||
sub _throw_error {
|
||||
my ($name, $error, $vars) = @_;
|
||||
|
||||
$vars ||= {};
|
||||
|
||||
$vars->{error} = $error;
|
||||
|
||||
# Make sure any locked tables are unlocked
|
||||
# and the transaction is rolled back (if supported)
|
||||
Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT);
|
||||
|
||||
# If a writable $datadir/errorlog exists, log error details there.
|
||||
if (-w "$datadir/errorlog") {
|
||||
require Data::Dumper;
|
||||
my $mesg = "";
|
||||
for (1..75) { $mesg .= "-"; };
|
||||
$mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
|
||||
$mesg .= "$name $error ";
|
||||
$mesg .= "$ENV{REMOTE_ADDR} " if $ENV{REMOTE_ADDR};
|
||||
$mesg .= Bugzilla->user->login;
|
||||
$mesg .= "\n";
|
||||
my %params = Bugzilla->cgi->Vars;
|
||||
$Data::Dumper::Useqq = 1;
|
||||
for my $param (sort keys %params) {
|
||||
my $val = $params{$param};
|
||||
# obscure passwords
|
||||
$val = "*****" if $param =~ /password/i;
|
||||
# limit line length
|
||||
$val =~ s/^(.{512}).*$/$1\[CHOP\]/;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["param($param)"]);
|
||||
}
|
||||
for my $var (sort keys %ENV) {
|
||||
my $val = $ENV{$var};
|
||||
$val = "*****" if $val =~ /password|http_pass/i;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["env($var)"]);
|
||||
}
|
||||
open(ERRORLOGFID, ">>$datadir/errorlog");
|
||||
print ERRORLOGFID "$mesg\n";
|
||||
close ERRORLOGFID;
|
||||
}
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
if (Bugzilla->batch) {
|
||||
my $message;
|
||||
$template->process($name, $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
die("$message");
|
||||
} else {
|
||||
print Bugzilla->cgi->header();
|
||||
$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) = @_;
|
||||
|
||||
# Make sure any locked tables are unlocked
|
||||
# and the transaction is rolled back (if supported)
|
||||
Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT);
|
||||
|
||||
my $vars = {};
|
||||
if (Bugzilla->batch) {
|
||||
die("error: template error: $template_err");
|
||||
}
|
||||
|
||||
$vars->{'template_error_msg'} = $template_err;
|
||||
$vars->{'error'} = "template_error";
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
# Try a template first; but if this one fails too, fall back
|
||||
# on plain old print statements.
|
||||
if (!$template->process("global/code-error.html.tmpl", $vars)) {
|
||||
my $maintainer = Bugzilla::Config::Param('maintainer');
|
||||
my $error = Bugzilla::Util::html_quote($vars->{'template_error_msg'});
|
||||
my $error2 = Bugzilla::Util::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.
|
||||
|
||||
These functions automatically unlock the database tables, if there were any
|
||||
locked. They will also roll back the transaction, if it is supported by
|
||||
the underlying DB.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<ThrowUserError>
|
||||
|
||||
This function takes an error tag as the first argument, and an optional hashref
|
||||
of variables as a second argument. These are used by the
|
||||
I<global/user-error.html.tmpl> template to format the error, using the passed
|
||||
in variables as required.
|
||||
|
||||
=item C<ThrowCodeError>
|
||||
|
||||
This function is used when an internal check detects an error of some sort.
|
||||
This usually indicates a bug in Bugzilla, although it can occur if the user
|
||||
manually constructs urls without correct parameters.
|
||||
|
||||
This function's behaviour is similar to C<ThrowUserError>, except that the
|
||||
template used to display errors is I<global/code-error.html.tmpl>. In addition
|
||||
if the hashref used as the optional second argument contains a key I<variables>
|
||||
then the contents of the hashref (which is expected to be another hashref) will
|
||||
be displayed after the error message, as a debugging aid.
|
||||
|
||||
=item C<ThrowTemplateError>
|
||||
|
||||
This function should only be called if a C<template-<gt>process()> fails.
|
||||
It tries another template first, because often one template being
|
||||
broken or missing doesn't mean that they all are. But it falls back to
|
||||
a print statement as a last-ditch error.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla|Bugzilla>
|
||||
@@ -1,623 +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>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::FlagType - A module to deal with Bugzilla flag types.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
FlagType.pm provides an interface to flag types as stored in Bugzilla.
|
||||
See below for more information.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
=over
|
||||
|
||||
=item *
|
||||
|
||||
Prior to calling routines in this module, it's assumed that you have
|
||||
already done a C<require CGI.pl>. This will eventually change in a
|
||||
future version when CGI.pl is removed.
|
||||
|
||||
=item *
|
||||
|
||||
Use of private functions/variables outside this module may lead to
|
||||
unexpected results after an upgrade. Please avoid using private
|
||||
functions in other files/modules. Private functions are functions
|
||||
whose names start with _ or are specifically noted as being private.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
######################################################################
|
||||
# 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;
|
||||
|
||||
######################################################################
|
||||
# Global Variables
|
||||
######################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE VARIABLES/CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<@base_columns>
|
||||
|
||||
basic sets of columns and tables for getting flag types from the
|
||||
database. B<Used by get, match, sqlify_criteria and perlify_record>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
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",
|
||||
"flagtypes.grant_group_id", "flagtypes.request_group_id");
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<@base_tables>
|
||||
|
||||
Which database(s) is the data coming from?
|
||||
|
||||
Note: when adding tables to @base_tables, make sure to include the separator
|
||||
(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take
|
||||
multiple separators based on the join type, and therefore it is not possible
|
||||
to join them later using a single known separator.
|
||||
B<Used by get, match, sqlify_criteria and perlify_record>
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=cut
|
||||
|
||||
my @base_tables = ("flagtypes");
|
||||
|
||||
######################################################################
|
||||
# Public Functions
|
||||
######################################################################
|
||||
|
||||
=head1 PUBLIC FUNCTIONS/METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get($id)>
|
||||
|
||||
Returns a hash of information about a flag type.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get {
|
||||
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;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_inclusions($id)>
|
||||
|
||||
Someone please document this
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_inclusions {
|
||||
my ($id) = @_;
|
||||
return get_clusions($id, "in");
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_exclusions($id)>
|
||||
|
||||
Someone please document this
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_exclusions {
|
||||
my ($id) = @_;
|
||||
return get_clusions($id, "ex");
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_clusions($id, $type)>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
associated with the flagtype:
|
||||
$clusions{'product_name:component_name'} = "product_ID:component_ID"
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_clusions {
|
||||
my ($id, $type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $list =
|
||||
$dbh->selectall_arrayref("SELECT products.id, products.name, " .
|
||||
" components.id, components.name " .
|
||||
"FROM flagtypes, flag${type}clusions " .
|
||||
"LEFT OUTER JOIN products " .
|
||||
" ON flag${type}clusions.product_id = products.id " .
|
||||
"LEFT OUTER JOIN components " .
|
||||
" ON flag${type}clusions.component_id = components.id " .
|
||||
"WHERE flagtypes.id = ? " .
|
||||
" AND flag${type}clusions.type_id = flagtypes.id",
|
||||
undef, $id);
|
||||
my %clusions;
|
||||
foreach my $data (@$list) {
|
||||
my ($product_id, $product_name, $component_id, $component_name) = @$data;
|
||||
$product_id ||= 0;
|
||||
$product_name ||= "__Any__";
|
||||
$component_id ||= 0;
|
||||
$component_name ||= "__Any__";
|
||||
$clusions{"$product_name:$component_name"} = "$product_id:$component_id";
|
||||
}
|
||||
return \%clusions;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<match($criteria, $include_count)>
|
||||
|
||||
Queries the database for flag types matching the given criteria
|
||||
and returns the set of matching types.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub match {
|
||||
my ($criteria, $include_count) = @_;
|
||||
|
||||
my @tables = @base_tables;
|
||||
my @columns = @base_columns;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# 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);
|
||||
|
||||
# Build the query, grouping the types if we are counting flags.
|
||||
# DISTINCT is used in order to count flag types only once when
|
||||
# they appear several times in the flaginclusions table.
|
||||
my $select_clause = "SELECT DISTINCT " . join(", ", @columns);
|
||||
my $from_clause = "FROM " . join(" ", @tables);
|
||||
my $where_clause = "WHERE " . join(" AND ", @criteria);
|
||||
|
||||
my $query = "$select_clause $from_clause $where_clause";
|
||||
$query .= " " . $dbh->sql_group_by('flagtypes.id',
|
||||
join(', ', @base_columns[2..$#base_columns]))
|
||||
if $include_count;
|
||||
$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;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<count($criteria)>
|
||||
|
||||
Returns the total number of flag types matching the given criteria.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub count {
|
||||
my ($criteria) = @_;
|
||||
|
||||
# Generate query components.
|
||||
my @tables = @base_tables;
|
||||
my @criteria = sqlify_criteria($criteria, \@tables);
|
||||
|
||||
# Build the query.
|
||||
my $select_clause = "SELECT COUNT(flagtypes.id)";
|
||||
my $from_clause = "FROM " . join(" ", @tables);
|
||||
my $where_clause = "WHERE " . join(" AND ", @criteria);
|
||||
my $query = "$select_clause $from_clause $where_clause";
|
||||
|
||||
# Execute the query and get the results.
|
||||
&::PushGlobalSQLState();
|
||||
&::SendSQL($query);
|
||||
my $count = &::FetchOneColumn();
|
||||
&::PopGlobalSQLState();
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<validate($cgi, $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.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub validate {
|
||||
my ($cgi, $bug_id, $attach_id) = @_;
|
||||
|
||||
my $user = Bugzilla->user;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my @ids = map(/^flag_type-(\d+)$/ ? $1 : (), $cgi->param());
|
||||
|
||||
return unless scalar(@ids);
|
||||
|
||||
# No flag reference should exist when changing several bugs at once.
|
||||
ThrowCodeError("flags_not_available", { type => 'b' }) unless $bug_id;
|
||||
|
||||
# We don't check that these flag types are valid for
|
||||
# this bug/attachment. This check will be done later when
|
||||
# processing new flags, see Flag::FormToNewFlags().
|
||||
|
||||
# All flag types have to be active
|
||||
my $inactive_flagtypes =
|
||||
$dbh->selectrow_array("SELECT 1 FROM flagtypes
|
||||
WHERE id IN (" . join(',', @ids) . ")
|
||||
AND is_active = 0 " .
|
||||
$dbh->sql_limit(1));
|
||||
|
||||
ThrowCodeError("flag_type_inactive") if $inactive_flagtypes;
|
||||
|
||||
foreach my $id (@ids) {
|
||||
my $status = $cgi->param("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 user didn't specify a requestee unless the flag
|
||||
# is specifically requestable.
|
||||
my $new_requestee = trim($cgi->param("requestee_type-$id") || '');
|
||||
|
||||
if ($status eq '?'
|
||||
&& !$flag_type->{is_requesteeble}
|
||||
&& $new_requestee)
|
||||
{
|
||||
ThrowCodeError("flag_requestee_disabled",
|
||||
{ name => $flag_type->{name} });
|
||||
}
|
||||
|
||||
# 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}
|
||||
&& $new_requestee)
|
||||
{
|
||||
# We know the requestee exists because we ran
|
||||
# Bugzilla::User::match_field before getting here.
|
||||
my $requestee = Bugzilla::User->new_from_login($new_requestee);
|
||||
|
||||
# Throw an error if the user can't see the bug.
|
||||
if (!$requestee->can_see_bug($bug_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")
|
||||
&& $cgi->param('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 });
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure the user is authorized to modify flags, see bug 180879
|
||||
# - User in the $grant_gid group can set flags, including "+" and "-"
|
||||
next if (!$flag_type->{grant_gid}
|
||||
|| $user->in_group(&::GroupIdToName($flag_type->{grant_gid})));
|
||||
|
||||
# - User in the $request_gid group can request flags
|
||||
next if ($status eq '?'
|
||||
&& (!$flag_type->{request_gid}
|
||||
|| $user->in_group(&::GroupIdToName($flag_type->{request_gid}))));
|
||||
|
||||
# - Any other flag modification is denied
|
||||
ThrowUserError("flag_update_denied",
|
||||
{ name => $flag_type->{name},
|
||||
status => $status,
|
||||
old_status => "X" });
|
||||
}
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<normalize(@ids)>
|
||||
|
||||
Given a list of flag types, checks its flags to make sure they should
|
||||
still exist after a change to the inclusions/exclusions lists.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub normalize {
|
||||
# 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
|
||||
######################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<sqlify_criteria($criteria, $tables)>
|
||||
|
||||
Converts a hash of criteria into a list of SQL criteria.
|
||||
$criteria is a reference to the criteria (field => value),
|
||||
$tables is a reference to an array of tables being accessed
|
||||
by the query.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub sqlify_criteria {
|
||||
my ($criteria, $tables) = @_;
|
||||
|
||||
# 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, "INNER JOIN flaginclusions ON " .
|
||||
"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 add a WHERE criteria to use only records with
|
||||
# NULL exclusion type, i.e. without any 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(@criteria, "flagexclusions.type_id IS NULL");
|
||||
}
|
||||
if ($criteria->{group}) {
|
||||
my $gid = $criteria->{group};
|
||||
detaint_natural($gid);
|
||||
push(@criteria, "(flagtypes.grant_group_id = $gid " .
|
||||
" OR flagtypes.request_group_id = $gid)");
|
||||
}
|
||||
|
||||
return @criteria;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<perlify_record()>
|
||||
|
||||
Converts data retrieved from the database into a Perl record. Depends on the
|
||||
formatting as described in @base_columns.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub perlify_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->{'grant_gid'} = $_[11];
|
||||
$type->{'request_gid'} = $_[12];
|
||||
$type->{'flag_count'} = $_[13];
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end private
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over
|
||||
|
||||
=item B<Bugzilla::Flags>
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONTRIBUTORS
|
||||
|
||||
=over
|
||||
|
||||
=item Myk Melez <myk@mozilla.org>
|
||||
|
||||
=item Kevin Benton <kevin.benton@amd.com>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,51 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Joel Peshkin <bugreport@peshkin.net>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Group;
|
||||
|
||||
use Bugzilla::Config;
|
||||
|
||||
# ValidateGroupName checks to see if ANY of the users in the provided list
|
||||
# of user objects can see the named group. It returns the group id if
|
||||
# successful and undef otherwise.
|
||||
sub ValidateGroupName {
|
||||
my ($name, @users) = (@_);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $query = "SELECT id FROM groups " .
|
||||
"WHERE name = ?";
|
||||
if (Param('usevisibilitygroups')) {
|
||||
my @visible = (-1);
|
||||
foreach my $user (@users) {
|
||||
$user && push @visible, @{$user->visible_groups_direct};
|
||||
}
|
||||
my $visible = join(', ', @visible);
|
||||
$query .= " AND id IN($visible)";
|
||||
}
|
||||
my $sth = $dbh->prepare($query);
|
||||
$sth->execute($name);
|
||||
my ($ret) = $sth->fetchrow_array();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,260 +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)) " .
|
||||
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name, series.creator, series.frequency, ' .
|
||||
'series.query, series.public'));
|
||||
|
||||
if (@series) {
|
||||
$self->initFromParameters(@series);
|
||||
return $self;
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub initFromParameters {
|
||||
# Pass undef as the first parameter if you are creating a new series.
|
||||
my $self = shift;
|
||||
|
||||
($self->{'series_id'}, $self->{'category'}, $self->{'subcategory'},
|
||||
$self->{'name'}, $self->{'creator'}, $self->{'frequency'},
|
||||
$self->{'query'}, $self->{'public'}) = @_;
|
||||
}
|
||||
|
||||
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->bz_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->bz_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,449 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Tobias Burnus <burnus@net-b.de>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
|
||||
package Bugzilla::Template;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config qw(:DEFAULT $templatedir $datadir);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
|
||||
# for time2str - replace by TT Date plugin??
|
||||
use Date::Format ();
|
||||
|
||||
use base qw(Template);
|
||||
|
||||
# Convert the constants in the Bugzilla::Constants module into a hash we can
|
||||
# pass to the template object for reflection into its "constants" namespace
|
||||
# (which is like its "variables" namespace, but for constants). To do so, we
|
||||
# traverse the arrays of exported and exportable symbols, pulling out functions
|
||||
# (which is how Perl implements constants) and ignoring the rest (which, if
|
||||
# Constants.pm exports only constants, as it should, will be nothing else).
|
||||
use Bugzilla::Constants ();
|
||||
my %constants;
|
||||
foreach my $constant (@Bugzilla::Constants::EXPORT,
|
||||
@Bugzilla::Constants::EXPORT_OK)
|
||||
{
|
||||
if (defined &{$Bugzilla::Constants::{$constant}}) {
|
||||
# Constants can be lists, and we can't know whether we're getting
|
||||
# a scalar or a list in advance, since they come to us as the return
|
||||
# value of a function call, so we have to retrieve them all in list
|
||||
# context into anonymous arrays, then extract the scalar ones (i.e.
|
||||
# the ones whose arrays contain a single element) from their arrays.
|
||||
$constants{$constant} = [&{$Bugzilla::Constants::{$constant}}];
|
||||
if (scalar(@{$constants{$constant}}) == 1) {
|
||||
$constants{$constant} = @{$constants{$constant}}[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 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 /^\Q$lang\E(-.+)?$/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 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;
|
||||
},
|
||||
|
||||
# Prevents line break on hyphens and whitespaces.
|
||||
no_break => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/ /\ /g;
|
||||
$var =~ s/-/\‑/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
xml => \&Bugzilla::Util::xml_quote ,
|
||||
|
||||
# This filter escapes characters in a variable or value string for
|
||||
# use in a query string. It escapes all characters NOT in the
|
||||
# regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
|
||||
# a full URL that may have characters that need encoding.
|
||||
url_quote => \&Bugzilla::Util::url_quote ,
|
||||
|
||||
# This filter is similar to url_quote but used a \ instead of a %
|
||||
# as prefix. In addition it replaces a ' ' by a '_'.
|
||||
css_class_quote => \&Bugzilla::Util::css_class_quote ,
|
||||
|
||||
quoteUrls => \&::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
|
||||
],
|
||||
|
||||
# Wrap a displayed comment to the appropriate length
|
||||
wrap_comment => \&Bugzilla::Util::wrap_comment,
|
||||
|
||||
# We force filtering of every variable in key security-critical
|
||||
# places; we have a none filter for people to use when they
|
||||
# really, really don't want a variable to be changed.
|
||||
none => sub { return $_[0]; } ,
|
||||
},
|
||||
|
||||
PLUGIN_BASE => 'Bugzilla::Template::Plugin',
|
||||
|
||||
CONSTANTS => \%constants,
|
||||
|
||||
# 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' => \&Bugzilla::User::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,65 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joel Peshkin <bugreport@peshkin.net>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::User;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla::User;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla::User->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::User
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the C<User>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::User>, L<Template::Plugin>
|
||||
|
||||
@@ -1,311 +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 Bugzilla::Util;
|
||||
|
||||
use Date::Format;
|
||||
use Date::Parse;
|
||||
|
||||
# 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;
|
||||
|
||||
################################################################################
|
||||
# Public Functions
|
||||
################################################################################
|
||||
|
||||
sub IssueEmailChangeToken {
|
||||
my ($userid, $old_email, $new_email) = @_;
|
||||
|
||||
my ($token, $token_ts) = _create_token($userid, 'emailold', $old_email . ":" . $new_email);
|
||||
|
||||
my $newtoken = _create_token($userid, 'emailnew', $old_email . ":" . $new_email);
|
||||
|
||||
# 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) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# 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 > NOW() - " .
|
||||
$dbh->sql_interval('10 MINUTE') . "
|
||||
WHERE " . $dbh->sql_istrcmp('login_name', $quotedloginname));
|
||||
my ($userid, $toosoon) = &::FetchSQLData();
|
||||
|
||||
if ($toosoon) {
|
||||
ThrowUserError('too_soon_for_new_token');
|
||||
};
|
||||
|
||||
my ($token, $token_ts) = _create_token($userid, 'password', $::ENV{'REMOTE_ADDR'});
|
||||
|
||||
# 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 IssueSessionToken {
|
||||
# Generates a random token, adds it to the tokens table, and returns
|
||||
# the token to the caller.
|
||||
|
||||
my $data = shift;
|
||||
return _create_token(Bugzilla->user->id, 'session', $data);
|
||||
}
|
||||
|
||||
sub CleanTokenTable {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->bz_lock_tables('tokens WRITE');
|
||||
&::SendSQL("DELETE FROM tokens WHERE " .
|
||||
$dbh->sql_to_days('NOW()') . " - " .
|
||||
$dbh->sql_to_days('issuedate') . " >= " . $maxtokenage);
|
||||
$dbh->bz_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;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare("SELECT userid FROM tokens WHERE token = ?");
|
||||
|
||||
while ($duplicate) {
|
||||
++$tries;
|
||||
if ($tries > 100) {
|
||||
ThrowCodeError("token_generation_error");
|
||||
}
|
||||
$token = &::GenerateRandomPassword();
|
||||
$sth->execute($token);
|
||||
$duplicate = $sth->fetchrow_array;
|
||||
}
|
||||
|
||||
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) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Quote the token for inclusion in SQL statements.
|
||||
my $quotedtoken = &::SqlQuote($token);
|
||||
|
||||
# Get information about the token being cancelled.
|
||||
&::SendSQL("SELECT " . $dbh->sql_date_format('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.
|
||||
DeleteToken($token);
|
||||
}
|
||||
|
||||
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) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
&::SendSQL("SELECT token FROM tokens WHERE userid = $userid " .
|
||||
"AND (tokentype = 'emailnew' OR tokentype = 'emailold') " .
|
||||
$dbh->sql_limit(1));
|
||||
my ($token) = &::FetchSQLData();
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
sub GetTokenData($) {
|
||||
# Returns the userid, issuedate and eventdata for the specified token
|
||||
|
||||
my ($token) = @_;
|
||||
return unless defined $token;
|
||||
trick_taint($token);
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
return $dbh->selectrow_array(
|
||||
"SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
|
||||
FROM tokens
|
||||
WHERE token = ?", undef, $token);
|
||||
}
|
||||
|
||||
sub DeleteToken($) {
|
||||
# Deletes specified token
|
||||
|
||||
my ($token) = @_;
|
||||
return unless defined $token;
|
||||
trick_taint($token);
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->bz_lock_tables('tokens WRITE');
|
||||
$dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token);
|
||||
$dbh->bz_unlock_tables();
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Internal Functions
|
||||
################################################################################
|
||||
|
||||
sub _create_token($$$) {
|
||||
# Generates a unique token and inserts it into the database
|
||||
# Returns the token and the token timestamp
|
||||
my ($userid, $tokentype, $eventdata) = @_;
|
||||
|
||||
detaint_natural($userid);
|
||||
trick_taint($tokentype);
|
||||
trick_taint($eventdata);
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->bz_lock_tables('tokens WRITE');
|
||||
|
||||
my $token = GenerateUniqueToken();
|
||||
|
||||
$dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
|
||||
VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata));
|
||||
|
||||
$dbh->bz_unlock_tables();
|
||||
|
||||
if (wantarray) {
|
||||
my (undef, $token_ts, undef) = GetTokenData($token);
|
||||
$token_ts = str2time($token_ts);
|
||||
return ($token, $token_ts);
|
||||
} else {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,396 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
#
|
||||
|
||||
|
||||
package Bugzilla::User::Setting;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
# Module stuff
|
||||
@Bugzilla::User::Setting::EXPORT = qw(get_all_settings get_defaults
|
||||
add_setting);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util qw{trick_taint};
|
||||
|
||||
###############################
|
||||
### Module Initialization ###
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $setting_name = shift;
|
||||
my $user_id = shift;
|
||||
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Confirm that the $setting_name is properly formed;
|
||||
# if not, throw a code error.
|
||||
#
|
||||
# NOTE: due to the way that setting names are used in templates,
|
||||
# they must conform to to the limitations set for HTML NAMEs and IDs.
|
||||
#
|
||||
if ( !($setting_name =~ /^[a-zA-Z][-.:\w]*$/) ) {
|
||||
ThrowCodeError("setting_name_invalid", { name => $setting_name });
|
||||
}
|
||||
|
||||
# If there were only two parameters passed in, then we need
|
||||
# to retrieve the information for this setting ourselves.
|
||||
if (scalar @_ == 0) {
|
||||
|
||||
my ($default, $is_enabled, $value) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled, setting_value
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE name = ?
|
||||
AND profile_setting.user_id = ?},
|
||||
undef,
|
||||
$setting_name, $user_id);
|
||||
|
||||
# if not defined, then grab the default value
|
||||
if (! defined $value) {
|
||||
($default, $is_enabled) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled
|
||||
FROM setting
|
||||
WHERE name = ?},
|
||||
undef,
|
||||
$setting_name);
|
||||
}
|
||||
|
||||
$self->{'is_enabled'} = $is_enabled;
|
||||
$self->{'default_value'} = $default;
|
||||
|
||||
# IF the setting is enabled, AND the user has chosen a setting
|
||||
# THEN return that value
|
||||
# ELSE return the site default, and note that it is the default.
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$self->{'value'} = $value;
|
||||
} else {
|
||||
$self->{'value'} = $default;
|
||||
$self->{'isdefault'} = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# If the values were passed in, simply assign them and return.
|
||||
$self->{'is_enabled'} = shift;
|
||||
$self->{'default_value'} = shift;
|
||||
$self->{'value'} = shift;
|
||||
$self->{'is_default'} = shift;
|
||||
}
|
||||
|
||||
$self->{'_setting_name'} = $setting_name;
|
||||
$self->{'_user_id'} = $user_id;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Subroutine Definitions ###
|
||||
###############################
|
||||
|
||||
sub add_setting {
|
||||
my ($name, $values, $default_value) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return if _setting_exists($name);
|
||||
|
||||
($name && $values && $default_value)
|
||||
|| ThrowCodeError("setting_info_invalid");
|
||||
|
||||
print "Adding a new user setting called '$name'\n";
|
||||
$dbh->do(q{INSERT INTO setting (name, default_value, is_enabled)
|
||||
VALUES (?, ?, 1)},
|
||||
undef, ($name, $default_value));
|
||||
|
||||
my $sth = $dbh->prepare(q{INSERT INTO setting_value (name, value, sortindex)
|
||||
VALUES (?, ?, ?)});
|
||||
|
||||
my @values_list = keys %{$values};
|
||||
foreach my $key (@values_list){
|
||||
$sth->execute($name, $key, $values->{$key});
|
||||
}
|
||||
}
|
||||
|
||||
sub get_all_settings {
|
||||
my ($user_id) = @_;
|
||||
my $settings = get_defaults($user_id); # first get the defaults
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(
|
||||
q{SELECT name, default_value, is_enabled, setting_value
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE profile_setting.user_id = ?
|
||||
ORDER BY name});
|
||||
|
||||
$sth->execute($user_id);
|
||||
while (my ($name, $default_value, $is_enabled, $value)
|
||||
= $sth->fetchrow_array()) {
|
||||
|
||||
my $is_default;
|
||||
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$is_default = 0;
|
||||
} else {
|
||||
$value = $default_value;
|
||||
$is_default = 1;
|
||||
}
|
||||
|
||||
$settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
sub get_defaults {
|
||||
my ($user_id) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $default_settings = {};
|
||||
|
||||
$user_id ||= 0;
|
||||
|
||||
my $sth = $dbh->prepare(q{SELECT name, default_value, is_enabled
|
||||
FROM setting
|
||||
ORDER BY name});
|
||||
$sth->execute();
|
||||
while (my ($name, $default_value, $is_enabled) = $sth->fetchrow_array()) {
|
||||
|
||||
$default_settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled, $default_value, $default_value, 1);
|
||||
}
|
||||
|
||||
return $default_settings;
|
||||
}
|
||||
|
||||
sub set_default {
|
||||
my ($setting_name, $default_value, $is_enabled) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(q{UPDATE setting
|
||||
SET default_value = ?, is_enabled = ?
|
||||
WHERE name = ?});
|
||||
$sth->execute($default_value, $is_enabled, $setting_name);
|
||||
}
|
||||
|
||||
sub _setting_exists {
|
||||
my ($setting_name) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare("SELECT name FROM setting WHERE name = ?");
|
||||
$sth->execute($setting_name);
|
||||
return ($sth->rows) ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$self->{'legal_values'} = $dbh->selectcol_arrayref(
|
||||
q{SELECT value
|
||||
FROM setting_value
|
||||
WHERE name = ?
|
||||
ORDER BY sortindex},
|
||||
undef, $self->{'_setting_name'});
|
||||
|
||||
return $self->{'legal_values'};
|
||||
}
|
||||
|
||||
sub validate_value {
|
||||
my $self = shift;
|
||||
|
||||
if (grep(/^$_[0]$/, @{$self->legal_values()})) {
|
||||
trick_taint($_[0]);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('setting_value_invalid',
|
||||
{'name' => $self->{'_setting_name'},
|
||||
'value' => $_[0]});
|
||||
}
|
||||
}
|
||||
|
||||
sub reset_to_default {
|
||||
my ($self) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->do(q{ DELETE
|
||||
FROM profile_setting
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?},
|
||||
undef, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
$self->{'value'} = $self->{'default_value'};
|
||||
$self->{'is_default'} = 1;
|
||||
}
|
||||
|
||||
sub set {
|
||||
my ($self, $value) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $query;
|
||||
|
||||
if ($self->{'is_default'}) {
|
||||
$query = q{INSERT INTO profile_setting
|
||||
(setting_value, setting_name, user_id)
|
||||
VALUES (?,?,?)};
|
||||
} else {
|
||||
$query = q{UPDATE profile_setting
|
||||
SET setting_value = ?
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?};
|
||||
}
|
||||
$dbh->do($query, undef, $value, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
|
||||
$self->{'value'} = $value;
|
||||
$self->{'is_default'} = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting - Object for a user preference setting
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Setting.pm creates a setting object, which is a hash containing the user
|
||||
preference information for a single preference for a single user. These
|
||||
are usually accessed through the "settings" object of a user, and not
|
||||
directly.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
use Bugzilla::User::Setting;
|
||||
my $settings;
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id);
|
||||
|
||||
OR
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default);
|
||||
|
||||
=head1 CLASS FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<add_setting($name, $values, $default_value)>
|
||||
|
||||
Description: Checks for the existence of a setting, and adds it
|
||||
to the database if it does not yet exist.
|
||||
Params: C<$name> - string - the name of the new setting
|
||||
C<$values> - hash - contains the new values (key) and
|
||||
sortindexes for the new setting
|
||||
C<$default_value> - string - the site default
|
||||
Returns: a pointer to a hash of settings
|
||||
#
|
||||
#
|
||||
=item C<get_all_settings($user_id)>
|
||||
|
||||
Description: Provides the user's choices for each setting in the
|
||||
system; if the user has made no choice, uses the site
|
||||
default instead.
|
||||
Params: C<$user_id> - integer - the user id.
|
||||
Returns: a pointer to a hash of settings
|
||||
|
||||
=item C<get_defaults($user_id)>
|
||||
|
||||
Description: When a user is not logged in, they must use the site
|
||||
defaults for every settings; this subroutine provides them.
|
||||
Params: C<$user_id> (optional) - integer - the user id. Note that
|
||||
this optional parameter is mainly for internal use only.
|
||||
Returns: A pointer to a hash of settings. If $user_id was passed, set
|
||||
the user_id value for each setting.
|
||||
|
||||
=item C<set_default($setting_name, $default_value, $is_enabled)>
|
||||
|
||||
Description: Sets the global default for a given setting. Also sets
|
||||
whether users are allowed to choose their own value for
|
||||
this setting, or if they must use the global default.
|
||||
Params: C<$setting_name> - string - the name of the setting
|
||||
C<$default_value> - string - the new default value for this setting
|
||||
C<$is_enabled> - boolean - if false, all users must use the global default
|
||||
Returns: nothing
|
||||
|
||||
=begin private
|
||||
|
||||
=item C<_setting_exists>
|
||||
|
||||
Description: Determines if a given setting exists in the database.
|
||||
Params: C<$setting_name> - string - the setting name
|
||||
Returns: boolean - true if the setting already exists in the DB.
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<legal_values($setting_name)>
|
||||
|
||||
Description: Returns all legal values for this setting
|
||||
Params: none
|
||||
Returns: A reference to an array containing all legal values
|
||||
|
||||
=item C<validate_value>
|
||||
|
||||
Description: Determines whether a value is valid for the setting
|
||||
by checking against the list of legal values.
|
||||
Untaints the parameter if the value is indeed valid,
|
||||
and throws a setting_value_invalid code error if not.
|
||||
Params: An lvalue containing a candidate for a setting value
|
||||
Returns: nothing
|
||||
|
||||
=item C<reset_to_default>
|
||||
|
||||
Description: If a user chooses to use the global default for a given
|
||||
setting, their saved entry is removed from the database via
|
||||
this subroutine.
|
||||
Params: none
|
||||
Returns: nothing
|
||||
|
||||
=item C<set($value)>
|
||||
|
||||
Description: If a user chooses to use their own value rather than the
|
||||
global value for a given setting, OR changes their value for
|
||||
a given setting, this subroutine is called to insert or
|
||||
update the database as appropriate.
|
||||
Params: C<$value> - string - the new value for this setting for this user.
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,624 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Util;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
|
||||
detaint_signed
|
||||
html_quote url_quote value_quote xml_quote
|
||||
css_class_quote
|
||||
i_am_cgi
|
||||
lsearch max min
|
||||
diff_arrays diff_strings
|
||||
trim wrap_comment find_wrap_point
|
||||
format_time format_time_decimal
|
||||
file_mod_time
|
||||
bz_crypt);
|
||||
|
||||
use Bugzilla::Config;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Constants;
|
||||
use Date::Parse;
|
||||
use Date::Format;
|
||||
use Text::Wrap;
|
||||
|
||||
# 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];
|
||||
my ($match) = $_[0] =~ /^(.*)$/s;
|
||||
$_[0] = $match;
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub detaint_natural {
|
||||
my ($match) = $_[0] =~ /^(\d+)$/;
|
||||
$_[0] = $match;
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub detaint_signed {
|
||||
my ($match) = $_[0] =~ /^([-+]?\d+)$/;
|
||||
$_[0] = $match;
|
||||
# Remove any leading plus sign.
|
||||
if (defined($_[0]) && $_[0] =~ /^\+(\d+)$/) {
|
||||
$_[0] = $1;
|
||||
}
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
sub html_quote {
|
||||
my ($var) = (@_);
|
||||
$var =~ s/\&/\&/g;
|
||||
$var =~ s/</\</g;
|
||||
$var =~ s/>/\>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
return $var;
|
||||
}
|
||||
|
||||
# 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 i_am_cgi {
|
||||
# I use SERVER_SOFTWARE because it's required to be
|
||||
# defined for all requests in the CGI spec.
|
||||
return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
|
||||
}
|
||||
|
||||
sub 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 diff_arrays {
|
||||
my ($old_ref, $new_ref) = @_;
|
||||
|
||||
my @old = @$old_ref;
|
||||
my @new = @$new_ref;
|
||||
|
||||
# For each pair of (old, new) entries:
|
||||
# If they're equal, set them to empty. When done, @old contains entries
|
||||
# that were removed; @new contains ones that got added.
|
||||
foreach my $oldv (@old) {
|
||||
foreach my $newv (@new) {
|
||||
next if ($newv eq '');
|
||||
if ($oldv eq $newv) {
|
||||
$newv = $oldv = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @removed = grep { $_ ne '' } @old;
|
||||
my @added = grep { $_ ne '' } @new;
|
||||
return (\@removed, \@added);
|
||||
}
|
||||
|
||||
sub trim {
|
||||
my ($str) = @_;
|
||||
if ($str) {
|
||||
$str =~ s/^\s+//g;
|
||||
$str =~ s/\s+$//g;
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
sub diff_strings {
|
||||
my ($oldstr, $newstr) = @_;
|
||||
|
||||
# Split the old and new strings into arrays containing their values.
|
||||
$oldstr =~ s/[\s,]+/ /g;
|
||||
$newstr =~ s/[\s,]+/ /g;
|
||||
my @old = split(" ", $oldstr);
|
||||
my @new = split(" ", $newstr);
|
||||
|
||||
my ($rem, $add) = diff_arrays(\@old, \@new);
|
||||
|
||||
my $removed = join (", ", @$rem);
|
||||
my $added = join (", ", @$add);
|
||||
|
||||
return ($removed, $added);
|
||||
}
|
||||
|
||||
sub wrap_comment ($) {
|
||||
my ($comment) = @_;
|
||||
my $wrappedcomment = "";
|
||||
|
||||
# Use 'local', as recommended by Text::Wrap's perldoc.
|
||||
local $Text::Wrap::columns = COMMENT_COLS;
|
||||
# Make words that are longer than COMMENT_COLS not wrap.
|
||||
local $Text::Wrap::huge = 'overflow';
|
||||
# Don't mess with tabs.
|
||||
local $Text::Wrap::unexpand = 0;
|
||||
|
||||
# If the line starts with ">", don't wrap it. Otherwise, wrap.
|
||||
foreach my $line (split(/\r\n|\r|\n/, $comment)) {
|
||||
if ($line =~ qr/^>/) {
|
||||
$wrappedcomment .= ($line . "\n");
|
||||
}
|
||||
else {
|
||||
$wrappedcomment .= (wrap('', '', $line) . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
return $wrappedcomment;
|
||||
}
|
||||
|
||||
sub find_wrap_point ($$) {
|
||||
my ($string, $maxpos) = @_;
|
||||
if (!$string) { return 0 }
|
||||
if (length($string) < $maxpos) { return length($string) }
|
||||
my $wrappoint = rindex($string, ",", $maxpos); # look for comma
|
||||
if ($wrappoint < 0) { # can't find comma
|
||||
$wrappoint = rindex($string, " ", $maxpos); # look for space
|
||||
if ($wrappoint < 0) { # can't find space
|
||||
$wrappoint = rindex($string, "-", $maxpos); # look for hyphen
|
||||
if ($wrappoint < 0) { # can't find hyphen
|
||||
$wrappoint = $maxpos; # just truncate it
|
||||
} else {
|
||||
$wrappoint++; # leave hyphen on the left side
|
||||
}
|
||||
}
|
||||
}
|
||||
return $wrappoint;
|
||||
}
|
||||
|
||||
sub format_time ($;$) {
|
||||
my ($date, $format) = @_;
|
||||
|
||||
# If $format is undefined, try to guess the correct date format.
|
||||
my $show_timezone;
|
||||
if (!defined($format)) {
|
||||
if ($date =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) {
|
||||
my $sec = $7;
|
||||
if (defined $sec) {
|
||||
$format = "%Y-%m-%d %T";
|
||||
} else {
|
||||
$format = "%Y-%m-%d %R";
|
||||
}
|
||||
} else {
|
||||
# Default date format. See Date::Format for other formats available.
|
||||
$format = "%Y-%m-%d %R";
|
||||
}
|
||||
# By default, we want the timezone to be displayed.
|
||||
$show_timezone = 1;
|
||||
}
|
||||
else {
|
||||
# Search for %Z or %z, meaning we want the timezone to be displayed.
|
||||
# Till bug 182238 gets fixed, we assume Param('timezone') is used.
|
||||
$show_timezone = ($format =~ s/\s?%Z$//i);
|
||||
}
|
||||
|
||||
# str2time($date) is undefined if $date has an invalid date format.
|
||||
my $time = str2time($date);
|
||||
|
||||
if (defined $time) {
|
||||
$date = time2str($format, $time);
|
||||
$date .= " " . &::Param('timezone') if $show_timezone;
|
||||
}
|
||||
else {
|
||||
# Don't let invalid (time) strings to be passed to templates!
|
||||
$date = '';
|
||||
}
|
||||
return trim($date);
|
||||
}
|
||||
|
||||
sub format_time_decimal {
|
||||
my ($time) = (@_);
|
||||
|
||||
my $newtime = sprintf("%.2f", $time);
|
||||
|
||||
if ($newtime =~ /0\Z/) {
|
||||
$newtime = sprintf("%.1f", $time);
|
||||
}
|
||||
|
||||
return $newtime;
|
||||
}
|
||||
|
||||
sub file_mod_time ($) {
|
||||
my ($filename) = (@_);
|
||||
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
|
||||
$atime,$mtime,$ctime,$blksize,$blocks)
|
||||
= stat($filename);
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
sub bz_crypt ($) {
|
||||
my ($password) = @_;
|
||||
|
||||
# The list of characters that can appear in a salt. Salts and hashes
|
||||
# are both encoded as a sequence of characters from a set containing
|
||||
# 64 characters, each one of which represents 6 bits of the salt/hash.
|
||||
# The encoding is similar to BASE64, the difference being that the
|
||||
# BASE64 plus sign (+) is replaced with a forward slash (/).
|
||||
my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
|
||||
|
||||
# Generate the salt. We use an 8 character (48 bit) salt for maximum
|
||||
# security on systems whose crypt uses MD5. Systems with older
|
||||
# versions of crypt will just use the first two characters of the salt.
|
||||
my $salt = '';
|
||||
for ( my $i=0 ; $i < 8 ; ++$i ) {
|
||||
$salt .= $saltchars[rand(64)];
|
||||
}
|
||||
|
||||
# Crypt the password.
|
||||
my $cryptedpassword = crypt($password, $salt);
|
||||
|
||||
# Return the crypted password.
|
||||
return $cryptedpassword;
|
||||
}
|
||||
|
||||
sub ValidateDate {
|
||||
my ($date, $format) = @_;
|
||||
my $date2;
|
||||
|
||||
# $ts is undefined if the parser fails.
|
||||
my $ts = str2time($date);
|
||||
if ($ts) {
|
||||
$date2 = time2str("%Y-%m-%d", $ts);
|
||||
|
||||
$date =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
|
||||
$date2 =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
|
||||
}
|
||||
if (!$ts || $date ne $date2) {
|
||||
ThrowUserError('illegal_date', {date => $date, format => $format});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Util - Generic utility functions for bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Util;
|
||||
|
||||
# Functions for dealing with variable tainting
|
||||
$rv = is_tainted($var);
|
||||
trick_taint($var);
|
||||
detaint_natural($var);
|
||||
detaint_signed($var);
|
||||
|
||||
# Functions for quoting
|
||||
html_quote($var);
|
||||
url_quote($var);
|
||||
value_quote($var);
|
||||
xml_quote($var);
|
||||
|
||||
# Functions for searching
|
||||
$loc = lsearch(\@arr, $val);
|
||||
$val = max($a, $b, $c);
|
||||
$val = min($a, $b, $c);
|
||||
|
||||
# Data manipulation
|
||||
($removed, $added) = diff_arrays(\@old, \@new);
|
||||
|
||||
# Functions for manipulating strings
|
||||
$val = trim(" abc ");
|
||||
($removed, $added) = diff_strings($old, $new);
|
||||
$wrapped = wrap_comment($comment);
|
||||
|
||||
# Functions for formatting time
|
||||
format_time($time);
|
||||
|
||||
# Functions for dealing with files
|
||||
$time = file_mod_time($filename);
|
||||
|
||||
# Cryptographic Functions
|
||||
$crypted_password = bz_crypt($password);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package contains various utility functions which do not belong anywhere
|
||||
else.
|
||||
|
||||
B<It is not intended as a general dumping group for something which
|
||||
people feel might be useful somewhere, someday>. Do not add methods to this
|
||||
package unless it is intended to be used for a significant number of files,
|
||||
and it does not belong anywhere else.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
This package provides several types of routines:
|
||||
|
||||
=head2 Tainting
|
||||
|
||||
Several functions are available to deal with tainted variables. B<Use these
|
||||
with care> to avoid security holes.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<is_tainted>
|
||||
|
||||
Determines whether a particular variable is tainted
|
||||
|
||||
=item C<trick_taint($val)>
|
||||
|
||||
Tricks perl into untainting a particular variable.
|
||||
|
||||
Use trick_taint() when you know that there is no way that the data
|
||||
in a scalar can be tainted, but taint mode still bails on it.
|
||||
|
||||
B<WARNING!! Using this routine on data that really could be tainted defeats
|
||||
the purpose of taint mode. It should only be used on variables that have been
|
||||
sanity checked in some way and have been determined to be OK.>
|
||||
|
||||
=item C<detaint_natural($num)>
|
||||
|
||||
This routine detaints a natural number. It returns a true value if the
|
||||
value passed in was a valid natural number, else it returns false. You
|
||||
B<MUST> check the result of this routine to avoid security holes.
|
||||
|
||||
=item C<detaint_signed($num)>
|
||||
|
||||
This routine detaints a signed integer. It returns a true value if the
|
||||
value passed in was a valid signed integer, else it returns false. You
|
||||
B<MUST> check the result of this routine to avoid security holes.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Quoting
|
||||
|
||||
Some values may need to be quoted from perl. However, this should in general
|
||||
be done in the template where possible.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<html_quote($val)>
|
||||
|
||||
Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, and E<34> being
|
||||
replaced with their appropriate HTML entities.
|
||||
|
||||
=item C<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 Data Manipulation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<diff_arrays(\@old, \@new)>
|
||||
|
||||
Description: Takes two arrayrefs, and will tell you what it takes to
|
||||
get from @old to @new.
|
||||
Params: @old = array that you are changing from
|
||||
@new = array that you are changing to
|
||||
Returns: A list of two arrayrefs. The first is a reference to an
|
||||
array containing items that were removed from @old. The
|
||||
second is a reference to an array containing items
|
||||
that were added to @old. If both returned arrays are
|
||||
empty, @old and @new contain the same values.
|
||||
|
||||
=back
|
||||
|
||||
=head2 String Manipulation
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<trim($str)>
|
||||
|
||||
Removes any leading or trailing whitespace from a string. This routine does not
|
||||
modify the existing string.
|
||||
|
||||
=item C<diff_strings($oldstr, $newstr)>
|
||||
|
||||
Takes two strings containing a list of comma- or space-separated items
|
||||
and returns what items were removed from or added to the new one,
|
||||
compared to the old one. Returns a list, where the first entry is a scalar
|
||||
containing removed items, and the second entry is a scalar containing added
|
||||
items.
|
||||
|
||||
=item C<wrap_comment($comment)>
|
||||
|
||||
Takes a bug comment, and wraps it to the appropriate length. The length is
|
||||
currently specified in C<Bugzilla::Constants::COMMENT_COLS>. Lines beginning
|
||||
with ">" are assumed to be quotes, and they will not be wrapped.
|
||||
|
||||
The intended use of this function is to wrap comments that are about to be
|
||||
displayed or emailed. Generally, wrapped text should not be stored in the
|
||||
database.
|
||||
|
||||
=item C<find_wrap_point($string, $maxpos)>
|
||||
|
||||
Search for a comma, a whitespace or a hyphen to split $string, within the first
|
||||
$maxpos characters. If none of them is found, just split $string at $maxpos.
|
||||
The search starts at $maxpos and goes back to the beginning of the string.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Formatting Time
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<format_time($time)>
|
||||
|
||||
Takes a time, converts it to the desired format and appends the timezone
|
||||
as defined in editparams.cgi, if desired. This routine will be expanded
|
||||
in the future to adjust for user preferences regarding what timezone to
|
||||
display times in.
|
||||
|
||||
This routine is mainly called from templates to filter dates, see
|
||||
"FILTER time" in Templates.pm. In this case, $format is undefined and
|
||||
the routine has to "guess" the date format that was passed to $dbh->sql_date_format().
|
||||
|
||||
|
||||
=item C<format_time_decimal($time)>
|
||||
|
||||
Returns a number with 2 digit precision, unless the last digit is a 0. Then it
|
||||
returns only 1 digit precision.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Files
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<file_mod_time($filename)>
|
||||
|
||||
Takes a filename and returns the modification time. It returns it in the format
|
||||
of the "mtime" parameter of the perl "stat" function.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Cryptography
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<bz_crypt($password)>
|
||||
|
||||
Takes a string and returns a C<crypt>ed value for it, using a random salt.
|
||||
|
||||
Please always use this function instead of the built-in perl "crypt"
|
||||
when initially encrypting a password.
|
||||
|
||||
=begin undocumented
|
||||
|
||||
Random salts are generated because the alternative is usually
|
||||
to use the first two characters of the password itself, and since
|
||||
the salt appears in plaintext at the beginning of the encrypted
|
||||
password string this has the effect of revealing the first two
|
||||
characters of the password to anyone who views the encrypted version.
|
||||
|
||||
=end undocumented
|
||||
|
||||
=back
|
||||
@@ -1,374 +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;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::User;
|
||||
|
||||
# Used in LogActivityEntry(). Gives the max length of lines in the
|
||||
# activity table.
|
||||
use constant MAX_LINE_LENGTH => 254;
|
||||
|
||||
# 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 and log out. (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$:) {
|
||||
# For security reasons, log out users when Bugzilla is down.
|
||||
# Bugzilla->login() is required to catch the logincookie, if any.
|
||||
my $user = Bugzilla->login(LOGIN_OPTIONAL);
|
||||
my $userid = $user->id;
|
||||
Bugzilla->logout();
|
||||
|
||||
# Return the appropriate HTTP response headers.
|
||||
print Bugzilla->cgi->header();
|
||||
|
||||
$::vars->{'message'} = "shutdown";
|
||||
$::vars->{'userid'} = $userid;
|
||||
# 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 ($cgi, # a CGI object
|
||||
$fieldname, # the fieldname to check
|
||||
$legalsRef # (optional) ref to a list of legal values
|
||||
) = @_;
|
||||
|
||||
if (!defined $cgi->param($fieldname)
|
||||
|| trim($cgi->param($fieldname)) eq ""
|
||||
|| (defined($legalsRef)
|
||||
&& lsearch($legalsRef, $cgi->param($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 });
|
||||
}
|
||||
}
|
||||
|
||||
# check and see if a given field is defined, and abort if not
|
||||
sub CheckFormFieldDefined ($$) {
|
||||
my ($cgi, # a CGI object
|
||||
$fieldname, # the fieldname to check
|
||||
) = @_;
|
||||
|
||||
if (!defined $cgi->param($fieldname)) {
|
||||
ThrowCodeError("undefined_field", { field => $fieldname });
|
||||
}
|
||||
}
|
||||
|
||||
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 = bug_alias_to_id($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 Bugzilla->user->can_see_bug($id);
|
||||
|
||||
# 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 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 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) > MAX_LINE_LENGTH) {
|
||||
my $commaposition = find_wrap_point($removed, MAX_LINE_LENGTH);
|
||||
$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) > MAX_LINE_LENGTH) {
|
||||
my $commaposition = find_wrap_point($added, MAX_LINE_LENGTH);
|
||||
$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 = "";
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
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 COALESCE(attachments.isprivate, 0) = 0";
|
||||
}
|
||||
my $query = "
|
||||
SELECT COALESCE(fielddefs.description, "
|
||||
# This is a hack - PostgreSQL requires both COALESCE
|
||||
# arguments to be of the same type, and this is the only
|
||||
# way supported by both MySQL 3 and PostgreSQL to convert
|
||||
# an integer to a string. MySQL 4 supports CAST.
|
||||
. $dbh->sql_string_concat('bugs_activity.fieldid', q{''}) .
|
||||
"), fielddefs.name, bugs_activity.attach_id, " .
|
||||
$dbh->sql_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
|
||||
INNER JOIN profiles
|
||||
ON profiles.userid = bugs_activity.who
|
||||
WHERE bugs_activity.bug_id = $id
|
||||
$datepart
|
||||
$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' ||
|
||||
$fieldname eq 'deadline') {
|
||||
|
||||
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;
|
||||
|
||||
$::buffer = $::cgi->query_string();
|
||||
|
||||
# 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,67 +0,0 @@
|
||||
<!ELEMENT bugzilla (bug+)>
|
||||
<!ATTLIST bugzilla
|
||||
version CDATA #REQUIRED
|
||||
urlbase CDATA #REQUIRED
|
||||
maintainer CDATA #REQUIRED
|
||||
exporter CDATA #IMPLIED
|
||||
>
|
||||
<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, 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)?, group*, long_desc*, attachment*)?)>
|
||||
<!ATTLIST bug
|
||||
error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
|
||||
>
|
||||
<!ELEMENT bug_id (#PCDATA)>
|
||||
<!ELEMENT alias (#PCDATA)>
|
||||
<!ELEMENT reporter_accessible (#PCDATA)>
|
||||
<!ELEMENT cclist_accessible (#PCDATA)>
|
||||
<!ELEMENT exporter (#PCDATA)>
|
||||
<!ELEMENT urlbase (#PCDATA)>
|
||||
<!ELEMENT bug_status (#PCDATA)>
|
||||
<!ELEMENT classification_id (#PCDATA)>
|
||||
<!ELEMENT classification (#PCDATA)>
|
||||
<!ELEMENT product (#PCDATA)>
|
||||
<!ELEMENT priority (#PCDATA)>
|
||||
<!ELEMENT version (#PCDATA)>
|
||||
<!ELEMENT rep_platform (#PCDATA)>
|
||||
<!ELEMENT assigned_to (#PCDATA)>
|
||||
<!ELEMENT delta_ts (#PCDATA)>
|
||||
<!ELEMENT component (#PCDATA)>
|
||||
<!ELEMENT reporter (#PCDATA)>
|
||||
<!ELEMENT target_milestone (#PCDATA)>
|
||||
<!ELEMENT bug_severity (#PCDATA)>
|
||||
<!ELEMENT creation_ts (#PCDATA)>
|
||||
<!ELEMENT qa_contact (#PCDATA)>
|
||||
<!ELEMENT status_whiteboard (#PCDATA)>
|
||||
<!ELEMENT op_sys (#PCDATA)>
|
||||
<!ELEMENT resolution (#PCDATA)>
|
||||
<!ELEMENT bug_file_loc (#PCDATA)>
|
||||
<!ELEMENT short_desc (#PCDATA)>
|
||||
<!ELEMENT keywords (#PCDATA)>
|
||||
<!ELEMENT dependson (#PCDATA)>
|
||||
<!ELEMENT blocked (#PCDATA)>
|
||||
<!ELEMENT votes (#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?, flag*)>
|
||||
<!ATTLIST attachment
|
||||
isobsolete (0|1) #IMPLIED
|
||||
ispatch (0|1) #IMPLIED
|
||||
>
|
||||
<!ELEMENT attachid (#PCDATA)>
|
||||
<!ELEMENT date (#PCDATA)>
|
||||
<!ELEMENT desc (#PCDATA)>
|
||||
<!ELEMENT type (#PCDATA)>
|
||||
<!ELEMENT data (#PCDATA)>
|
||||
<!ELEMENT flag EMPTY>
|
||||
<!ATTLIST flag
|
||||
name CDATA #REQUIRED
|
||||
status CDATA #REQUIRED
|
||||
setter CDATA #IMPLIED
|
||||
requestee CDATA #IMPLIED
|
||||
>
|
||||
@@ -1,316 +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 Bugzilla::User;
|
||||
|
||||
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("auth_failure", {group => Param("chartgroup"),
|
||||
action => "use",
|
||||
object => "charts"});
|
||||
|
||||
# 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");
|
||||
|
||||
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,158 +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;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::User;
|
||||
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");
|
||||
|
||||
if (Param("useclassification")) {
|
||||
push(@masterlist, "classification");
|
||||
}
|
||||
|
||||
push(@masterlist, ("product", "component", "version", "op_sys"));
|
||||
|
||||
if (Param("usevotes")) {
|
||||
push (@masterlist, "votes");
|
||||
}
|
||||
if (Param("usebugaliases")) {
|
||||
unshift(@masterlist, "alias");
|
||||
}
|
||||
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", "deadline"));
|
||||
}
|
||||
|
||||
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')? 1: 0;
|
||||
}
|
||||
}
|
||||
my $list = join(" ", @collist);
|
||||
my $urlbase = Param("urlbase");
|
||||
|
||||
if ($list) {
|
||||
$cgi->send_cookie(-name => 'COLUMNLIST',
|
||||
-value => $list,
|
||||
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
|
||||
}
|
||||
else {
|
||||
$cgi->remove_cookie('COLUMNLIST');
|
||||
}
|
||||
if ($splitheader) {
|
||||
$cgi->send_cookie(-name => 'SPLITHEADER',
|
||||
-value => $splitheader,
|
||||
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
|
||||
}
|
||||
else {
|
||||
$cgi->remove_cookie('SPLITHEADER');
|
||||
}
|
||||
|
||||
$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,513 +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 {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $rows = $dbh->selectall_arrayref("SELECT dupe_of, dupe FROM duplicates");
|
||||
|
||||
my %dupes;
|
||||
my %count;
|
||||
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.
|
||||
foreach my $row (@$rows) {
|
||||
my ($dupe_of, $dupe) = @$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 $dbh = Bugzilla->dbh;
|
||||
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 products.name = " . SqlQuote($product);
|
||||
$from_product = "INNER JOIN products " .
|
||||
"ON bugs.product_id = products.id";
|
||||
}
|
||||
|
||||
# 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 " . $dbh->sql_to_days('creation_ts') . " AS start, " .
|
||||
$dbh->sql_to_days('current_date') . " AS end, " .
|
||||
$dbh->sql_to_days("'1970-01-01'") .
|
||||
" FROM bugs $from_product WHERE " .
|
||||
$dbh->sql_to_days('creation_ts') . " != 'NULL'" .
|
||||
$and_product .
|
||||
" ORDER BY start " . $dbh->sql_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 " .
|
||||
"INNER JOIN fielddefs " .
|
||||
" ON bugs_activity.fieldid = fielddefs.fieldid " .
|
||||
" WHERE fielddefs.name = 'bug_status' " .
|
||||
" AND bugs_activity.bug_id = $bug " .
|
||||
" AND bugs_activity.bug_when >= from_days($day) " .
|
||||
"ORDER BY bugs_activity.bug_when " .
|
||||
$dbh->sql_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 " .
|
||||
"INNER JOIN fielddefs " .
|
||||
" ON bugs_activity.fieldid = fielddefs.fieldid " .
|
||||
" WHERE fielddefs.name = 'resolution' " .
|
||||
" AND bugs_activity.bug_id = $bug " .
|
||||
" AND bugs_activity.bug_when >= from_days($day) " .
|
||||
"ORDER BY bugs_activity.bug_when " .
|
||||
$dbh->sql_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,115 +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 strict;
|
||||
|
||||
# Include the Bugzilla CGI and general utility library.
|
||||
use lib qw(.);
|
||||
require "CGI.pl";
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
|
||||
# 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);
|
||||
|
||||
Bugzilla->login(LOGIN_OPTIONAL);
|
||||
|
||||
# If the 'requirelogin' parameter is on and the user is not
|
||||
# authenticated, return empty fields.
|
||||
if (Param('requirelogin') && !Bugzilla->user->id) {
|
||||
display_data();
|
||||
}
|
||||
|
||||
# Retrieve this installation's configuration.
|
||||
GetVersionTable();
|
||||
|
||||
# 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'} = [Bugzilla->dbh->bz_get_field_defs()];
|
||||
|
||||
display_data($vars);
|
||||
|
||||
|
||||
sub display_data {
|
||||
my $vars = shift;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
# Determine how the user would like to receive the output;
|
||||
# default is JavaScript.
|
||||
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());
|
||||
exit;
|
||||
}
|
||||
@@ -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.
|
||||
#
|
||||
# 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 $dbh = Bugzilla->dbh;
|
||||
|
||||
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 " .
|
||||
$dbh->sql_istrcmp('login_name', $dbh->quote($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 " . $dbh->sql_istrcmp(
|
||||
'login_name', $dbh->quote($username), $dbh->sql_regexp());
|
||||
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 " .$dbh->sql_istrcmp(
|
||||
'login_name', $dbh->quote($username), $dbh->sql_regexp());
|
||||
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 a default assignee for every product/version/component.
|
||||
He owns the bug by default. The default assignee 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,301 +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
|
||||
|
||||
class ErrorURLopener(urllib.URLopener):
|
||||
"""URLopener that handles HTTP 404s"""
|
||||
def http_error_404(self, url, fp, errcode, errmsg, headers, *extra):
|
||||
raise ValueError, errmsg # 'File Not Found'
|
||||
|
||||
# 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)
|
||||
try:
|
||||
url = ErrorURLopener().open("%s/post_bug.cgi" % bugzilla, postdata)
|
||||
except ValueError:
|
||||
error("Bugzilla site at %s not found (HTTP returned 404)" % bugzilla)
|
||||
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
|
||||
if ret.find("process_bug.cgi") == -1:
|
||||
error("could not post bug to %s: are you sure this "
|
||||
"is Bugzilla instance's top-level directory?" % bugzilla)
|
||||
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,221 +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-stdin </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>bugzilla-submit accepts a single argument:
|
||||
<replaceable>bugzilla-url</replaceable>. Its value must match the
|
||||
relevant Bugzilla instance's base URL (technically, its
|
||||
<replaceable>urlbase</replaceable> param). The program also accepts the
|
||||
following options to set or override fields:</para>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>-b, --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>
|
||||
|
||||
<para>For example, if your Bugzilla instance is at
|
||||
"http://landfill.bugzilla.org/bztest/", and your login and password
|
||||
there are "john@doe.com" and "foo", respectively, your
|
||||
<filename>.netrc</filename> entry should look something like:</para>
|
||||
|
||||
<screen>
|
||||
machine "http://landfill.bugzilla.org/bztest/"
|
||||
login john@doe.com
|
||||
password foo
|
||||
|
||||
</screen>
|
||||
|
||||
<para>Note that the machine entry should match exactly the instance URL
|
||||
specified to <application>bugzilla-submit</application>.</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,197 +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;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# 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 " .
|
||||
$dbh->sql_istrcmp('login_name', $dbh->quote($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,7 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
thisdir=`dirname "$0"`
|
||||
bugids=`$thisdir/bugids "$@"`
|
||||
if test "$?" != "0"; then echo "$bugids" 1>&2; exit 1; fi
|
||||
|
||||
echo "$bugids" | wc -w
|
||||
@@ -1,32 +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@mathweb.org>.
|
||||
# Corporation. Portions created by Andreas Franke are
|
||||
# Copyright (C) 2001,2005 Andreas Franke. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
|
||||
thisdir=`dirname "$0"`
|
||||
buglist="$thisdir/buglist"
|
||||
csvfile="$thisdir/buglist.csv"
|
||||
|
||||
$thisdir/buglist "$@" 2>&1 1>${csvfile}
|
||||
if test "$?" != "0"; then cat "$csvfile" 1>&2; exit 1; fi
|
||||
|
||||
# 1. use 'awk' to select the first column (bug_id)
|
||||
# 2. use 'grep -v' to remove the first line with the column headers
|
||||
# 3. use backquotes & 'echo' to get all values in one line, space separated
|
||||
echo `cat ${csvfile} | awk -F, '{printf $1 "\n"}' | grep -v bug_id`
|
||||
@@ -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@mathweb.org>.
|
||||
# Corporation. Portions created by Andreas Franke are
|
||||
# Copyright (C) 2001,2005 Andreas Franke. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
|
||||
defaultcolumnlist="severity priority platform status resolution target_milestone status_whiteboard keywords summaryfull"
|
||||
|
||||
thisdir=`dirname "$0"`
|
||||
query=`$thisdir/makequery "$@"`
|
||||
if test "$?" != "0"; then exit 1; fi
|
||||
|
||||
outputfile="/dev/stdout"
|
||||
#outputfile="buglist.html"
|
||||
#\rm -f ${outputfile}
|
||||
wget -q -O ${outputfile} --header="Cookie: COLUMNLIST=${COLUMNLIST-${defaultcolumnlist}}" "${query}"
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
thisdir=`dirname "$0"`
|
||||
bugids=`$thisdir/bugids "$@"` || echo "$bugids" 1>&2 && exit 1
|
||||
|
||||
echo "$bugids" | sed -e 's/ /\,/g'
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
thisdir=`dirname "$0"`
|
||||
bugids=`$thisdir/bugids "$@"` || echo "$bugids" 1>&2 && exit 1
|
||||
|
||||
bugids=`echo "$bugids" | sed -e 's/ /\,/g'`
|
||||
echo "https://bugzilla.mozilla.org/buglist.cgi?ctype=html&bug_id=$bugids"
|
||||
|
||||
@@ -1,108 +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@mathweb.org>.
|
||||
# Corporation. Portions created by Andreas Franke are
|
||||
# Copyright (C) 2001,2005 Andreas Franke. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
|
||||
conf="`dirname $0`/query.conf"
|
||||
|
||||
query="https://bugzilla.mozilla.org/buglist.cgi?ctype=csv"
|
||||
|
||||
chart=0
|
||||
and=0
|
||||
while test "X$1" != "X"; do
|
||||
arg="$1"
|
||||
shift
|
||||
if test 0 != `expr "X$arg" : 'X--[^=]*\$'`; then
|
||||
# long option: --name val (without '=')
|
||||
name=`expr "X$arg" : 'X--\(.*\)'`
|
||||
val="$1"
|
||||
shift
|
||||
elif test 0 != `expr "X$arg" : 'X--[^=][^=]*='`; then
|
||||
# long option: --name=val
|
||||
name=`expr "X$arg" : 'X--\([^=]*\)'`
|
||||
val=`expr "X$arg" : 'X--[^=]*=\(.*\)'`
|
||||
elif test 0 != `expr "X$arg" : 'X-[a-zA-Z]\$'`; then
|
||||
# short option like -X foo (with space in between)
|
||||
name=`expr "X$arg" : 'X-\(.\)'`
|
||||
val="$1"
|
||||
shift
|
||||
elif test 0 != `expr "X$arg" : 'X-[a-zA-Z]='`; then
|
||||
# reject things like -X=foo
|
||||
echo "Unrecognized option $arg" 1>&2
|
||||
echo "Use -Xfoo or -X foo instead of -X=foo" 1>&2
|
||||
exit 1
|
||||
elif test 0 != `expr "X$arg" : 'X-[a-zA-Z]'`; then
|
||||
# short option like -Xfoo (without space)
|
||||
name=`expr "X$arg" : 'X-\(.\)'`
|
||||
val=`expr "X$arg" : 'X-.\(.*\)'`
|
||||
else
|
||||
name="default"
|
||||
val="$arg"
|
||||
#echo "Unrecognized option $arg" 1>&2
|
||||
#exit 1
|
||||
fi
|
||||
|
||||
# plausibility check: val must not be empty, nor start with '-'
|
||||
if test "X$val" = "X"; then
|
||||
echo "No value found for '$name'!" 1>&2
|
||||
exit 1
|
||||
elif test 0 != `expr "X$val" : "X-"` && \
|
||||
test 0 = `expr "X$val" : "X---"`; then
|
||||
echo "Suspicious value for '$name': '$val' looks like an option!" 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 "X$field" = "X" || test "X$type" = "X"; then
|
||||
if test "X$name" = "Xdefault"; then
|
||||
echo 1>&2 "Error: unexpected argument '$arg'"
|
||||
cat 1>&2 <<EOF
|
||||
Use short options like -P1 or long options like --priority=1 ,
|
||||
or enable the 'default' behaviour in the 'query.conf' file.
|
||||
EOF
|
||||
else
|
||||
echo "Unknown field name '$name'." 1>&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# split val into comma-separated alternative values
|
||||
or=0
|
||||
while test "X$val" != "X"; do
|
||||
# val1 gets everything before the first comma; val gets the rest
|
||||
if test 0 != `expr "X$val" : 'X[^,]*,'`; then
|
||||
val1=`expr "X$val" : 'X\([^,]*\),'`
|
||||
val=`expr "X$val" : 'X[^,]*,\(.*\)'`
|
||||
else
|
||||
val1="$val"
|
||||
val=""
|
||||
fi
|
||||
# append to query
|
||||
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
|
||||
or=`expr ${or} + 1`
|
||||
done
|
||||
chart=`expr ${chart} + 1`
|
||||
done
|
||||
|
||||
echo "${query}"
|
||||
@@ -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@mathweb.org>.
|
||||
# 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 "summary","defaultREMOVEME"
|
||||
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,305 +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," \
|
||||
"delta_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]),
|
||||
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,107 +0,0 @@
|
||||
#!/usr/bin/perl -w
|
||||
#
|
||||
# sendbugmail.pl
|
||||
#
|
||||
# Nick Barnes, Ravenbrook Limited, 2004-04-01.
|
||||
#
|
||||
# $Id: sendbugmail.pl,v 1.3 2005-02-24 23:42:48 mkanat%kerio.com 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;
|
||||
use Bugzilla::User;
|
||||
|
||||
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(!login_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,68 +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;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
SendSQL("SELECT bug_id FROM bugs
|
||||
WHERE lastdiffed IS NULL
|
||||
OR lastdiffed < delta_ts AND delta_ts < NOW() - "
|
||||
. $dbh->sql_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);
|
||||
if ($ARGV[0] && $ARGV[0] eq "--report") {
|
||||
print "Mail sent to:\n";
|
||||
foreach (sort @{$outputref->{sent}}) {
|
||||
print $_ . "\n";
|
||||
}
|
||||
|
||||
print "Excluded:\n";
|
||||
foreach (sort @{$outputref->{excluded}}) {
|
||||
print $_ . "\n";
|
||||
}
|
||||
}
|
||||
else {
|
||||
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,291 +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 $dbh = Bugzilla->dbh;
|
||||
|
||||
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 " . $dbh->sql_istrcmp('login_name',
|
||||
$dbh->quote($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 " .
|
||||
$dbh->sql_istrcmp('login_name', $dbh->quote($key)));
|
||||
} else {
|
||||
SendSQL("UPDATE profiles SET realname = '" . @$value{'realname'} .
|
||||
"' WHERE " . $dbh->sql_istrcmp('login_name', $dbh->quote($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,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>
|
||||
# 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";
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::User;
|
||||
|
||||
# Shut up misguided -w warnings about "used only once":
|
||||
use vars qw($template $vars);
|
||||
|
||||
# Just in case someone already has an account, let them get the correct footer
|
||||
# on an error message. The user is logged out just after the account is
|
||||
# actually created.
|
||||
Bugzilla->login(LOGIN_OPTIONAL);
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
print $cgi->header();
|
||||
|
||||
# If we're using LDAP for login, then we can't create a new account here.
|
||||
unless (Bugzilla::Auth->can_edit('new')) {
|
||||
ThrowUserError("auth_cant_create_account");
|
||||
}
|
||||
|
||||
my $createexp = Param('createemailregexp');
|
||||
unless ($createexp) {
|
||||
ThrowUserError("account_creation_disabled");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
$dbh->bz_lock_tables('profiles WRITE', 'email_setting WRITE', 'tokens READ');
|
||||
|
||||
if (!is_available_username($login)) {
|
||||
# Account already exists
|
||||
$dbh->bz_unlock_tables();
|
||||
$template->process("account/exists.html.tmpl", $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($login !~ /$createexp/) {
|
||||
ThrowUserError("account_creation_disabled");
|
||||
}
|
||||
|
||||
# Create account
|
||||
my $password = insert_new_user($login, $realname);
|
||||
|
||||
$dbh->bz_unlock_tables();
|
||||
|
||||
# Clear out the login cookies in case the user is currently logged in.
|
||||
Bugzilla->logout();
|
||||
|
||||
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,112 +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);
|
||||
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];
|
||||
$product_id = get_product_id($product);
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# 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,63 +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;
|
||||
use Bugzilla::User;
|
||||
|
||||
require "CGI.pl";
|
||||
|
||||
# Use the global template variables.
|
||||
use vars qw($vars $template);
|
||||
|
||||
Bugzilla->login();
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
SendSQL("SELECT keyworddefs.name, keyworddefs.description,
|
||||
COUNT(keywords.bug_id)
|
||||
FROM keyworddefs LEFT JOIN keywords
|
||||
ON keyworddefs.id = keywords.keywordid " .
|
||||
$dbh->sql_group_by('keyworddefs.id',
|
||||
'keyworddefs.name, keyworddefs.description') . "
|
||||
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: 72 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,203 +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
|
||||
* Remove the BZ-DEVEL comments
|
||||
- COMPILE DOCS AND CHECKIN -
|
||||
Also, tag and tarball before completing
|
||||
* bz-ver to devel version
|
||||
|
||||
For a devel release, simple bump bz-ver and bz-date
|
||||
-->
|
||||
|
||||
<!ENTITY bz-ver "2.20">
|
||||
<!ENTITY bz-nextver "2.20.1">
|
||||
<!ENTITY bz-date "2005-09-30">
|
||||
<!ENTITY current-year "2005">
|
||||
|
||||
<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-tip/">
|
||||
<!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/">
|
||||
|
||||
<!-- For minimum versions -->
|
||||
<!ENTITY min-mysql-ver "3.23.41">
|
||||
<!ENTITY min-pg-ver "7.3.x">
|
||||
<!ENTITY min-perl-ver "5.6.1">
|
||||
<!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.84">
|
||||
<!ENTITY min-data-dumper-ver "any">
|
||||
<!ENTITY min-dbd-mysql-ver "2.9003">
|
||||
<!ENTITY min-dbd-pg-ver "1.31">
|
||||
<!ENTITY min-dbi-ver "1.38">
|
||||
<!ENTITY min-date-format-ver "2.21">
|
||||
<!ENTITY min-cgi-ver "2.93">
|
||||
<!ENTITY min-mail-mailer-ver "1.65">
|
||||
<!ENTITY min-storable-ver "any">
|
||||
<!-- 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;
|
||||
<!-- BZ-DEVEL -->Development <!-- /BZ-DEVEL -->
|
||||
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,235 +0,0 @@
|
||||
<!-- <!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
|
||||
<!ENTITY conventions SYSTEM "conventions.xml"> ] > -->
|
||||
<!-- $Id: about.xml,v 1.19 2005-01-14 12:08:47 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.
|
||||
<!-- BZ-DEVEL --> This version of the guide, like its associated Bugzilla version, is a
|
||||
development version.<!-- /BZ-DEVEL -->
|
||||
</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: -->
|
||||
|
||||