Compare commits

..

23 Commits

Author SHA1 Message Date
Alex Ionescu
e341e1728e Actual merge, sorry for the false alert. Merges with 13937.
svn path=/branches/alex_devel_branch/; revision=13942
2005-03-11 23:56:02 +00:00
Alex Ionescu
e8265f6b9f Final merge with 13934
svn path=/branches/alex_devel_branch/; revision=13939
2005-03-11 23:09:59 +00:00
Thomas Bluemel
c3564dbbab 1. fixed querying tokens
2. implemented calling vectored exception handlers

svn path=/branches/alex_devel_branch/; revision=13897
2005-03-09 00:04:13 +00:00
Alex Ionescu
7461a505f8 Merge to 13895. Remove ACPI static compiler flag and used dynamic variable with detection done through Freeloader (still some work left). Add more profile code and begin tiny work on Io functions.
svn path=/branches/alex_devel_branch/; revision=13896
2005-03-08 23:33:28 +00:00
Alex Ionescu
d8e8dcbca0 Merge with 13846. Fixes QEMU+KDBG problem.
svn path=/branches/alex_devel_branch/; revision=13847
2005-03-06 05:32:54 +00:00
Alex Ionescu
77f9f2a33d Merge with 13841:13843. Blight's rewrite adds a bug with ENTER in qemu, waiting on his fix.
svn path=/branches/alex_devel_branch/; revision=13844
2005-03-06 00:50:48 +00:00
Alex Ionescu
55f889579b Merge with blight's rewrite
svn path=/branches/alex_devel_branch/; revision=13842
2005-03-05 23:58:25 +00:00
Alex Ionescu
4d17ba7371 - Fix bootlog/debug to file problem -- Steven. 3GB now works (Thanks to Filip) but a bug remains in ntoskrnl.
- Basic beginning of Vector Exception Handling implementation -- Thomas
    - Merge with 13793:13838

svn path=/branches/alex_devel_branch/; revision=13839
2005-03-05 23:19:42 +00:00
Alex Ionescu
4a19968720 - FreeLdr Part II (ntoskrnl is now relocated, removes 3GB compiler flag). Note that there is a bug in LD which Filip and I are examining, so do not try this yet.
- Fix Registry ObRef/ObDeref bug -- Hartmut
    - Fix SID Capture Bug -- Thomas
    - Use KPRCB pointer properly (results in more portable and much faster code)

svn path=/branches/alex_devel_branch/; revision=13832
2005-03-05 20:44:57 +00:00
Alex Ionescu
5a6d0e7770 Add SEH to more functions, clean up some things, initialize profiling stuff (not yet enabled), and fix Registry Object re/defrecenging bug (Jim -- please retest). Thanks to Thomas for Security fixes and Iocompletion SEH + CM fix.
svn path=/branches/alex_devel_branch/; revision=13821
2005-03-04 20:56:32 +00:00
Alex Ionescu
53fa90faf0 Fix queue item not being cleaned. Thank you Jim
svn path=/branches/alex_devel_branch/; revision=13813
2005-03-04 04:15:46 +00:00
Alex Ionescu
96541a41d0 Fix queue item not being cleaned. Thank you Jim
svn path=/branches/alex_devel_branch/; revision=13812
2005-03-04 04:10:03 +00:00
Alex Ionescu
4c05caf407 Remove kdbg profiling so that NT Native Kernel profiling can be used with it (to come before the merge), more cleanup and movning stuff around, fix building with kdbg (a bug is left with debug flag though), implement KeRemoveSystemDescriptorTable, merge with latest HEAD
svn path=/branches/alex_devel_branch/; revision=13809
2005-03-04 00:18:25 +00:00
Alex Ionescu
9f8029eb42 Move Win32K callbacks to Win32K where they belong. Registrationis done with Ps function just like on XP+. Also allows non-win32k stuff to manage their own desktops and Window stations
svn path=/branches/alex_devel_branch/; revision=13806
2005-03-03 06:18:18 +00:00
Alex Ionescu
1a8d3f0b5c Fix bug with registry loading on bootcd (Thanks Steven). Properly implement Profile Objects with Ex/Ke architecture (structures are a guess, and code is based on dwelch and some simple testing/common sense, but not ready for primetime (NT doesnt even use theirs). Optimize Device Queues a bit and use the inserted field properly.
svn path=/branches/alex_devel_branch/; revision=13805
2005-03-03 03:57:59 +00:00
Alex Ionescu
31bc805285 Make this header shared internally.
svn path=/branches/alex_devel_branch/; revision=13799
2005-03-02 21:54:58 +00:00
Alex Ionescu
9472476bff Make ASSERTs bugcheck properly. Thanks Steven
svn path=/branches/alex_devel_branch/; revision=13798
2005-03-02 21:54:28 +00:00
Alex Ionescu
3094c6b3d4 Random fixes
svn path=/branches/alex_devel_branch/; revision=13794
2005-03-02 03:09:37 +00:00
Alex Ionescu
59ddacc24b Fix bugcheck code and make debugging easier for unhandled exceptions/spinlocks. fixg a race condition with tab+b, fix irql to be high_level, fix calling unsafe function by caching bugcode data, fix support for smp by using IPI, fix not-breakpointing when no debugger is there, implement KeBugCheck callbacks with Reason, fix callbacks not being called, fix proper breakpoint during bugcheck, fix errenous assert, merge with 13774.
svn path=/branches/alex_devel_branch/; revision=13793
2005-03-02 02:55:16 +00:00
Alex Ionescu
6f0e1ef75b My kernel fixes, new apis, reformatting, commenting, documenting, reorganizing, bug fixing, optimizing patch. More detailed changelog at www.relsoft.net/changelog. Passes all regression tests and installs/runs Abiword. Will be updated along the week and merged later.
svn path=/branches/alex_devel_branch/; revision=13786
2005-02-28 22:03:10 +00:00
Alex Ionescu
c413e47865 testing...
svn path=/branches/alex_devel_branch/; revision=13778
2005-02-28 18:16:48 +00:00
Alex Ionescu
bc694ae803 Finally a bootable version
svn path=/branches/alex_devel_branch/; revision=13774
2005-02-28 04:43:30 +00:00
Alex Ionescu
0880766ff5 People keep telling me to use a branch...here goes nothing
svn path=/branches/alex_devel_branch/; revision=13692
2005-02-20 18:46:57 +00:00
12833 changed files with 2701698 additions and 1206870 deletions

View File

@@ -1,24 +0,0 @@
$Id: README.txt 15922 2006-03-05 17:42:12Z frik85 $
===============
ReactOS Website
===============
If you run into problems or have suggestions for making the ReactOS Website better, please
visit the address below. Mailing lists are available for a variety of topics,
bugs should be submitted to bugzilla and general chat takes place in the forums,
or #reactos-web on freenode
More Information about the ReactOS Website:
http://www.reactos.org/?page=about_homepage
Previous versions of the ReactOS Website:
http://web.archive.org/web/*/http://www.reactos.com
ReactOS Website Team
http://www.reactos.org

View File

@@ -1,648 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
# Erik Stambaugh <erik@dasbistro.com>
# A. Karl Kornel <karl@kornel.name>
# Marc Schumann <wurblzap@gmail.com>
package Bugzilla;
use strict;
use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Auth;
use Bugzilla::Auth::Persist::Cookie;
use Bugzilla::CGI;
use Bugzilla::DB;
use Bugzilla::Install::Localconfig qw(read_localconfig);
use Bugzilla::Template;
use Bugzilla::User;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Field;
use File::Basename;
use Safe;
# This creates the request cache for non-mod_perl installations.
our $_request_cache = {};
#####################################################################
# Constants
#####################################################################
# Scripts that are not stopped by shutdownhtml being in effect.
use constant SHUTDOWNHTML_EXEMPT => [
'editparams.cgi',
'checksetup.pl',
'recode.pl',
];
# Non-cgi scripts that should silently exit.
use constant SHUTDOWNHTML_EXIT_SILENTLY => [
'whine.pl'
];
#####################################################################
# Global Code
#####################################################################
# The following subroutine is for debugging purposes only.
# Uncommenting this sub and the $::SIG{__DIE__} trap underneath it will
# cause any fatal errors to result in a call stack trace to help track
# down weird errors.
#
#sub die_with_dignity {
# use Carp ();
# my ($err_msg) = @_;
# print $err_msg;
# Carp::confess($err_msg);
#}
#$::SIG{__DIE__} = \&Bugzilla::die_with_dignity;
sub init_page {
# Some environment variables are not taint safe
delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Some modules throw undefined errors (notably File::Spec::Win32) if
# PATH is undefined.
$ENV{'PATH'} = '';
# IIS prints out warnings to the webpage, so ignore them, or log them
# to a file if the file exists.
if ($ENV{SERVER_SOFTWARE} && $ENV{SERVER_SOFTWARE} =~ /microsoft-iis/i) {
$SIG{__WARN__} = sub {
my ($msg) = @_;
my $datadir = bz_locations()->{'datadir'};
if (-w "$datadir/errorlog") {
my $warning_log = new IO::File(">>$datadir/errorlog");
print $warning_log $msg;
$warning_log->close();
}
};
}
# If Bugzilla is shut down, do not allow anything to run, just display a
# message to the user about the downtime and log out. Scripts listed in
# SHUTDOWNHTML_EXEMPT are exempt from this message.
#
# Because this is code which is run live from perl "use" commands of other
# scripts, we're skipping this part if we get here during a perl syntax
# check -- runtests.pl compiles scripts without running them, so we
# need to make sure that this check doesn't apply to 'perl -c' calls.
#
# This code must go here. It cannot go anywhere in Bugzilla::CGI, because
# it uses Template, and that causes various dependency loops.
if (!$^C && Bugzilla->params->{"shutdownhtml"}
&& lsearch(SHUTDOWNHTML_EXEMPT, basename($0)) == -1)
{
# Allow non-cgi scripts to exit silently (without displaying any
# message), if desired. At this point, no DBI call has been made
# yet, and no error will be returned if the DB is inaccessible.
if (lsearch(SHUTDOWNHTML_EXIT_SILENTLY, basename($0)) > -1
&& !i_am_cgi())
{
exit;
}
# For security reasons, log out users when Bugzilla is down.
# Bugzilla->login() is required to catch the logincookie, if any.
my $user = Bugzilla->login(LOGIN_OPTIONAL);
my $userid = $user->id;
Bugzilla->logout();
my $template = Bugzilla->template;
my $vars = {};
$vars->{'message'} = 'shutdown';
$vars->{'userid'} = $userid;
# Generate and return a message about the downtime, appropriately
# for if we're a command-line script or a CGI script.
my $extension;
if (i_am_cgi() && (!Bugzilla->cgi->param('ctype')
|| Bugzilla->cgi->param('ctype') eq 'html')) {
$extension = 'html';
}
else {
$extension = 'txt';
}
print Bugzilla->cgi->header() if i_am_cgi();
my $t_output;
$template->process("global/message.$extension.tmpl", $vars, \$t_output)
|| ThrowTemplateError($template->error);
print $t_output . "\n";
exit;
}
}
init_page() if !$ENV{MOD_PERL};
#####################################################################
# Subroutines and Methods
#####################################################################
sub template {
my $class = shift;
request_cache()->{language} = "";
request_cache()->{template} ||= Bugzilla::Template->create();
return request_cache()->{template};
}
sub template_inner {
my ($class, $lang) = @_;
$lang = defined($lang) ? $lang : (request_cache()->{language} || "");
request_cache()->{language} = $lang;
request_cache()->{"template_inner_$lang"} ||= Bugzilla::Template->create();
return request_cache()->{"template_inner_$lang"};
}
sub cgi {
my $class = shift;
request_cache()->{cgi} ||= new Bugzilla::CGI();
return request_cache()->{cgi};
}
sub localconfig {
request_cache()->{localconfig} ||= read_localconfig();
return request_cache()->{localconfig};
}
sub params {
my $class = shift;
request_cache()->{params} ||= Bugzilla::Config::read_param_file();
return request_cache()->{params};
}
sub user {
my $class = shift;
request_cache()->{user} ||= new Bugzilla::User;
return request_cache()->{user};
}
sub set_user {
my ($class, $user) = @_;
$class->request_cache->{user} = $user;
}
sub sudoer {
my $class = shift;
return request_cache()->{sudoer};
}
sub sudo_request {
my $class = shift;
my $new_user = shift;
my $new_sudoer = shift;
request_cache()->{user} = $new_user;
request_cache()->{sudoer} = $new_sudoer;
# NOTE: If you want to log the start of an sudo session, do it here.
return;
}
sub login {
my ($class, $type) = @_;
return Bugzilla->user if Bugzilla->user->id;
my $authorizer = new Bugzilla::Auth();
$type = LOGIN_REQUIRED if Bugzilla->cgi->param('GoAheadAndLogIn');
if (!defined $type || $type == LOGIN_NORMAL) {
$type = Bugzilla->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
}
my $authenticated_user = $authorizer->login($type);
# At this point, we now know if a real person is logged in.
# We must now check to see if an sudo session is in progress.
# For a session to be in progress, the following must be true:
# 1: There must be a logged in user
# 2: That user must be in the 'bz_sudoer' group
# 3: There must be a valid value in the 'sudo' cookie
# 4: A Bugzilla::User object must exist for the given cookie value
# 5: That user must NOT be in the 'bz_sudo_protect' group
my $sudo_cookie = $class->cgi->cookie('sudo');
detaint_natural($sudo_cookie) if defined($sudo_cookie);
my $sudo_target;
$sudo_target = new Bugzilla::User($sudo_cookie) if defined($sudo_cookie);
if (defined($authenticated_user) &&
$authenticated_user->in_group('bz_sudoers') &&
defined($sudo_cookie) &&
defined($sudo_target) &&
!($sudo_target->in_group('bz_sudo_protect'))
)
{
Bugzilla->set_user($sudo_target);
request_cache()->{sudoer} = $authenticated_user;
# And make sure that both users have the same Auth object,
# since we never call Auth::login for the sudo target.
$sudo_target->set_authorizer($authenticated_user->authorizer);
# NOTE: If you want to do any special logging, do it here.
}
else {
Bugzilla->set_user($authenticated_user);
}
return Bugzilla->user;
}
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::Persist::Cookie->logout({type => $option});
Bugzilla->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
}
sub logout_user {
my ($class, $user) = @_;
# When we're logging out another user we leave cookies alone, and
# therefore avoid calling Bugzilla->logout() directly.
Bugzilla::Auth::Persist::Cookie->logout({user => $user});
}
# just a compatibility front-end to logout_user that gets a user by id
sub logout_user_by_id {
my ($class, $id) = @_;
my $user = new Bugzilla::User($id);
$class->logout_user($user);
}
# hack that invalidates credentials for a single request
sub logout_request {
delete request_cache()->{user};
delete request_cache()->{sudoer};
# We can't delete from $cgi->cookie, so logincookie data will remain
# there. Don't rely on it: use Bugzilla->user->login instead!
}
sub dbh {
my $class = shift;
# If we're not connected, then we must want the main db
request_cache()->{dbh} ||= request_cache()->{dbh_main}
= Bugzilla::DB::connect_main();
return request_cache()->{dbh};
}
sub error_mode {
my $class = shift;
my $newval = shift;
if (defined $newval) {
request_cache()->{error_mode} = $newval;
}
return request_cache()->{error_mode}
|| Bugzilla::Constants::ERROR_MODE_WEBPAGE;
}
sub usage_mode {
my $class = shift;
my $newval = shift;
if (defined $newval) {
if ($newval == USAGE_MODE_BROWSER) {
$class->error_mode(ERROR_MODE_WEBPAGE);
}
elsif ($newval == USAGE_MODE_CMDLINE) {
$class->error_mode(ERROR_MODE_DIE);
}
elsif ($newval == USAGE_MODE_WEBSERVICE) {
$class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
}
elsif ($newval == USAGE_MODE_EMAIL) {
$class->error_mode(ERROR_MODE_DIE);
}
else {
ThrowCodeError('usage_mode_invalid',
{'invalid_usage_mode', $newval});
}
request_cache()->{usage_mode} = $newval;
}
return request_cache()->{usage_mode}
|| Bugzilla::Constants::USAGE_MODE_BROWSER;
}
sub installation_mode {
my ($class, $newval) = @_;
($class->request_cache->{installation_mode} = $newval) if defined $newval;
return $class->request_cache->{installation_mode}
|| INSTALLATION_MODE_INTERACTIVE;
}
sub installation_answers {
my ($class, $filename) = @_;
if ($filename) {
my $s = new Safe;
$s->rdo($filename);
die "Error reading $filename: $!" if $!;
die "Error evaluating $filename: $@" if $@;
# Now read the param back out from the sandbox
$class->request_cache->{installation_answers} = $s->varglob('answer');
}
return $class->request_cache->{installation_answers} || {};
}
sub switch_to_shadow_db {
my $class = shift;
if (!request_cache()->{dbh_shadow}) {
if (Bugzilla->params->{'shadowdb'}) {
request_cache()->{dbh_shadow} = Bugzilla::DB::connect_shadow();
} else {
request_cache()->{dbh_shadow} = request_cache()->{dbh_main};
}
}
request_cache()->{dbh} = request_cache()->{dbh_shadow};
# we have to return $class->dbh instead of {dbh} as
# {dbh_shadow} may be undefined if no shadow DB is used
# and no connection to the main DB has been established yet.
return $class->dbh;
}
sub switch_to_main_db {
my $class = shift;
request_cache()->{dbh} = request_cache()->{dbh_main};
# We have to return $class->dbh instead of {dbh} as
# {dbh_main} may be undefined if no connection to the main DB
# has been established yet.
return $class->dbh;
}
sub get_fields {
my $class = shift;
my $criteria = shift;
# This function may be called during installation, and Field::match
# may fail at that time. so we want to return an empty list in that
# case.
my $fields = eval { Bugzilla::Field->match($criteria) } || [];
return @$fields;
}
sub custom_field_names {
# Get a list of custom fields and convert it into a list of their names.
return map($_->{name},
@{Bugzilla::Field->match({ custom=>1, obsolete=>0 })});
}
sub hook_args {
my ($class, $args) = @_;
$class->request_cache->{hook_args} = $args if $args;
return $class->request_cache->{hook_args};
}
sub request_cache {
if ($ENV{MOD_PERL}) {
require Apache2::RequestUtil;
return Apache2::RequestUtil->request->pnotes();
}
return $_request_cache;
}
# Private methods
# Per process cleanup
sub _cleanup {
# When we support transactions, need to ->rollback here
my $main = request_cache()->{dbh_main};
my $shadow = request_cache()->{dbh_shadow};
$main->disconnect if $main;
$shadow->disconnect if $shadow && Bugzilla->params->{"shadowdb"};
undef $_request_cache;
}
sub END {
# Bugzilla.pm cannot compile in mod_perl.pl if this runs.
_cleanup() unless $ENV{MOD_PERL};
}
1;
__END__
=head1 NAME
Bugzilla - Semi-persistent collection of various objects used by scripts
and modules
=head1 SYNOPSIS
use Bugzilla;
sub someModulesSub {
Bugzilla->dbh->prepare(...);
Bugzilla->template->process(...);
}
=head1 DESCRIPTION
Several Bugzilla 'things' are used by a variety of modules and scripts. This
includes database handles, template objects, and so on.
This module is a singleton intended as a central place to store these objects.
This approach has several advantages:
=over 4
=item *
They're not global variables, so we don't have issues with them staying around
with mod_perl
=item *
Everything is in one central place, so it's easy to access, modify, and maintain
=item *
Code in modules can get access to these objects without having to have them
all passed from the caller, and the caller's caller, and....
=item *
We can reuse objects across requests using mod_perl where appropriate (eg
templates), whilst destroying those which are only valid for a single request
(such as the current user)
=back
Note that items accessible via this object are demand-loaded when requested.
For something to be added to this object, it should either be able to benefit
from persistence when run under mod_perl (such as the a C<template> object),
or should be something which is globally required by a large ammount of code
(such as the current C<user> object).
=head1 METHODS
Note that all C<Bugzilla> functionality is method based; use C<Bugzilla-E<gt>dbh>
rather than C<Bugzilla::dbh>. Nothing cares about this now, but don't rely on
that.
=over 4
=item C<template>
The current C<Template> object, to be used for output
=item C<template_inner>
If you ever need a L<Bugzilla::Template> object while you're already
processing a template, use this. Also use it if you want to specify
the language to use. If no argument is passed, it uses the last
language set. If the argument is "" (empty string), the language is
reset to the current one (the one used by Bugzilla->template).
=item C<cgi>
The current C<cgi> object. Note that modules should B<not> be using this in
general. Not all Bugzilla actions are cgi requests. Its useful as a convenience
method for those scripts/templates which are only use via CGI, though.
=item C<user>
C<undef> if there is no currently logged in user or if the login code has not
yet been run. If an sudo session is in progress, the C<Bugzilla::User>
corresponding to the person who is being impersonated. If no session is in
progress, the current C<Bugzilla::User>.
=item C<set_user>
Allows you to directly set what L</user> will return. You can use this
if you want to bypass L</login> for some reason and directly "log in"
a specific L<Bugzilla::User>. Be careful with it, though!
=item C<sudoer>
C<undef> if there is no currently logged in user, the currently logged in user
is not in the I<sudoer> group, or there is no session in progress. If an sudo
session is in progress, returns the C<Bugzilla::User> object corresponding to
the person who logged in and initiated the session. If no session is in
progress, returns the C<Bugzilla::User> object corresponding to the currently
logged in user.
=item C<sudo_request>
This begins an sudo session for the current request. It is meant to be
used when a session has just started. For normal use, sudo access should
normally be set at login time.
=item C<login>
Logs in a user, returning a C<Bugzilla::User> object, or C<undef> if there is
no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth>, and
L<Bugzilla::User|Bugzilla::User>.
=item C<logout($option)>
Logs out the current user, which involves invalidating user sessions and
cookies. Three options are available from
L<Bugzilla::Constants|Bugzilla::Constants>: LOGOUT_CURRENT (the
default), LOGOUT_ALL or LOGOUT_KEEP_CURRENT.
=item C<logout_user($user)>
Logs out the specified user (invalidating all his sessions), taking a
Bugzilla::User instance.
=item C<logout_by_id($id)>
Logs out the user with the id specified. This is a compatibility
function to be used in callsites where there is only a userid and no
Bugzilla::User instance.
=item C<logout_request>
Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has the
effect of logging out a user for the current request only; cookies and
database sessions are left intact.
=item C<error_mode>
Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
or C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT)> to
change this flag's default of C<Bugzilla::Constants::ERROR_MODE_WEBPAGE> and to
indicate that errors should be passed to error mode specific error handlers
rather than being sent to a browser and finished with an exit().
This is useful, for example, to keep C<eval> blocks from producing wild HTML
on errors, making it easier for you to catch them.
(Remember to reset the error mode to its previous value afterwards, though.)
C<Bugzilla->error_mode> will return the current state of this flag.
Note that C<Bugzilla->error_mode> is being called by C<Bugzilla->usage_mode> on
usage mode changes.
=item C<usage_mode>
Call either C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE)> near the
beginning of your script to change this flag's default of
C<Bugzilla::Constants::USAGE_MODE_BROWSER> and to indicate that Bugzilla is
being called in a non-interactive manner.
This influences error handling because on usage mode changes, C<usage_mode>
calls C<Bugzilla->error_mode> to set an error mode which makes sense for the
usage mode.
C<Bugzilla->usage_mode> will return the current state of this flag.
=item C<installation_mode>
Determines whether or not installation should be silent. See
L<Bugzilla::Constants> for the C<INSTALLATION_MODE> constants.
=item C<installation_answers>
Returns a hashref representing any "answers" file passed to F<checksetup.pl>,
used to automatically answer or skip prompts.
=item C<dbh>
The current database handle. See L<DBI>.
=item C<switch_to_shadow_db>
Switch from using the main database to using the shadow database.
=item C<switch_to_main_db>
Change the database object to refer to the main database.
=item C<params>
The current Parameters of Bugzilla, as a hashref. If C<data/params>
does not exist, then we return an empty hashref. If C<data/params>
is unreadable or is not valid perl, we C<die>.
=item C<hook_args>
If you are running inside a code hook (see L<Bugzilla::Hook>) this
is how you get the arguments passed to the hook.
=back

View File

@@ -1,890 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Myk Melez <myk@mozilla.org>
# Marc Schumann <wurblzap@gmail.com>
# Frédéric Buclin <LpSolit@gmail.com>
use strict;
package Bugzilla::Attachment;
=head1 NAME
Bugzilla::Attachment - a file related to a bug that a user has uploaded
to the Bugzilla server
=head1 SYNOPSIS
use Bugzilla::Attachment;
# Get the attachment with the given ID.
my $attachment = Bugzilla::Attachment->get($attach_id);
# Get the attachments with the given IDs.
my $attachments = Bugzilla::Attachment->get_list($attach_ids);
=head1 DESCRIPTION
This module defines attachment objects, which represent files related to bugs
that users upload to the Bugzilla server.
=cut
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Flag;
use Bugzilla::User;
use Bugzilla::Util;
use Bugzilla::Field;
sub get {
my $invocant = shift;
my $id = shift;
my $attachments = _retrieve([$id]);
my $self = $attachments->[0];
bless($self, ref($invocant) || $invocant) if $self;
return $self;
}
sub get_list {
my $invocant = shift;
my $ids = shift;
my $attachments = _retrieve($ids);
foreach my $attachment (@$attachments) {
bless($attachment, ref($invocant) || $invocant);
}
return $attachments;
}
sub _retrieve {
my ($ids) = @_;
return [] if scalar(@$ids) == 0;
my @columns = (
'attachments.attach_id AS id',
'attachments.bug_id AS bug_id',
'attachments.description AS description',
'attachments.mimetype AS contenttype',
'attachments.submitter_id AS _attacher_id',
Bugzilla->dbh->sql_date_format('attachments.creation_ts',
'%Y.%m.%d %H:%i') . " AS attached",
'attachments.filename AS filename',
'attachments.ispatch AS ispatch',
'attachments.isurl AS isurl',
'attachments.isobsolete AS isobsolete',
'attachments.isprivate AS isprivate'
);
my $columns = join(", ", @columns);
my $records = Bugzilla->dbh->selectall_arrayref("SELECT $columns
FROM attachments
WHERE attach_id IN (" .
join(",", @$ids) . ")
ORDER BY attach_id",
{ Slice => {} });
return $records;
}
=pod
=head2 Instance Properties
=over
=item C<id>
the unique identifier for the attachment
=back
=cut
sub id {
my $self = shift;
return $self->{id};
}
=over
=item C<bug_id>
the ID of the bug to which the attachment is attached
=back
=cut
# XXX Once Bug.pm slims down sufficiently this should become a reference
# to a bug object.
sub bug_id {
my $self = shift;
return $self->{bug_id};
}
=over
=item C<description>
user-provided text describing the attachment
=back
=cut
sub description {
my $self = shift;
return $self->{description};
}
=over
=item C<contenttype>
the attachment's MIME media type
=back
=cut
sub contenttype {
my $self = shift;
return $self->{contenttype};
}
=over
=item C<attacher>
the user who attached the attachment
=back
=cut
sub attacher {
my $self = shift;
return $self->{attacher} if exists $self->{attacher};
$self->{attacher} = new Bugzilla::User($self->{_attacher_id});
return $self->{attacher};
}
=over
=item C<attached>
the date and time on which the attacher attached the attachment
=back
=cut
sub attached {
my $self = shift;
return $self->{attached};
}
=over
=item C<filename>
the name of the file the attacher attached
=back
=cut
sub filename {
my $self = shift;
return $self->{filename};
}
=over
=item C<ispatch>
whether or not the attachment is a patch
=back
=cut
sub ispatch {
my $self = shift;
return $self->{ispatch};
}
=over
=item C<isurl>
whether or not the attachment is a URL
=back
=cut
sub isurl {
my $self = shift;
return $self->{isurl};
}
=over
=item C<isobsolete>
whether or not the attachment is obsolete
=back
=cut
sub isobsolete {
my $self = shift;
return $self->{isobsolete};
}
=over
=item C<isprivate>
whether or not the attachment is private
=back
=cut
sub isprivate {
my $self = shift;
return $self->{isprivate};
}
=over
=item C<data>
the content of the attachment
=back
=cut
sub data {
my $self = shift;
return $self->{data} if exists $self->{data};
# First try to get the attachment data from the database.
($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata
FROM attach_data
WHERE id = ?",
undef,
$self->{id});
# If there's no attachment data in the database, the attachment is stored
# in a local file, so retrieve it from there.
if (length($self->{data}) == 0) {
if (open(AH, $self->_get_local_filename())) {
local $/;
binmode AH;
$self->{data} = <AH>;
close(AH);
}
}
return $self->{data};
}
=over
=item C<datasize>
the length (in characters) of the attachment content
=back
=cut
# datasize is a property of the data itself, and it's unclear whether we should
# expose it at all, since you can easily derive it from the data itself: in TT,
# attachment.data.size; in Perl, length($attachment->{data}). But perhaps
# it makes sense for performance reasons, since accessing the data forces it
# to get retrieved from the database/filesystem and loaded into memory,
# while datasize avoids loading the attachment into memory, calling SQL's
# LENGTH() function or stat()ing the file instead. I've left it in for now.
sub datasize {
my $self = shift;
return $self->{datasize} if exists $self->{datasize};
# If we have already retrieved the data, return its size.
return length($self->{data}) if exists $self->{data};
$self->{datasize} =
Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata)
FROM attach_data
WHERE id = ?",
undef, $self->{id}) || 0;
# If there's no attachment data in the database, either the attachment
# is stored in a local file, and so retrieve its size from the file,
# or the attachment has been deleted.
unless ($self->{datasize}) {
if (open(AH, $self->_get_local_filename())) {
binmode AH;
$self->{datasize} = (stat(AH))[7];
close(AH);
}
}
return $self->{datasize};
}
=over
=item C<flags>
flags that have been set on the attachment
=back
=cut
sub flags {
my $self = shift;
return $self->{flags} if exists $self->{flags};
$self->{flags} = Bugzilla::Flag::match({ 'attach_id' => $self->id });
return $self->{flags};
}
# Instance methods; no POD documentation here yet because the only ones so far
# are private.
sub _get_local_filename {
my $self = shift;
my $hash = ($self->id % 100) + 100;
$hash =~ s/.*(\d\d)$/group.$1/;
return bz_locations()->{'attachdir'} . "/$hash/attachment." . $self->id;
}
sub _validate_filename {
my ($throw_error) = @_;
my $cgi = Bugzilla->cgi;
defined $cgi->upload('data')
|| ($throw_error ? ThrowUserError("file_not_specified") : return 0);
my $filename = $cgi->upload('data');
# Remove path info (if any) from the file name. The browser should do this
# for us, but some are buggy. This may not work on Mac file names and could
# mess up file names with slashes in them, but them's the breaks. We only
# use this as a hint to users downloading attachments anyway, so it's not
# a big deal if it munges incorrectly occasionally.
$filename =~ s/^.*[\/\\]//;
# Truncate the filename to 100 characters, counting from the end of the
# string to make sure we keep the filename extension.
$filename = substr($filename, -100, 100);
return $filename;
}
sub _validate_data {
my ($throw_error, $hr_vars) = @_;
my $cgi = Bugzilla->cgi;
my $maxsize = $cgi->param('ispatch') ? Bugzilla->params->{'maxpatchsize'}
: Bugzilla->params->{'maxattachmentsize'};
$maxsize *= 1024; # Convert from K
my $fh;
# Skip uploading into a local variable if the user wants to upload huge
# attachments into local files.
if (!$cgi->param('bigfile')) {
$fh = $cgi->upload('data');
}
my $data;
# We could get away with reading only as much as required, except that then
# we wouldn't have a size to print to the error handler below.
if (!$cgi->param('bigfile')) {
# enable 'slurp' mode
local $/;
$data = <$fh>;
}
$data
|| ($cgi->param('bigfile'))
|| ($throw_error ? ThrowUserError("zero_length_file") : return 0);
# Windows screenshots are usually uncompressed BMP files which
# makes for a quick way to eat up disk space. Let's compress them.
# We do this before we check the size since the uncompressed version
# could easily be greater than maxattachmentsize.
if (Bugzilla->params->{'convert_uncompressed_images'}
&& $cgi->param('contenttype') eq 'image/bmp') {
require Image::Magick;
my $img = Image::Magick->new(magick=>'bmp');
$img->BlobToImage($data);
$img->set(magick=>'png');
my $imgdata = $img->ImageToBlob();
$data = $imgdata;
$cgi->param('contenttype', 'image/png');
$$hr_vars->{'convertedbmp'} = 1;
}
# Make sure the attachment does not exceed the maximum permitted size
my $len = $data ? length($data) : 0;
if ($maxsize && $len > $maxsize) {
my $vars = { filesize => sprintf("%.0f", $len/1024) };
if ($cgi->param('ispatch')) {
$throw_error ? ThrowUserError("patch_too_large", $vars) : return 0;
}
else {
$throw_error ? ThrowUserError("file_too_large", $vars) : return 0;
}
}
return $data || '';
}
=pod
=head2 Class Methods
=over
=item C<get_attachments_by_bug($bug_id)>
Description: retrieves and returns the attachments the currently logged in
user can view for the given bug.
Params: C<$bug_id> - integer - the ID of the bug for which
to retrieve and return attachments.
Returns: a reference to an array of attachment objects.
=cut
sub get_attachments_by_bug {
my ($class, $bug_id) = @_;
my $user = Bugzilla->user;
my $dbh = Bugzilla->dbh;
# By default, private attachments are not accessible, unless the user
# is in the insider group or submitted the attachment.
my $and_restriction = '';
my @values = ($bug_id);
unless ($user->is_insider) {
$and_restriction = 'AND (isprivate = 0 OR submitter_id = ?)';
push(@values, $user->id);
}
my $attach_ids = $dbh->selectcol_arrayref("SELECT attach_id FROM attachments
WHERE bug_id = ? $and_restriction",
undef, @values);
my $attachments = Bugzilla::Attachment->get_list($attach_ids);
return $attachments;
}
=pod
=item C<validate_is_patch()>
Description: validates the "patch" flag passed in by CGI.
Returns: 1 on success.
=cut
sub validate_is_patch {
my ($class, $throw_error) = @_;
my $cgi = Bugzilla->cgi;
# Set the ispatch flag to zero if it is undefined, since the UI uses
# an HTML checkbox to represent this flag, and unchecked HTML checkboxes
# do not get sent in HTML requests.
$cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
# Set the content type to text/plain if the attachment is a patch.
$cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
return 1;
}
=pod
=item C<validate_description()>
Description: validates the description passed in by CGI.
Returns: 1 on success.
=cut
sub validate_description {
my ($class, $throw_error) = @_;
my $cgi = Bugzilla->cgi;
$cgi->param('description')
|| ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
return 1;
}
=pod
=item C<validate_content_type()>
Description: validates the content type passed in by CGI.
Returns: 1 on success.
=cut
sub validate_content_type {
my ($class, $throw_error) = @_;
my $cgi = Bugzilla->cgi;
if (!defined $cgi->param('contenttypemethod')) {
$throw_error ? ThrowUserError("missing_content_type_method") : return 0;
}
elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
my $contenttype =
$cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
# The user asked us to auto-detect the content type, so use the type
# specified in the HTTP request headers.
if ( !$contenttype ) {
$throw_error ? ThrowUserError("missing_content_type") : return 0;
}
$cgi->param('contenttype', $contenttype);
}
elsif ($cgi->param('contenttypemethod') eq 'list') {
# The user selected a content type from the list, so use their
# selection.
$cgi->param('contenttype', $cgi->param('contenttypeselection'));
}
elsif ($cgi->param('contenttypemethod') eq 'manual') {
# The user entered a content type manually, so use their entry.
$cgi->param('contenttype', $cgi->param('contenttypeentry'));
}
else {
$throw_error ?
ThrowCodeError("illegal_content_type_method",
{ contenttypemethod => $cgi->param('contenttypemethod') }) :
return 0;
}
if ( $cgi->param('contenttype') !~
/^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
$throw_error ?
ThrowUserError("invalid_content_type",
{ contenttype => $cgi->param('contenttype') }) :
return 0;
}
return 1;
}
=pod
=item C<validate_can_edit($attachment, $product_id)>
Description: validates if the user is allowed to view and edit the attachment.
Only the submitter or someone with editbugs privs can edit it.
Only the submitter and users in the insider group can view
private attachments.
Params: $attachment - the attachment object being edited.
$product_id - the product ID the attachment belongs to.
Returns: 1 on success. Else an error is thrown.
=cut
sub validate_can_edit {
my ($attachment, $product_id) = @_;
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
# Bug 97729 - the submitter can edit their attachments.
return if ($attachment->attacher->id == $user->id);
# Only users in the insider group can view private attachments.
if ($attachment->isprivate && !$user->is_insider) {
ThrowUserError('illegal_attachment_edit', {attach_id => $attachment->id});
}
# Users with editbugs privs can edit all attachments.
return if $user->in_group('editbugs', $product_id);
# If we come here, then this attachment cannot be seen by the user.
ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
}
=item C<validate_obsolete($bug)>
Description: validates if attachments the user wants to mark as obsolete
really belong to the given bug and are not already obsolete.
Moreover, a user cannot mark an attachment as obsolete if
he cannot view it (due to restrictions on it).
Params: $bug - The bug object obsolete attachments should belong to.
Returns: 1 on success. Else an error is thrown.
=cut
sub validate_obsolete {
my ($class, $bug) = @_;
my $cgi = Bugzilla->cgi;
# Make sure the attachment id is valid and the user has permissions to view
# the bug to which it is attached. Make sure also that the user can view
# the attachment itself.
my @obsolete_attachments;
foreach my $attachid ($cgi->param('obsolete')) {
my $vars = {};
$vars->{'attach_id'} = $attachid;
detaint_natural($attachid)
|| ThrowCodeError('invalid_attach_id_to_obsolete', $vars);
my $attachment = Bugzilla::Attachment->get($attachid);
# Make sure the attachment exists in the database.
ThrowUserError('invalid_attach_id', $vars) unless $attachment;
# Check that the user can view and edit this attachment.
$attachment->validate_can_edit($bug->product_id);
$vars->{'description'} = $attachment->description;
if ($attachment->bug_id != $bug->bug_id) {
$vars->{'my_bug_id'} = $bug->bug_id;
$vars->{'attach_bug_id'} = $attachment->bug_id;
ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
}
if ($attachment->isobsolete) {
ThrowCodeError('attachment_already_obsolete', $vars);
}
push(@obsolete_attachments, $attachment);
}
return @obsolete_attachments;
}
=pod
=item C<insert_attachment_for_bug($throw_error, $bug, $user, $timestamp, $hr_vars)>
Description: inserts an attachment from CGI input for the given bug.
Params: C<$bug> - Bugzilla::Bug object - the bug for which to insert
the attachment.
C<$user> - Bugzilla::User object - the user we're inserting an
attachment for.
C<$timestamp> - scalar - timestamp of the insert as returned
by SELECT NOW().
C<$hr_vars> - hash reference - reference to a hash of template
variables.
Returns: the ID of the new attachment.
=back
=cut
sub insert_attachment_for_bug {
my ($class, $throw_error, $bug, $user, $timestamp, $hr_vars) = @_;
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
my $attachurl = $cgi->param('attachurl') || '';
my $data;
my $filename;
my $contenttype;
my $isurl;
$class->validate_is_patch($throw_error) || return 0;
$class->validate_description($throw_error) || return 0;
if (Bugzilla->params->{'allow_attach_url'}
&& ($attachurl =~ /^(http|https|ftp):\/\/\S+/)
&& !defined $cgi->upload('data'))
{
$filename = '';
$data = $attachurl;
$isurl = 1;
$contenttype = 'text/plain';
$cgi->param('ispatch', 0);
$cgi->delete('bigfile');
}
else {
$filename = _validate_filename($throw_error) || return 0;
# need to validate content type before data as
# we now check the content type for image/bmp in _validate_data()
unless ($cgi->param('ispatch')) {
$class->validate_content_type($throw_error) || return 0;
# Set the ispatch flag to 1 if we're set to autodetect
# and the content type is text/x-diff or text/x-patch
if ($cgi->param('contenttypemethod') eq 'autodetect'
&& $cgi->param('contenttype') =~ m{text/x-(?:diff|patch)})
{
$cgi->param('ispatch', 1);
$cgi->param('contenttype', 'text/plain');
}
}
$data = _validate_data($throw_error, $hr_vars);
# If the attachment is stored locally, $data eq ''.
# If an error is thrown, $data eq '0'.
($data ne '0') || return 0;
$contenttype = $cgi->param('contenttype');
# These are inserted using placeholders so no need to panic
trick_taint($filename);
trick_taint($contenttype);
$isurl = 0;
}
# Check attachments the user tries to mark as obsolete.
my @obsolete_attachments;
if ($cgi->param('obsolete')) {
@obsolete_attachments = $class->validate_obsolete($bug);
}
# The order of these function calls is important, as Flag::validate
# assumes User::match_field has ensured that the
# values in the requestee fields are legitimate user email addresses.
my $match_status = Bugzilla::User::match_field($cgi, {
'^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
}, MATCH_SKIP_CONFIRM);
$$hr_vars->{'match_field'} = 'requestee';
if ($match_status == USER_MATCH_FAILED) {
$$hr_vars->{'message'} = 'user_match_failed';
}
elsif ($match_status == USER_MATCH_MULTIPLE) {
$$hr_vars->{'message'} = 'user_match_multiple';
}
# Escape characters in strings that will be used in SQL statements.
my $description = $cgi->param('description');
trick_taint($description);
my $isprivate = $cgi->param('isprivate') ? 1 : 0;
# Insert the attachment into the database.
my $sth = $dbh->do(
"INSERT INTO attachments
(bug_id, creation_ts, filename, description,
mimetype, ispatch, isurl, isprivate, submitter_id)
VALUES (?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $filename,
$description, $contenttype, $cgi->param('ispatch'),
$isurl, $isprivate, $user->id));
# Retrieve the ID of the newly created attachment record.
my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
# We only use $data here in this INSERT with a placeholder,
# so it's safe.
$sth = $dbh->prepare("INSERT INTO attach_data
(id, thedata) VALUES ($attachid, ?)");
trick_taint($data);
$sth->bind_param(1, $data, $dbh->BLOB_TYPE);
$sth->execute();
# If the file is to be stored locally, stream the file from the webserver
# to the local file without reading it into a local variable.
if ($cgi->param('bigfile')) {
my $attachdir = bz_locations()->{'attachdir'};
my $fh = $cgi->upload('data');
my $hash = ($attachid % 100) + 100;
$hash =~ s/.*(\d\d)$/group.$1/;
mkdir "$attachdir/$hash", 0770;
chmod 0770, "$attachdir/$hash";
open(AH, ">$attachdir/$hash/attachment.$attachid");
binmode AH;
my $sizecount = 0;
my $limit = (Bugzilla->params->{"maxlocalattachment"} * 1048576);
while (<$fh>) {
print AH $_;
$sizecount += length($_);
if ($sizecount > $limit) {
close AH;
close $fh;
unlink "$attachdir/$hash/attachment.$attachid";
$throw_error ? ThrowUserError("local_file_too_large") : return 0;
}
}
close AH;
close $fh;
}
# Make existing attachments obsolete.
my $fieldid = get_field_id('attachments.isobsolete');
foreach my $obsolete_attachment (@obsolete_attachments) {
# If the obsolete attachment has request flags, cancel them.
# This call must be done before updating the 'attachments' table.
Bugzilla::Flag::CancelRequests($bug, $obsolete_attachment, $timestamp);
$dbh->do('UPDATE attachments SET isobsolete = 1 WHERE attach_id = ?',
undef, $obsolete_attachment->id);
$dbh->do('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)',
undef, ($bug->bug_id, $obsolete_attachment->id, $user->id,
$timestamp, $fieldid, 0, 1));
}
my $attachment = Bugzilla::Attachment->get($attachid);
# 1. Add flags, if any. To avoid dying if something goes wrong
# while processing flags, we will eval() flag validation.
# This requires errors to die().
# XXX: this can go away as soon as flag validation is able to
# fail without dying.
#
# 2. Flag::validate() should not detect any reference to existing flags
# when creating a new attachment. Setting the third param to -1 will
# force this function to check this point.
my $error_mode_cache = Bugzilla->error_mode;
Bugzilla->error_mode(ERROR_MODE_DIE);
eval {
Bugzilla::Flag::validate($cgi, $bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
Bugzilla::Flag::process($bug, $attachment, $timestamp, $cgi);
};
Bugzilla->error_mode($error_mode_cache);
if ($@) {
$$hr_vars->{'message'} = 'flag_creation_failed';
$$hr_vars->{'flag_creation_error'} = $@;
}
# Return the ID of the new attachment.
return $attachid;
}
1;

View File

@@ -1,273 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): John Keiser <john@johnkeiser.com>
# Frédéric Buclin <LpSolit@gmail.com>
use strict;
package Bugzilla::Attachment::PatchReader;
use Bugzilla::Error;
use Bugzilla::Attachment;
sub process_diff {
my ($attachment, $format, $context) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
my $lc = Bugzilla->localconfig;
my $vars = {};
my ($reader, $last_reader) = setup_patch_readers(undef, $context);
if ($format eq 'raw') {
require PatchReader::DiffPrinter::raw;
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
-expires => '+3M');
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
}
else {
my @other_patches = ();
if ($lc->{interdiffbin} && $lc->{diffpath}) {
# Get the list of attachments that the user can view in this bug.
my @attachments =
@{Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id)};
# Extract patches only.
@attachments = grep {$_->ispatch == 1} @attachments;
# We want them sorted from newer to older.
@attachments = sort { $b->id <=> $a->id } @attachments;
# Ignore the current patch, but select the one right before it
# chronologically.
my $select_next_patch = 0;
foreach my $attach (@attachments) {
if ($attach->id == $attachment->id) {
$select_next_patch = 1;
}
else {
push(@other_patches, { 'id' => $attach->id,
'desc' => $attach->description,
'selected' => $select_next_patch });
$select_next_patch = 0;
}
}
}
$vars->{'bugid'} = $attachment->bug_id;
$vars->{'attachid'} = $attachment->id;
$vars->{'description'} = $attachment->description;
$vars->{'other_patches'} = \@other_patches;
setup_template_patch_reader($last_reader, $format, $context, $vars);
# Actually print out the patch.
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
}
}
sub process_interdiff {
my ($old_attachment, $new_attachment, $format, $context) = @_;
my $cgi = Bugzilla->cgi;
my $lc = Bugzilla->localconfig;
my $vars = {};
# Get old patch data.
my ($old_filename, $old_file_list) = get_unified_diff($old_attachment);
# Get new patch data.
my ($new_filename, $new_file_list) = get_unified_diff($new_attachment);
my $warning = warn_if_interdiff_might_fail($old_file_list, $new_file_list);
# Send through interdiff, send output directly to template.
# Must hack path so that interdiff will work.
$ENV{'PATH'} = $lc->{diffpath};
open my $interdiff_fh, "$lc->{interdiffbin} $old_filename $new_filename|";
binmode $interdiff_fh;
my ($reader, $last_reader) = setup_patch_readers("", $context);
if ($format eq 'raw') {
require PatchReader::DiffPrinter::raw;
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
-expires => '+3M');
}
else {
$vars->{'warning'} = $warning if $warning;
$vars->{'bugid'} = $new_attachment->bug_id;
$vars->{'oldid'} = $old_attachment->id;
$vars->{'old_desc'} = $old_attachment->description;
$vars->{'newid'} = $new_attachment->id;
$vars->{'new_desc'} = $new_attachment->description;
setup_template_patch_reader($last_reader, $format, $context, $vars);
}
$reader->iterate_fh($interdiff_fh, 'interdiff #' . $old_attachment->id .
' #' . $new_attachment->id);
close $interdiff_fh;
$ENV{'PATH'} = '';
# Delete temporary files.
unlink($old_filename) or warn "Could not unlink $old_filename: $!";
unlink($new_filename) or warn "Could not unlink $new_filename: $!";
}
######################
# Internal routines
######################
sub get_unified_diff {
my $attachment = shift;
# Bring in the modules we need.
require PatchReader::Raw;
require PatchReader::FixPatchRoot;
require PatchReader::DiffPrinter::raw;
require PatchReader::PatchInfoGrabber;
require File::Temp;
$attachment->ispatch
|| ThrowCodeError('must_be_patch', { 'attach_id' => $attachment->id });
# Reads in the patch, converting to unified diff in a temp file.
my $reader = new PatchReader::Raw;
my $last_reader = $reader;
# Fixes patch root (makes canonical if possible).
if (Bugzilla->params->{'cvsroot'}) {
my $fix_patch_root =
new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'});
$last_reader->sends_data_to($fix_patch_root);
$last_reader = $fix_patch_root;
}
# Grabs the patch file info.
my $patch_info_grabber = new PatchReader::PatchInfoGrabber();
$last_reader->sends_data_to($patch_info_grabber);
$last_reader = $patch_info_grabber;
# Prints out to temporary file.
my ($fh, $filename) = File::Temp::tempfile();
my $raw_printer = new PatchReader::DiffPrinter::raw($fh);
$last_reader->sends_data_to($raw_printer);
$last_reader = $raw_printer;
# Iterate!
$reader->iterate_string($attachment->id, $attachment->data);
return ($filename, $patch_info_grabber->patch_info()->{files});
}
sub warn_if_interdiff_might_fail {
my ($old_file_list, $new_file_list) = @_;
# Verify that the list of files diffed is the same.
my @old_files = sort keys %{$old_file_list};
my @new_files = sort keys %{$new_file_list};
if (@old_files != @new_files
|| join(' ', @old_files) ne join(' ', @new_files))
{
return 'interdiff1';
}
# Verify that the revisions in the files are the same.
foreach my $file (keys %{$old_file_list}) {
if ($old_file_list->{$file}{old_revision} ne
$new_file_list->{$file}{old_revision})
{
return 'interdiff2';
}
}
return undef;
}
sub setup_patch_readers {
my ($diff_root, $context) = @_;
# Parameters:
# format=raw|html
# context=patch|file|0-n
# collapsed=0|1
# headers=0|1
# Define the patch readers.
# The reader that reads the patch in (whatever its format).
require PatchReader::Raw;
my $reader = new PatchReader::Raw;
my $last_reader = $reader;
# Fix the patch root if we have a cvs root.
if (Bugzilla->params->{'cvsroot'}) {
require PatchReader::FixPatchRoot;
$last_reader->sends_data_to(new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'}));
$last_reader->sends_data_to->diff_root($diff_root) if defined($diff_root);
$last_reader = $last_reader->sends_data_to;
}
# Add in cvs context if we have the necessary info to do it
if ($context ne 'patch' && Bugzilla->localconfig->{cvsbin}
&& Bugzilla->params->{'cvsroot_get'})
{
require PatchReader::AddCVSContext;
# We need to set $cvsbin as global, because PatchReader::CVSClient
# needs it in order to find 'cvs'.
$main::cvsbin = Bugzilla->localconfig->{cvsbin};
$last_reader->sends_data_to(
new PatchReader::AddCVSContext($context, Bugzilla->params->{'cvsroot_get'}));
$last_reader = $last_reader->sends_data_to;
}
return ($reader, $last_reader);
}
sub setup_template_patch_reader {
my ($last_reader, $format, $context, $vars) = @_;
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
require PatchReader::DiffPrinter::template;
# Define the vars for templates.
if (defined $cgi->param('headers')) {
$vars->{'headers'} = $cgi->param('headers');
}
else {
$vars->{'headers'} = 1 if !defined $cgi->param('headers');
}
$vars->{'collapsed'} = $cgi->param('collapsed');
$vars->{'context'} = $context;
$vars->{'do_context'} = Bugzilla->localconfig->{cvsbin}
&& Bugzilla->params->{'cvsroot_get'} && !$vars->{'newid'};
# Print everything out.
print $cgi->header(-type => 'text/html',
-expires => '+3M');
$last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
"attachment/diff-header.$format.tmpl",
"attachment/diff-file.$format.tmpl",
"attachment/diff-footer.$format.tmpl",
{ %{$vars},
bonsai_url => Bugzilla->params->{'bonsai_url'},
lxr_url => Bugzilla->params->{'lxr_url'},
lxr_root => Bugzilla->params->{'lxr_root'},
}));
}
1;
__END__

View File

@@ -1,466 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth;
use strict;
use fields qw(
_info_getter
_verifier
_persister
);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Auth::Login::Stack;
use Bugzilla::Auth::Verify::Stack;
use Bugzilla::Auth::Persist::ROSCMS;
sub new {
my ($class, $params) = @_;
my $self = fields::new($class);
$params ||= {};
$params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie';
$params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
$self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
$self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
# If we ever have any other login persistence methods besides cookies,
# this could become more configurable.
$self->{_persister} = new Bugzilla::Auth::Persist::ROSCMS();
return $self;
}
sub login {
my ($self, $type) = @_;
my $dbh = Bugzilla->dbh;
# Get login info from the cookie, form, environment variables, etc.
my $login_info = $self->{_info_getter}->get_login_info();
if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type);
}
# Now verify his username and password against the DB, LDAP, etc.
if ($self->{_info_getter}->{successful}->requires_verification) {
$login_info = $self->{_verifier}->check_credentials($login_info);
if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type);
}
$login_info =
$self->{_verifier}->{successful}->create_or_update_user($login_info);
}
else {
$login_info = $self->{_verifier}->create_or_update_user($login_info);
}
if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type);
}
# Make sure the user isn't disabled.
my $user = $login_info->{user};
if ($user->disabledtext) {
return $self->_handle_login_result({ failure => AUTH_DISABLED,
user => $user }, $type);
}
$user->set_authorizer($self);
return $self->_handle_login_result($login_info, $type);
}
sub can_change_password {
my ($self) = @_;
my $verifier = $self->{_verifier}->{successful};
$verifier ||= $self->{_verifier};
my $getter = $self->{_info_getter}->{successful};
$getter = $self->{_info_getter}
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
return $verifier->can_change_password &&
$getter->user_can_create_account;
}
sub can_login {
my ($self) = @_;
my $getter = $self->{_info_getter}->{successful};
$getter = $self->{_info_getter}
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
return $getter->can_login;
}
sub can_logout {
my ($self) = @_;
my $getter = $self->{_info_getter}->{successful};
# If there's no successful getter, we're not logged in, so of
# course we can't log out!
return 0 unless $getter;
return $getter->can_logout;
}
sub user_can_create_account {
my ($self) = @_;
my $verifier = $self->{_verifier}->{successful};
$verifier ||= $self->{_verifier};
my $getter = $self->{_info_getter}->{successful};
$getter = $self->{_info_getter}
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
return $verifier->user_can_create_account
&& $getter->user_can_create_account;
}
sub can_change_email {
return $_[0]->user_can_create_account;
}
sub _handle_login_result {
my ($self, $result, $login_type) = @_;
my $dbh = Bugzilla->dbh;
my $user = $result->{user};
my $fail_code = $result->{failure};
if (!$fail_code) {
if ($self->{_info_getter}->{successful}->requires_persistence) {
$self->{_persister}->persist_login($user);
}
}
elsif ($fail_code == AUTH_ERROR) {
ThrowCodeError($result->{error}, $result->{details});
}
elsif ($fail_code == AUTH_NODATA) {
if ($login_type == LOGIN_REQUIRED) {
# This seems like as good as time as any to get rid of
# old crufty junk in the logincookies table. Get rid
# of any entry that hasn't been used in a month.
$dbh->do("DELETE FROM logincookies WHERE " .
$dbh->sql_to_days('NOW()') . " - " .
$dbh->sql_to_days('lastused') . " > 30");
$self->{_info_getter}->fail_nodata($self);
}
# Otherwise, we just return the "default" user.
$user = Bugzilla->user;
}
# The username/password may be wrong
# Don't let the user know whether the username exists or whether
# the password was just wrong. (This makes it harder for a cracker
# to find account names by brute force)
elsif (($fail_code == AUTH_LOGINFAILED) || ($fail_code == AUTH_NO_SUCH_USER)) {
ThrowUserError("invalid_username_or_password");
}
# The account may be disabled
elsif ($fail_code == AUTH_DISABLED) {
$self->{_persister}->logout();
# XXX This is NOT a good way to do this, architecturally.
$self->{_persister}->clear_browser_cookies();
# and throw a user error
ThrowUserError("account_disabled",
{'disabled_reason' => $result->{user}->disabledtext});
}
# If we get here, then we've run out of options, which shouldn't happen.
else {
ThrowCodeError("authres_unhandled", { value => $fail_code });
}
return $user;
}
1;
__END__
=head1 NAME
Bugzilla::Auth - An object that authenticates the login credentials for
a user.
=head1 DESCRIPTION
Handles authentication for Bugzilla users.
Authentication from Bugzilla involves two sets of modules. One set is
used to obtain the username/password (from CGI, email, etc), and the
other set uses this data to authenticate against the datasource
(the Bugzilla DB, LDAP, PAM, etc.).
Modules for obtaining the username/password are subclasses of
L<Bugzilla::Auth::Login>, and modules for authenticating are subclasses
of L<Bugzilla::Auth::Verify>.
=head1 AUTHENTICATION ERROR CODES
Whenever a method in the C<Bugzilla::Auth> family fails in some way,
it will return a hashref containing at least a single key called C<failure>.
C<failure> will point to an integer error code, and depending on the error
code the hashref may contain more data.
The error codes are explained here below.
=head2 C<AUTH_NODATA>
Insufficient login data was provided by the user. This may happen in several
cases, such as cookie authentication when the cookie is not present.
=head2 C<AUTH_ERROR>
An error occurred when trying to use the login mechanism.
The hashref will also contain an C<error> element, which is the name
of an error from C<template/en/default/global/code-error.html> --
the same type of error that would be thrown by
L<Bugzilla::Error::ThrowCodeError>.
The hashref *may* contain an element called C<details>, which is a hashref
that should be passed to L<Bugzilla::Error::ThrowCodeError> as the
various fields to be used in the error message.
=head2 C<AUTH_LOGINFAILED>
An incorrect username or password was given.
=head2 C<AUTH_NO_SUCH_USER>
This is an optional more-specific version of C<AUTH_LOGINFAILED>.
Modules should throw this error when they discover that the
requested user account actually does not exist, according to them.
That is, for example, L<Bugzilla::Auth::Verify::LDAP> would throw
this if the user didn't exist in LDAP.
The difference between C<AUTH_NO_SUCH_USER> and C<AUTH_LOGINFAILED>
should never be communicated to the user, for security reasons.
=head2 C<AUTH_DISABLED>
The user successfully logged in, but their account has been disabled.
Usually this is throw only by C<Bugzilla::Auth::login>.
=head1 LOGIN TYPES
The C<login> function (below) can do different types of login, depending
on what constant you pass into it:
=head2 C<LOGIN_OPTIONAL>
A login is never required to access this data. Attempting to login is
still useful, because this allows the page to be personalised. Note that
an incorrect login will still trigger an error, even though the lack of
a login will be OK.
=head2 C<LOGIN_NORMAL>
A login may or may not be required, depending on the setting of the
I<requirelogin> parameter. This is the default if you don't specify a
type.
=head2 C<LOGIN_REQUIRED>
A login is always required to access this data.
=head1 METHODS
These are methods that can be called on a C<Bugzilla::Auth> object
itself.
=head2 Login
=over 4
=item C<login($type)>
Description: Logs a user in. For more details on how this works
internally, see the section entitled "STRUCTURE."
Params: $type - One of the Login Types from above.
Returns: An authenticated C<Bugzilla::User>. Or, if the type was
not C<LOGIN_REQUIRED>, then we return an
empty C<Bugzilla::User> if no login data was passed in.
=back
=head2 Info Methods
These are methods that give information about the Bugzilla::Auth object.
=over 4
=item C<can_change_password>
Description: Tells you whether or not the current login system allows
changing passwords.
Params: None
Returns: C<true> if users and administrators should be allowed to
change passwords, C<false> otherwise.
=item C<can_login>
Description: Tells you whether or not the current login system allows
users to log in through the web interface.
Params: None
Returns: C<true> if users can log in through the web interface,
C<false> otherwise.
=item C<can_logout>
Description: Tells you whether or not the current login system allows
users to log themselves out.
Params: None
Returns: C<true> if users can log themselves out, C<false> otherwise.
If a user isn't logged in, we always return C<false>.
=item C<user_can_create_account>
Description: Tells you whether or not users are allowed to manually create
their own accounts, based on the current login system in use.
Note that this doesn't check the C<createemailregexp>
parameter--you have to do that by yourself in your code.
Params: None
Returns: C<true> if users are allowed to create new Bugzilla accounts,
C<false> otherwise.
=item C<can_change_email>
Description: Whether or not the current login system allows users to
change their own email address.
Params: None
Returns: C<true> if users can change their own email address,
C<false> otherwise.
=back
=head1 STRUCTURE
This section is mostly interesting to developers who want to implement
a new authentication type. It describes the general structure of the
Bugzilla::Auth family, and how the C<login> function works.
A C<Bugzilla::Auth> object is essentially a collection of a few other
objects: the "Info Getter," the "Verifier," and the "Persistence
Mechanism."
They are used inside the C<login> function in the following order:
=head2 The Info Getter
This is a C<Bugzilla::Auth::Login> object. Basically, it gets the
username and password from the user, somehow. Or, it just gets enough
information to uniquely identify a user, and passes that on down the line.
(For example, a C<user_id> is enough to uniquely identify a user,
even without a username and password.)
Some Info Getters don't require any verification. For example, if we got
the C<user_id> from a Cookie, we don't need to check the username and
password.
If an Info Getter returns only a C<user_id> and no username/password,
then it MUST NOT require verification. If an Info Getter requires
verfication, then it MUST return at least a C<username>.
=head2 The Verifier
This verifies that the username and password are valid.
It's possible that some methods of verification don't require a password.
=head2 The Persistence Mechanism
This makes it so that the user doesn't have to log in on every page.
Normally this object just sends a cookie to the user's web browser,
as that's the most common method of "login persistence."
=head2 Other Things We Do
After we verify the username and password, sometimes we automatically
create an account in the Bugzilla database, for certain authentication
types. We use the "Account Source" to get data about the user, and
create them in the database. (Or, if their data has changed since the
last time they logged in, their data gets updated.)
=head2 The C<$login_data> Hash
All of the C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify>
methods take an argument called C<$login_data>. This is basically
a hash that becomes more and more populated as we go through the
C<login> function.
All C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods
also *return* the C<$login_data> structure, when they succeed. They
may have added new data to it.
For all C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods,
the rule is "you must return the same hashref you were passed in." You can
modify the hashref all you want, but you can't create a new one. The only
time you can return a new one is if you're returning some error code
instead of the C<$login_data> structure.
Each C<Bugzilla::Auth::Login> or C<Bugzilla::Auth::Verify> method
explains in its documentation which C<$login_data> elements are
required by it, and which are set by it.
Here are all of the elements that *may* be in C<$login_data>:
=over 4
=item C<user_id>
A Bugzilla C<user_id> that uniquely identifies a user.
=item C<username>
The username that was provided by the user.
=item C<bz_username>
The username of this user inside of Bugzilla. Sometimes this differs from
C<username>.
=item C<password>
The password provided by the user.
=item C<realname>
The real name of the user.
=item C<extern_id>
Some string that uniquely identifies the user in an external account
source. If this C<extern_id> already exists in the database with
a different username, the username will be *changed* to be the
username specified in this C<$login_data>.
That is, let's my extern_id is C<mkanat>. I already have an account
in Bugzilla with the username of C<mkanat@foo.com>. But this time,
when I log in, I have an extern_id of C<mkanat> and a C<username>
of C<mkanat@bar.org>. So now, Bugzilla will automatically change my
username to C<mkanat@bar.org> instead of C<mkanat@foo.com>.
=item C<user>
A L<Bugzilla::User> object representing the authenticated user.
Note that C<Bugzilla::Auth::login> may modify this object at various points.
=back

View File

@@ -1,125 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login;
use strict;
use fields qw();
# Determines whether or not a user can logout. It's really a subroutine,
# but we implement it here as a constant. Override it in subclasses if
# that particular type of login method cannot log out.
use constant can_logout => 1;
use constant can_login => 1;
use constant requires_persistence => 1;
use constant requires_verification => 1;
use constant user_can_create_account => 0;
sub new {
my ($class) = @_;
my $self = fields::new($class);
return $self;
}
1;
__END__
=head1 NAME
Bugzilla::Auth::Login - Gets username/password data from the user.
=head1 DESCRIPTION
Bugzilla::Auth::Login is used to get information that uniquely identifies
a user and allows us to authorize their Bugzilla access.
It is mostly an abstract class, requiring subclasses to implement
most methods.
Note that callers outside of the C<Bugzilla::Auth> package should never
create this object directly. Just create a C<Bugzilla::Auth> object
and call C<login> on it.
=head1 LOGIN METHODS
These are methods that have to do with getting the actual login data
from the user or handling a login somehow.
These methods are abstract -- they MUST be implemented by a subclass.
=over 4
=item C<get_login_info()>
Description: Gets a username/password from the user, or some other
information that uniquely identifies them.
Params: None
Returns: A C<$login_data> hashref. (See L<Bugzilla::Auth> for details.)
The hashref MUST contain: C<user_id> *or* C<username>
If this is a login method that requires verification,
the hashref MUST contain C<password>.
The hashref MAY contain C<realname> and C<extern_id>.
=item C<fail_nodata()>
Description: This function is called when Bugzilla doesn't get
a username/password and the login type is C<LOGIN_REQUIRED>
(See L<Bugzilla::Auth> for a description of C<LOGIN_REQUIRED>).
That is, this handles C<AUTH_NODATA> in that situation.
This function MUST stop CGI execution when it is complete.
That is, it must call C<exit> or C<ThrowUserError> or some
such thing.
Params: None
Returns: Never Returns.
=back
=head1 INFO METHODS
These are methods that describe the capabilities of this
C<Bugzilla::Auth::Login> object. These are all no-parameter
methods that return either C<true> or C<false>.
=over 4
=item C<can_logout>
Whether or not users can log out if they logged in using this
object. Defaults to C<true>.
=item C<can_login>
Whether or not users can log in through the web interface using
this object. Defaults to C<true>.
=item C<requires_persistence>
Whether or not we should send the user a cookie if they logged in with
this method. Defaults to C<true>.
=item C<requires_verification>
Whether or not we should check the username/password that we
got from this login method. Defaults to C<true>.
=item C<user_can_create_account>
Whether or not users can create accounts, if this login method is
currently being used by the system. Defaults to C<false>.
=back

View File

@@ -1,81 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Dave Miller <justdave@syndicomm.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
# Christian Reis <kiko@async.com.br>
# Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::CGI;
use strict;
use base qw(Bugzilla::Auth::Login);
use constant user_can_create_account => 1;
use Bugzilla::Constants;
use Bugzilla::WebService::Constants;
use Bugzilla::Util;
use Bugzilla::Error;
sub get_login_info {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $username = trim($cgi->param("Bugzilla_login"));
my $password = $cgi->param("Bugzilla_password");
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
if (!defined $username || !defined $password) {
return { failure => AUTH_NODATA };
}
return { username => $username, password => $password };
}
sub fail_nodata {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
if (Bugzilla->error_mode == Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT) {
die SOAP::Fault
->faultcode(ERROR_AUTH_NODATA)
->faultstring('Login Required');
}
# Redirect to SSL if required
if (Bugzilla->params->{'sslbase'} ne ''
and Bugzilla->params->{'ssl'} ne 'never')
{
$cgi->require_https(Bugzilla->params->{'sslbase'});
}
print $cgi->header();
$template->process("account/auth/login.html.tmpl",
{ 'target' => $cgi->url(-relative=>1) })
|| ThrowTemplateError($template->error());
exit;
}
1;

View File

@@ -1,96 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::Cookie;
use strict;
use base qw(Bugzilla::Auth::Login);
use Bugzilla::Constants;
use Bugzilla::Util;
use List::Util qw(first);
use constant requires_persistence => 0;
use constant requires_verification => 0;
use constant can_login => 0;
# Note that Cookie never consults the Verifier, it always assumes
# it has a valid DB account or it fails.
sub get_login_info {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
my $ip_addr = $cgi->remote_addr();
my $net_addr = get_netaddr($ip_addr);
my $login_cookie = $cgi->cookie("Bugzilla_logincookie");
my $user_id = $cgi->cookie("Bugzilla_login");
# If cookies cannot be found, this could mean that they haven't
# been made available yet. In this case, look at Bugzilla_cookie_list.
unless ($login_cookie) {
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
@{$cgi->{'Bugzilla_cookie_list'}};
$login_cookie = $cookie->value if $cookie;
}
unless ($user_id) {
my $cookie = first {$_->name eq 'Bugzilla_login'}
@{$cgi->{'Bugzilla_cookie_list'}};
$user_id = $cookie->value if $cookie;
}
if ($login_cookie && $user_id) {
# Anything goes for these params - they're just strings which
# we're going to verify against the db
trick_taint($ip_addr);
trick_taint($login_cookie);
detaint_natural($user_id);
my $query = "SELECT userid
FROM logincookies
WHERE logincookies.cookie = ?
AND logincookies.userid = ?
AND (logincookies.ipaddr = ?";
# If we have a network block that's allowed to use this cookie,
# as opposed to just a single IP.
my @params = ($login_cookie, $user_id, $ip_addr);
if (defined $net_addr) {
trick_taint($net_addr);
$query .= " OR logincookies.ipaddr = ?";
push(@params, $net_addr);
}
$query .= ")";
# If the cookie is valid, return a valid username.
if ($dbh->selectrow_array($query, undef, @params)) {
# If we logged in successfully, then update the lastused
# time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW()
WHERE cookie = ?", undef, $login_cookie);
return { user_id => $user_id };
}
}
# Either the he cookie is invalid, or we got no cookie. We don't want
# to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
# actually throw an error when it gets a bad cookie. It should just
# look like there was no cookie to begin with.
return { failure => AUTH_NODATA };
}
1;

View File

@@ -1,52 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::Env;
use strict;
use base qw(Bugzilla::Auth::Login);
use Bugzilla::Constants;
use Bugzilla::Error;
use constant can_logout => 0;
use constant can_login => 0;
use constant requires_verification => 0;
sub get_login_info {
my ($self) = @_;
my $dbh = Bugzilla->dbh;
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
return { failure => AUTH_NODATA } if !$env_email;
return { username => $env_email, extern_id => $env_id,
realname => $env_realname };
}
sub fail_nodata {
ThrowCodeError('env_no_email');
}
1;

View File

@@ -1,135 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Dave Miller <justdave@syndicomm.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
# Christian Reis <kiko@async.com.br>
# Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Auth::Login class for RosCMS
# based on the former class for Bugzilla 2.x by G<> van Geldorp and Michael Wirth and the Auth::Login::CGI class
# improved and made compatible with Bugzilla 3.x and Deskzilla by Colin Finck (2007-08-06)
package Bugzilla::Auth::Login::ROSCMS;
use strict;
use base qw(Bugzilla::Auth::Login);
use constant can_logout => 0; # The Bugzilla Logout feature has to be disabled, so the user can only log out with the RosCMS Logout feature
use URI;
use URI::Escape;
use Bugzilla::Constants;
use Bugzilla::WebService::Constants;
use Bugzilla::User;
use Bugzilla::Util;
use Bugzilla::Error;
my $session_cookie_name = "roscmsusrkey";
my $roscms_db_name = "roscms";
my $roscms_login_page = "/roscms/?page=login&target=";
sub get_login_info {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
# Check if we have username and password given (usual CGI method for apps like Deskzilla)
my $username = trim($cgi->param("Bugzilla_login"));
my $password = $cgi->param("Bugzilla_password");
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
if(defined $username && defined $password) {
return { username => $username, password => $password };
}
# No, then check for the RosCMS Login cookie
my $dbh = Bugzilla->dbh;
my $user_id;
my $roscms_user_id;
my $session_id = $cgi->cookie($session_cookie_name);
if ( defined $session_id ) {
my $session_id_clean = $session_id;
trick_taint($session_id_clean);
my $remote_addr_clean;
if ($ENV{'REMOTE_ADDR'} =~ m/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/) {
$remote_addr_clean = $1;
} else {
$remote_addr_clean = 'invalid';
}
my $browser_agent_clean = $ENV{'HTTP_USER_AGENT'};
trick_taint($browser_agent_clean);
my $query = "SELECT m.map_subsys_userid, m.map_roscms_userid " .
" FROM $roscms_db_name.user_sessions s, " .
" $roscms_db_name.users u, " .
" $roscms_db_name.subsys_mappings m " .
" WHERE s.usersession_id = ? " .
" AND (s.usersession_expires IS NULL OR " .
" NOW() <= s.usersession_expires) " .
" AND u.user_id = s.usersession_user_id " .
" AND (u.user_setting_ipaddress = 'false' OR " .
" s.usersession_ipaddress = ?) " .
" AND (u.user_setting_browseragent = 'false' OR " .
" s.usersession_browseragent = ?) " .
" AND m.map_roscms_userid = s.usersession_user_id " .
" AND m.map_subsys_name = 'bugzilla'";
my @params = ($session_id_clean, $remote_addr_clean, $browser_agent_clean);
($user_id, $roscms_user_id) = $dbh->selectrow_array($query, undef, @params);
if ($user_id) {
# Update time of last session use
$query = "UPDATE $roscms_db_name.user_sessions " .
" SET usersession_expires = DATE_ADD(NOW(), INTERVAL 30 MINUTE) " .
" WHERE usersession_id = ? " .
" AND usersession_expires IS NOT NULL";
@params = ($session_id_clean);
$dbh->do($query, undef, @params);
# Get the user name and the MD5 password from the database
# We don't check the password explicitly here as we only deal with the session cookie.
# To show the Verify module that it should trust us, we pass the MD5 password hash to it. This should be secure as long as we're the only one who knows this MD5 hash.
my $username = user_id_to_login($user_id);
(my $md5_password) = $dbh->selectrow_array("SELECT user_roscms_password FROM $roscms_db_name.users WHERE user_id = ?", undef, $roscms_user_id);
# We need to set a parameter for the Auth::Persist::ROSCMS module
$cgi->param('ROSCMS_login', 1);
return { username => $username, md5_password => $md5_password };
}
}
return { failure => AUTH_NODATA };
}
sub fail_nodata {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
# Throw up the login page
my $this_uri = uri_escape($cgi->url(-absolute=>1, -path_info=>1, -query=>1));
print $cgi->redirect($roscms_login_page . $this_uri);
exit;
}
1;

View File

@@ -1,87 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Login::Stack;
use strict;
use base qw(Bugzilla::Auth::Login);
use fields qw(
_stack
successful
);
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
my $list = shift;
$self->{_stack} = [];
foreach my $login_method (split(',', $list)) {
require "Bugzilla/Auth/Login/${login_method}.pm";
push(@{$self->{_stack}},
"Bugzilla::Auth::Login::$login_method"->new(@_));
}
return $self;
}
sub get_login_info {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->get_login_info(@_);
$self->{successful} = $object;
last if !$result->{failure};
# So that if none of them succeed, it's undef.
$self->{successful} = undef;
}
return $result;
}
sub fail_nodata {
my $self = shift;
# We fail from the bottom of the stack.
my @reverse_stack = reverse @{$self->{_stack}};
foreach my $object (@reverse_stack) {
# We pick the first object that actually has the method
# implemented.
if ($object->can('fail_nodata')) {
$object->fail_nodata(@_);
}
}
}
sub can_login {
my ($self) = @_;
# We return true if any method can log in.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_login;
}
return 0;
}
sub user_can_create_account {
my ($self) = @_;
# We return true if any method allows users to create accounts.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
}
1;

View File

@@ -1,151 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Dave Miller <justdave@syndicomm.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
# Christian Reis <kiko@async.com.br>
# Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Persist::Cookie;
use strict;
use fields qw();
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Token;
use List::Util qw(first);
sub new {
my ($class) = @_;
my $self = fields::new($class);
return $self;
}
sub persist_login {
my ($self, $user) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
my $ip_addr = $cgi->remote_addr;
unless ($cgi->param('Bugzilla_restrictlogin') ||
Bugzilla->params->{'loginnetmask'} == 32)
{
$ip_addr = get_netaddr($ip_addr);
}
# The IP address is valid, at least for comparing with itself in a
# subsequent login
trick_taint($ip_addr);
my $login_cookie =
Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
VALUES (?, ?, ?, NOW())",
undef, $login_cookie, $user->id, $ip_addr);
# Remember cookie only if admin has told so
# or admin didn't forbid it and user told to remember.
if ( Bugzilla->params->{'rememberlogin'} eq 'on' ||
(Bugzilla->params->{'rememberlogin'} ne 'off' &&
$cgi->param('Bugzilla_remember') &&
$cgi->param('Bugzilla_remember') eq 'on') )
{
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $user->id,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
}
else {
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $user->id);
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie);
}
}
sub logout {
my ($self, $param) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
$param = {} unless $param;
my $user = $param->{user} || Bugzilla->user;
my $type = $param->{type} || LOGOUT_ALL;
if ($type == LOGOUT_ALL) {
$dbh->do("DELETE FROM logincookies WHERE userid = ?",
undef, $user->id);
return;
}
# The LOGOUT_*_CURRENT options require the current login cookie.
# If a new cookie has been issued during this run, that's the current one.
# If not, it's the one we've received.
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
@{$cgi->{'Bugzilla_cookie_list'}};
my $login_cookie;
if ($cookie) {
$login_cookie = $cookie->value;
}
else {
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
}
trick_taint($login_cookie);
# These queries use both the cookie ID and the user ID as keys. Even
# though we know the userid must match, we still check it in the SQL
# as a sanity check, since there is no locking here, and if the user
# logged out from two machines simultaneously, while someone else
# logged in and got the same cookie, we could be logging the other
# user out here. Yes, this is very very very unlikely, but why take
# chances? - bbaetz
if ($type == LOGOUT_KEEP_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
undef, $login_cookie, $user->id);
} elsif ($type == LOGOUT_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
undef, $login_cookie, $user->id);
} else {
die("Invalid type $type supplied to logout()");
}
if ($type != LOGOUT_KEEP_CURRENT) {
clear_browser_cookies();
}
}
sub clear_browser_cookies {
my $cgi = Bugzilla->cgi;
$cgi->remove_cookie('Bugzilla_login');
$cgi->remove_cookie('Bugzilla_logincookie');
}
1;

View File

@@ -1,159 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Dave Miller <justdave@syndicomm.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
# Christian Reis <kiko@async.com.br>
# Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Auth::Persist class for RosCMS
# developed by Colin Finck based on the Auth::Persist::Cookie class (2007-07-29)
package Bugzilla::Auth::Persist::ROSCMS;
use strict;
use fields qw();
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Token;
use List::Util qw(first);
sub new {
my ($class) = @_;
my $self = fields::new($class);
return $self;
}
sub persist_login {
my ($self, $user) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
# When we log in using RosCMS, it handles the persistence itself
if( defined $cgi->param('ROSCMS_login') ) {
return;
}
my $ip_addr = $cgi->remote_addr;
unless ($cgi->param('Bugzilla_restrictlogin') ||
Bugzilla->params->{'loginnetmask'} == 32)
{
$ip_addr = get_netaddr($ip_addr);
}
# The IP address is valid, at least for comparing with itself in a
# subsequent login
trick_taint($ip_addr);
my $login_cookie =
Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
VALUES (?, ?, ?, NOW())",
undef, $login_cookie, $user->id, $ip_addr);
# Remember cookie only if admin has told so
# or admin didn't forbid it and user told to remember.
if ( Bugzilla->params->{'rememberlogin'} eq 'on' ||
(Bugzilla->params->{'rememberlogin'} ne 'off' &&
$cgi->param('Bugzilla_remember') &&
$cgi->param('Bugzilla_remember') eq 'on') )
{
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $user->id,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie,
-expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
}
else {
$cgi->send_cookie(-name => 'Bugzilla_login',
-value => $user->id);
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie);
}
}
sub logout {
my ($self, $param) = @_;
my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi;
$param = {} unless $param;
my $user = $param->{user} || Bugzilla->user;
my $type = $param->{type} || LOGOUT_ALL;
if ($type == LOGOUT_ALL) {
$dbh->do("DELETE FROM logincookies WHERE userid = ?",
undef, $user->id);
return;
}
# The LOGOUT_*_CURRENT options require the current login cookie.
# If a new cookie has been issued during this run, that's the current one.
# If not, it's the one we've received.
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
@{$cgi->{'Bugzilla_cookie_list'}};
my $login_cookie;
if ($cookie) {
$login_cookie = $cookie->value;
}
else {
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
}
trick_taint($login_cookie);
# These queries use both the cookie ID and the user ID as keys. Even
# though we know the userid must match, we still check it in the SQL
# as a sanity check, since there is no locking here, and if the user
# logged out from two machines simultaneously, while someone else
# logged in and got the same cookie, we could be logging the other
# user out here. Yes, this is very very very unlikely, but why take
# chances? - bbaetz
if ($type == LOGOUT_KEEP_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
undef, $login_cookie, $user->id);
} elsif ($type == LOGOUT_CURRENT) {
$dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
undef, $login_cookie, $user->id);
} else {
die("Invalid type $type supplied to logout()");
}
if ($type != LOGOUT_KEEP_CURRENT) {
clear_browser_cookies();
}
}
sub clear_browser_cookies {
my $cgi = Bugzilla->cgi;
$cgi->remove_cookie('Bugzilla_login');
$cgi->remove_cookie('Bugzilla_logincookie');
}
1;

View File

@@ -1,236 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Verify;
use strict;
use fields qw();
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Util;
use constant user_can_create_account => 1;
sub new {
my ($class, $login_type) = @_;
my $self = fields::new($class);
return $self;
}
sub can_change_password {
return $_[0]->can('change_password');
}
sub create_or_update_user {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $extern_id = $params->{extern_id};
my $username = $params->{bz_username} || $params->{username};
my $password = $params->{password} || '*';
my $real_name = $params->{realname} || '';
my $user_id = $params->{user_id};
# A passed-in user_id always overrides anything else, for determining
# what account we should return.
if (!$user_id) {
my $username_user_id = login_to_id($username || '');
my $extern_user_id;
if ($extern_id) {
trick_taint($extern_id);
$extern_user_id = $dbh->selectrow_array('SELECT userid
FROM profiles WHERE extern_id = ?', undef, $extern_id);
}
# If we have both a valid extern_id and a valid username, and they are
# not the same id, then we have a conflict.
if ($username_user_id && $extern_user_id
&& $username_user_id ne $extern_user_id)
{
my $extern_name = Bugzilla::User->new($extern_user_id)->login;
return { failure => AUTH_ERROR, error => "extern_id_conflict",
details => {extern_id => $extern_id,
extern_user => $extern_name,
username => $username} };
}
# If we have a valid username, but no valid id,
# then we have to create the user. This happens when we're
# passed only a username, and that username doesn't exist already.
if ($username && !$username_user_id && !$extern_user_id) {
validate_email_syntax($username)
|| return { failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $username} };
# Usually we'd call validate_password, but external authentication
# systems might follow different standards than ours. So in this
# place here, we call trick_taint without checks.
trick_taint($password);
# XXX Theoretically this could fail with an error, but the fix for
# that is too involved to be done right now.
my $user = Bugzilla::User->create({
login_name => $username,
cryptpassword => $password,
realname => $real_name});
$username_user_id = $user->id;
}
# If we have a valid username id and an extern_id, but no valid
# extern_user_id, then we have to set the user's extern_id.
if ($extern_id && $username_user_id && !$extern_user_id) {
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
undef, $extern_id, $username_user_id);
}
# Finally, at this point, one of these will give us a valid user id.
$user_id = $extern_user_id || $username_user_id;
}
# If we still don't have a valid user_id, then we weren't passed
# enough information in $params, and we should die right here.
ThrowCodeError('bad_arg', {argument => 'params', function =>
'Bugzilla::Auth::Verify::create_or_update_user'})
unless $user_id;
my $user = new Bugzilla::User($user_id);
# Now that we have a valid User, we need to see if any data has to be
# updated.
if ($username && lc($user->login) ne lc($username)) {
validate_email_syntax($username)
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email',
details => {addr => $username} };
$dbh->do('UPDATE profiles SET login_name = ? WHERE userid = ?',
undef, $username, $user->id);
}
if ($real_name && $user->name ne $real_name) {
# $real_name is more than likely tainted, but we only use it
# in a placeholder and we never use it after this.
trick_taint($real_name);
$dbh->do('UPDATE profiles SET realname = ? WHERE userid = ?',
undef, $real_name, $user->id);
}
return { user => $user };
}
1;
__END__
=head1 NAME
Bugzilla::Auth::Verify - An object that verifies usernames and passwords.
=head1 DESCRIPTION
Bugzilla::Auth::Verify provides the "Verifier" part of the Bugzilla
login process. (For details, see the "STRUCTURE" section of
L<Bugzilla::Auth>.)
It is mostly an abstract class, requiring subclasses to implement
most methods.
Note that callers outside of the C<Bugzilla::Auth> package should never
create this object directly. Just create a C<Bugzilla::Auth> object
and call C<login> on it.
=head1 VERIFICATION METHODS
These are the methods that have to do with the actual verification.
Subclasses MUST implement these methods.
=over 4
=item C<check_credentials($login_data)>
Description: Checks whether or not a username is valid.
Params: $login_data - A C<$login_data> hashref, as described in
L<Bugzilla::Auth>.
This C<$login_data> hashref MUST contain
C<username>, and SHOULD also contain
C<password>.
Returns: A C<$login_data> hashref with C<bz_username> set. This
method may also set C<realname>. It must avoid changing
anything that is already set.
=back
=head1 MODIFICATION METHODS
These are methods that change data in the actual authentication backend.
These methods are optional, they do not have to be implemented by
subclasses.
=over 4
=item C<create_or_update_user($login_data)>
Description: Automatically creates a user account in the database
if it doesn't already exist, or updates the account
data if C<$login_data> contains newer information.
Params: $login_data - A C<$login_data> hashref, as described in
L<Bugzilla::Auth>.
This C<$login_data> hashref MUST contain
either C<user_id>, C<bz_username>, or
C<username>. If both C<username> and C<bz_username>
are specified, C<bz_username> is used as the
login name of the user to create in the database.
It MAY also contain C<extern_id>, in which
case it still MUST contain C<bz_username> or
C<username>.
It MAY contain C<password> and C<realname>.
Returns: A hashref with one element, C<user>, which is a
L<Bugzilla::User> object. May also return a login error
as described in L<Bugzilla::Auth>.
Note: This method is not abstract, it is actually implemented
and creates accounts in the Bugzilla database. Subclasses
should probably all call the C<Bugzilla::Auth::Verify>
version of this function at the end of their own
C<create_or_update_user>.
=item C<change_password($user, $password)>
Description: Modifies the user's password in the authentication backend.
Params: $user - A L<Bugzilla::User> object representing the user
whose password we want to change.
$password - The user's new password.
Returns: Nothing.
=back
=head1 INFO METHODS
These are methods that describe the capabilities of this object.
These are all no-parameter methods that return either C<true> or
C<false>.
=over 4
=item C<user_can_create_account>
Whether or not users can manually create accounts in this type of
account source. Defaults to C<true>.
=back

View File

@@ -1,78 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Dave Miller <justdave@syndicomm.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
# Christian Reis <kiko@async.com.br>
# Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
package Bugzilla::Auth::Verify::DB;
use strict;
use base qw(Bugzilla::Auth::Verify);
use Bugzilla::Constants;
use Bugzilla::Token;
use Bugzilla::Util;
use Bugzilla::User;
sub check_credentials {
my ($self, $login_data) = @_;
my $dbh = Bugzilla->dbh;
my $username = $login_data->{username};
my $user_id = login_to_id($username);
return { failure => AUTH_NO_SUCH_USER } unless $user_id;
$login_data->{bz_username} = $username;
my $password = $login_data->{password};
trick_taint($username);
my ($real_password_crypted) = $dbh->selectrow_array(
"SELECT cryptpassword FROM profiles WHERE userid = ?",
undef, $user_id);
# Using the internal crypted password as the salt,
# crypt the password the user entered.
my $entered_password_crypted = crypt($password, $real_password_crypted);
return { failure => AUTH_LOGINFAILED }
if $entered_password_crypted ne $real_password_crypted;
# The user's credentials are okay, so delete any outstanding
# password tokens they may have generated.
Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in");
return $login_data;
}
sub change_password {
my ($self, $user, $password) = @_;
my $dbh = Bugzilla->dbh;
my $cryptpassword = bz_crypt($password);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $user->id);
}
1;

View File

@@ -1,153 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Dave Miller <justdave@syndicomm.com>
# Christopher Aillon <christopher@aillon.com>
# Gervase Markham <gerv@gerv.net>
# Christian Reis <kiko@async.com.br>
# Bradley Baetz <bbaetz@acm.org>
# Erik Stambaugh <erik@dasbistro.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Verify::LDAP;
use strict;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
ldap
);
use Bugzilla::Constants;
use Bugzilla::Error;
use Net::LDAP;
use constant admin_can_create_account => 0;
use constant user_can_create_account => 0;
sub check_credentials {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
# We need to bind anonymously to the LDAP server. This is
# because we need to get the Distinguished Name of the user trying
# to log in. Some servers (such as iPlanet) allow you to have unique
# uids spread out over a subtree of an area (such as "People"), so
# just appending the Base DN to the uid isn't sufficient to get the
# user's DN. For servers which don't work this way, there will still
# be no harm done.
$self->_bind_ldap_anonymously();
# Now, we verify that the user exists, and get a LDAP Distinguished
# Name for the user.
my $username = $params->{username};
my $dn_result = $self->ldap->search(_bz_search_params($username),
attrs => ['dn']);
return { failure => AUTH_ERROR, error => "ldap_search_error",
details => {errstr => $dn_result->error, username => $username}
} if $dn_result->code;
return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count;
my $dn = $dn_result->shift_entry->dn;
# Check the password.
my $pw_result = $self->ldap->bind($dn, password => $params->{password});
return { failure => AUTH_LOGINFAILED } if $pw_result->code;
# And now we fill in the user's details.
my $detail_result = $self->ldap->search(_bz_search_params($username));
return { failure => AUTH_ERROR, error => "ldap_search_error",
details => {errstr => $detail_result->error, username => $username}
} if $detail_result->code;
my $user_entry = $detail_result->shift_entry;
my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
if ($mail_attr) {
if (!$user_entry->exists($mail_attr)) {
return { failure => AUTH_ERROR,
error => "ldap_cannot_retreive_attr",
details => {attr => $mail_attr} };
}
$params->{bz_username} = $user_entry->get_value($mail_attr);
} else {
$params->{bz_username} = $username;
}
$params->{realname} ||= $user_entry->get_value("displayName");
$params->{realname} ||= $user_entry->get_value("cn");
return $params;
}
sub _bz_search_params {
my ($username) = @_;
return (base => Bugzilla->params->{"LDAPBaseDN"},
scope => "sub",
filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
. "=$username)"
. Bugzilla->params->{"LDAPfilter"} . ')');
}
sub _bind_ldap_anonymously {
my ($self) = @_;
my $bind_result;
if (Bugzilla->params->{"LDAPbinddn"}) {
my ($LDAPbinddn,$LDAPbindpass) =
split(":",Bugzilla->params->{"LDAPbinddn"});
$bind_result =
$self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
}
else {
$bind_result = $self->ldap->bind();
}
ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
if $bind_result->code;
}
# We can't just do this in new(), because we're not allowed to throw any
# error from anywhere under Bugzilla::Auth::new -- otherwise we
# could create a situation where the admin couldn't get to editparams
# to fix his mistake. (Because Bugzilla->login always calls
# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
sub ldap {
my ($self) = @_;
return $self->{ldap} if $self->{ldap};
my $server = Bugzilla->params->{"LDAPserver"};
ThrowCodeError("ldap_server_not_defined") unless $server;
$self->{ldap} = new Net::LDAP($server)
|| ThrowCodeError("ldap_connect_failed", { server => $server });
# try to start TLS if needed
if (Bugzilla->params->{"LDAPstarttls"}) {
my $mesg = $self->{ldap}->start_tls();
ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() })
if $mesg->code();
}
return $self->{ldap};
}
1;

View File

@@ -1,83 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): 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>
# Auth::Verify class for RosCMS
# developed by Colin Finck based on the Auth::Verify::DB class (2007-08-06)
package Bugzilla::Auth::Verify::ROSCMS;
use strict;
use base qw(Bugzilla::Auth::Verify);
use constant can_change_password => 0; # Password has to be changed at myReactOS
use Bugzilla::Constants;
use Bugzilla::Token;
use Bugzilla::Util;
use Bugzilla::User;
use Digest::MD5 qw(md5_hex);
my $roscms_db_name = "roscms";
sub check_credentials {
my ($self, $login_data) = @_;
my $dbh = Bugzilla->dbh;
my $username = $login_data->{username};
my $user_id = login_to_id($username);
return { failure => AUTH_NO_SUCH_USER } unless $user_id;
$login_data->{bz_username} = $username;
my $md5_password = $login_data->{md5_password};
if( !defined $md5_password )
{
my $password = $login_data->{password};
$md5_password = md5_hex($password);
}
my $query = "SELECT u.user_roscms_password " .
"FROM $roscms_db_name.users u, " .
" $roscms_db_name.subsys_mappings m " .
"WHERE u.user_id = m.map_roscms_userid " .
" AND m.map_subsys_name = 'bugzilla' " .
" AND m.map_subsys_userid = ?";
(my $valid_md5_password) = $dbh->selectrow_array($query, undef, $user_id);
return { failure => AUTH_LOGINFAILED }
if $md5_password ne $valid_md5_password;
# The user's credentials are okay, so delete any outstanding
# password tokens they may have generated.
Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in");
return $login_data;
}
1;

View File

@@ -1,81 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Auth::Verify::Stack;
use strict;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
_stack
successful
);
sub new {
my $class = shift;
my $list = shift;
my $self = $class->SUPER::new(@_);
$self->{_stack} = [];
foreach my $verify_method (split(',', $list)) {
require "Bugzilla/Auth/Verify/${verify_method}.pm";
push(@{$self->{_stack}},
"Bugzilla::Auth::Verify::$verify_method"->new(@_));
}
return $self;
}
sub can_change_password {
my ($self) = @_;
# We return true if any method can change passwords.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_change_password;
}
return 0;
}
sub check_credentials {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->check_credentials(@_);
$self->{successful} = $object;
last if !$result->{failure};
# So that if none of them succeed, it's undef.
$self->{successful} = undef;
}
# Returns the result at the bottom of the stack if they all fail.
return $result;
}
sub create_or_update_user {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->create_or_update_user(@_);
last if !$result->{failure};
}
# Returns the result at the bottom of the stack if they all fail.
return $result;
}
sub user_can_create_account {
my ($self) = @_;
# We return true if any method allows the user to create an account.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
}
1;

File diff suppressed because it is too large Load Diff

View File

@@ -1,725 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>,
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
# Dan Mosedale <dmose@mozilla.org>
# Alan Raetz <al_raetz@yahoo.com>
# Jacob Steenhagen <jake@actex.net>
# Matthew Tuck <matty@chariot.net.au>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# J. Paul Reed <preed@sigkill.com>
# Gervase Markham <gerv@gerv.net>
# Byron Jones <bugzilla@glob.com.au>
use strict;
package Bugzilla::BugMail;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Bug;
use Bugzilla::Product;
use Bugzilla::Component;
use Bugzilla::Mailer;
use Date::Parse;
use Date::Format;
use constant BIT_DIRECT => 1;
use constant BIT_WATCHING => 2;
# We need these strings for the X-Bugzilla-Reasons header
# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
use constant REL_NAMES => {
REL_ASSIGNEE , "AssignedTo",
REL_REPORTER , "Reporter",
REL_QA , "QAcontact",
REL_CC , "CC",
REL_VOTER , "Voter",
REL_GLOBAL_WATCHER, "GlobalWatcher"
};
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) = (@_);
my @headerlist;
my %defmailhead;
my %fielddescription;
my $msg = "";
my $dbh = Bugzilla->dbh;
# XXX - These variables below are useless. We could use field object
# methods directly. But we first have to implement a cache in
# Bugzilla->get_fields to avoid querying the DB all the time.
foreach my $field (Bugzilla->get_fields({obsolete => 0})) {
push(@headerlist, $field->name);
$defmailhead{$field->name} = $field->in_new_bugmail;
$fielddescription{$field->name} = $field->description;
}
my %values = %{$dbh->selectrow_hashref(
'SELECT ' . join(',', editable_bug_fields()) . ', reporter,
lastdiffed AS start, LOCALTIMESTAMP(0) AS end
FROM bugs WHERE bug_id = ?',
undef, $id)};
my $product = new Bugzilla::Product($values{product_id});
$values{product} = $product->name;
my $component = new Bugzilla::Component($values{component_id});
$values{component} = $component->name;
my ($start, $end) = ($values{start}, $values{end});
# User IDs of people in various roles. More than one person can 'have' a
# role, if the person in that role has changed, or people are watching.
my $reporter = $values{'reporter'};
my @assignees = ($values{'assigned_to'});
my @qa_contacts = ($values{'qa_contact'});
my $cc_users = $dbh->selectall_arrayref(
"SELECT cc.who, profiles.login_name
FROM cc
INNER JOIN profiles
ON cc.who = profiles.userid
WHERE bug_id = ?",
undef, $id);
my (@ccs, @cc_login_names);
foreach my $cc_user (@$cc_users) {
my ($user_id, $user_login) = @$cc_user;
push (@ccs, $user_id);
push (@cc_login_names, $user_login);
}
# Include the people passed in as being in particular roles.
# This can include people who used to hold those roles.
# At this point, we don't care if there are duplicates in these arrays.
my $changer = $forced->{'changer'};
if ($forced->{'owner'}) {
push (@assignees, login_to_id($forced->{'owner'}, THROW_ERROR));
}
if ($forced->{'qacontact'}) {
push (@qa_contacts, login_to_id($forced->{'qacontact'}, THROW_ERROR));
}
if ($forced->{'cc'}) {
foreach my $cc (@{$forced->{'cc'}}) {
push(@ccs, login_to_id($cc, THROW_ERROR));
}
}
# Convert to names, for later display
$values{'changer'} = $changer;
# If no changer is specified, then it has no name.
if ($changer) {
$values{'changername'} = Bugzilla::User->new({name => $changer})->name;
}
$values{'assigned_to'} = user_id_to_login($values{'assigned_to'});
$values{'reporter'} = user_id_to_login($values{'reporter'});
if ($values{'qa_contact'}) {
$values{'qa_contact'} = user_id_to_login($values{'qa_contact'});
}
$values{'cc'} = join(', ', @cc_login_names);
$values{'estimated_time'} = format_time_decimal($values{'estimated_time'});
if ($values{'deadline'}) {
$values{'deadline'} = time2str("%Y-%m-%d", str2time($values{'deadline'}));
}
my $dependslist = $dbh->selectcol_arrayref(
'SELECT dependson FROM dependencies
WHERE blocked = ? ORDER BY dependson',
undef, ($id));
$values{'dependson'} = join(",", @$dependslist);
my $blockedlist = $dbh->selectcol_arrayref(
'SELECT blocked FROM dependencies
WHERE dependson = ? ORDER BY blocked',
undef, ($id));
$values{'blocked'} = join(",", @$blockedlist);
my @args = ($id);
# If lastdiffed is NULL, then we don't limit the search on time.
my $when_restriction = '';
if ($start) {
$when_restriction = ' AND bug_when > ? AND bug_when <= ?';
push @args, ($start, $end);
}
my $diffs = $dbh->selectall_arrayref(
"SELECT profiles.login_name, profiles.realname, fielddefs.description,
bugs_activity.bug_when, bugs_activity.removed,
bugs_activity.added, bugs_activity.attach_id, fielddefs.name
FROM bugs_activity
INNER JOIN fielddefs
ON fielddefs.id = bugs_activity.fieldid
INNER JOIN profiles
ON profiles.userid = bugs_activity.who
WHERE bugs_activity.bug_id = ?
$when_restriction
ORDER BY bugs_activity.bug_when", undef, @args);
my @new_depbugs;
my $difftext = "";
my $diffheader = "";
my @diffparts;
my $lastwho = "";
my $fullwho;
my @changedfields;
foreach my $ref (@$diffs) {
my ($who, $whoname, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref);
my $diffpart = {};
if ($who ne $lastwho) {
$lastwho = $who;
$fullwho = $whoname ? "$whoname <$who" . Bugzilla->params->{'emailsuffix'} . ">" :
"$who" . Bugzilla->params->{'emailsuffix'};
$diffheader = "\n$fullwho 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 ($fieldname eq 'dependson') {
push(@new_depbugs, grep {$_ =~ /^\d+$/} split(/[\s,]+/, $new));
}
if ($attachid) {
($diffpart->{'isprivate'}) = $dbh->selectrow_array(
'SELECT isprivate FROM attachments WHERE attach_id = ?',
undef, ($attachid));
}
$difftext = FormatTriple($what, $old, $new);
$diffpart->{'header'} = $diffheader;
$diffpart->{'fieldname'} = $fieldname;
$diffpart->{'text'} = $difftext;
push(@diffparts, $diffpart);
push(@changedfields, $what);
}
$values{'changed_fields'} = join(' ', @changedfields);
my @depbugs;
my $deptext = "";
# Do not include data about dependent bugs when they have just been added.
# Completely skip checking for dependent bugs on bug creation as all
# dependencies bugs will just have been added.
if ($start) {
my $dep_restriction = "";
if (scalar @new_depbugs) {
$dep_restriction = "AND bugs_activity.bug_id NOT IN (" .
join(", ", @new_depbugs) . ")";
}
my $dependency_diffs = $dbh->selectall_arrayref(
"SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name,
bugs_activity.removed, bugs_activity.added
FROM bugs_activity
INNER JOIN bugs
ON bugs.bug_id = bugs_activity.bug_id
INNER JOIN dependencies
ON bugs_activity.bug_id = dependencies.dependson
INNER JOIN fielddefs
ON fielddefs.id = bugs_activity.fieldid
WHERE dependencies.blocked = ?
AND (fielddefs.name = 'bug_status'
OR fielddefs.name = 'resolution')
$when_restriction
$dep_restriction
ORDER BY bugs_activity.bug_when, bugs.bug_id", undef, @args);
my $thisdiff = "";
my $lastbug = "";
my $interestingchange = 0;
foreach my $dependency_diff (@$dependency_diffs) {
my ($depbug, $summary, $what, $old, $new) = @$dependency_diff;
if ($depbug ne $lastbug) {
if ($interestingchange) {
$deptext .= $thisdiff;
}
$lastbug = $depbug;
my $urlbase = Bugzilla->params->{"urlbase"};
$thisdiff =
"\nBug $id depends on bug $depbug, which changed state.\n\n" .
"Bug $depbug Summary: $summary\n" .
"${urlbase}show_bug.cgi?id=$depbug\n\n";
$thisdiff .= FormatTriple("What ", "Old Value", "New Value");
$thisdiff .= ('-' x 76) . "\n";
$interestingchange = 0;
}
$thisdiff .= FormatTriple($fielddescription{$what}, $old, $new);
if ($what eq 'bug_status'
&& Bugzilla::Bug::is_open_state($old) ne Bugzilla::Bug::is_open_state($new))
{
$interestingchange = 1;
}
push(@depbugs, $depbug);
}
if ($interestingchange) {
$deptext .= $thisdiff;
}
$deptext = trim($deptext);
if ($deptext) {
my $diffpart = {};
$diffpart->{'text'} = "\n" . trim("\n\n" . $deptext);
push(@diffparts, $diffpart);
}
}
my ($raw_comments, $anyprivate, $count) = get_comments_by_bug($id, $start, $end);
###########################################################################
# Start of email filtering code
###########################################################################
# A user_id => roles hash to keep track of people.
my %recipients;
my %watching;
# Now we work out all the people involved with this bug, and note all of
# the relationships in a hash. The keys are userids, the values are an
# array of role constants.
# Voters
my $voters = $dbh->selectcol_arrayref(
"SELECT who FROM votes WHERE bug_id = ?", undef, ($id));
$recipients{$_}->{+REL_VOTER} = BIT_DIRECT foreach (@$voters);
# CCs
$recipients{$_}->{+REL_CC} = BIT_DIRECT foreach (@ccs);
# Reporter (there's only ever one)
$recipients{$reporter}->{+REL_REPORTER} = BIT_DIRECT;
# QA Contact
if (Bugzilla->params->{'useqacontact'}) {
foreach (@qa_contacts) {
# QA Contact can be blank; ignore it if so.
$recipients{$_}->{+REL_QA} = BIT_DIRECT if $_;
}
}
# Assignee
$recipients{$_}->{+REL_ASSIGNEE} = BIT_DIRECT foreach (@assignees);
# The last relevant set of people are those who are being removed from
# their roles in this change. We get their names out of the diffs.
foreach my $ref (@$diffs) {
my ($who, $whoname, $what, $when, $old, $new) = (@$ref);
if ($old) {
# You can't stop being the reporter, and mail isn't sent if you
# remove your vote.
# Ignore people whose user account has been deleted or renamed.
if ($what eq "CC") {
foreach my $cc_user (split(/[\s,]+/, $old)) {
my $uid = login_to_id($cc_user);
$recipients{$uid}->{+REL_CC} = BIT_DIRECT if $uid;
}
}
elsif ($what eq "QAContact") {
my $uid = login_to_id($old);
$recipients{$uid}->{+REL_QA} = BIT_DIRECT if $uid;
}
elsif ($what eq "AssignedTo") {
my $uid = login_to_id($old);
$recipients{$uid}->{+REL_ASSIGNEE} = BIT_DIRECT if $uid;
}
}
}
if (Bugzilla->params->{"supportwatchers"}) {
# Find all those user-watching anyone on the current list, who is not
# on it already themselves.
my $involved = join(",", keys %recipients);
my $userwatchers =
$dbh->selectall_arrayref("SELECT watcher, watched FROM watch
WHERE watched IN ($involved)");
# Mark these people as having the role of the person they are watching
foreach my $watch (@$userwatchers) {
while (my ($role, $bits) = each %{$recipients{$watch->[1]}}) {
$recipients{$watch->[0]}->{$role} |= BIT_WATCHING
if $bits & BIT_DIRECT;
}
push (@{$watching{$watch->[0]}}, $watch->[1]);
}
}
# Global watcher
my @watchers = split(/[,\s]+/, Bugzilla->params->{'globalwatchers'});
foreach (@watchers) {
my $watcher_id = login_to_id($_);
next unless $watcher_id;
$recipients{$watcher_id}->{+REL_GLOBAL_WATCHER} = BIT_DIRECT;
}
# We now have a complete set of all the users, and their relationships to
# the bug in question. However, we are not necessarily going to mail them
# all - there are preferences, permissions checks and all sorts to do yet.
my @sent;
my @excluded;
# Some comments are language specific. We cache them here.
my %comments;
foreach my $user_id (keys %recipients) {
my %rels_which_want;
my $sent_mail = 0;
my $user = new Bugzilla::User($user_id);
# Deleted users must be excluded.
next unless $user;
# What's the language chosen by this user for email?
my $lang = $user->settings->{'lang'}->{'value'};
if ($user->can_see_bug($id)) {
# It's time to format language specific comments.
unless (exists $comments{$lang}) {
Bugzilla->template_inner($lang);
$comments{$lang} = prepare_comments($raw_comments, $count);
Bugzilla->template_inner("");
}
# Go through each role the user has and see if they want mail in
# that role.
foreach my $relationship (keys %{$recipients{$user_id}}) {
if ($user->wants_bug_mail($id,
$relationship,
$diffs,
$comments{$lang},
$deptext,
$changer,
!$start))
{
$rels_which_want{$relationship} =
$recipients{$user_id}->{$relationship};
}
}
}
if (scalar(%rels_which_want)) {
# So the user exists, can see the bug, and wants mail in at least
# one role. But do we want to send it to them?
# If we are using insiders, and the comment is private, only send
# to insiders
my $insider_ok = 1;
$insider_ok = 0 if (Bugzilla->params->{"insidergroup"} &&
($anyprivate != 0) &&
(!$user->groups->{Bugzilla->params->{"insidergroup"}}));
# We shouldn't send mail if this is a dependency mail (i.e. there
# is something in @depbugs), and any of the depending bugs are not
# visible to the user. This is to avoid leaking the summaries of
# confidential bugs.
my $dep_ok = 1;
foreach my $dep_id (@depbugs) {
if (!$user->can_see_bug($dep_id)) {
$dep_ok = 0;
last;
}
}
# Make sure the user isn't in the nomail list, and the insider and
# dep checks passed.
if ($user->email_enabled &&
$insider_ok &&
$dep_ok)
{
# OK, OK, if we must. Email the user.
$sent_mail = sendMail($user,
\@headerlist,
\%rels_which_want,
\%values,
\%defmailhead,
\%fielddescription,
\@diffparts,
$comments{$lang},
$anyprivate,
$start,
$id,
exists $watching{$user_id} ?
$watching{$user_id} : undef);
}
}
if ($sent_mail) {
push(@sent, $user->login);
}
else {
push(@excluded, $user->login);
}
}
$dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
undef, ($end, $id));
return {'sent' => \@sent, 'excluded' => \@excluded};
}
sub sendMail {
my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,
$diffRef, $newcomments, $anyprivate, $start,
$id, $watchingRef) = @_;
my %values = %$valueRef;
my @headerlist = @$hlRef;
my %mailhead = %$dmhRef;
my %fielddescription = %$fdRef;
my @diffparts = @$diffRef;
my $head = "";
foreach my $f (@headerlist) {
if ($mailhead{$f}) {
my $value = $values{$f};
# If there isn't anything to show, don't include this header
if (! $value) {
next;
}
# Only send estimated_time if it is enabled and the user is in the group
if (($f ne 'estimated_time' && $f ne 'deadline') ||
$user->groups->{Bugzilla->params->{'timetrackinggroup'}}) {
my $desc = $fielddescription{$f};
$head .= 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->{Bugzilla->params->{"timetrackinggroup"}}) {
$add_diff = 1;
}
} elsif (($diff->{'isprivate'})
&& Bugzilla->params->{'insidergroup'}
&& !($user->groups->{Bugzilla->params->{'insidergroup'}})
) {
$add_diff = 0;
} else {
$add_diff = 1;
}
if ($add_diff) {
if (exists($diff->{'header'}) &&
($diffheader ne $diff->{'header'})) {
$diffheader = $diff->{'header'};
$difftext .= $diffheader;
}
$difftext .= $diff->{'text'};
}
}
if ($difftext eq "" && $newcomments eq "") {
# Whoops, no differences!
return 0;
}
my $isnew = !$start;
# If an attachment was created, then add an URL. (Note: the 'g'lobal
# replace should work with comments with multiple attachments.)
if ( $newcomments =~ /Created an attachment \(/ ) {
my $showattachurlbase =
Bugzilla->params->{'urlbase'} . "attachment.cgi?id=";
$newcomments =~ s/(Created an attachment \(id=([0-9]+)\))/$1\n --> \(${showattachurlbase}$2\)/g;
}
my $diffs = $difftext . "\n\n" . $newcomments;
if ($isnew) {
$diffs = $head . ($difftext ? "\n\n" : "") . $diffs;
}
my (@reasons, @reasons_watch);
while (my ($relationship, $bits) = each %{$relRef}) {
push(@reasons, $relationship) if ($bits & BIT_DIRECT);
push(@reasons_watch, $relationship) if ($bits & BIT_WATCHING);
}
my @headerrel = map { REL_NAMES->{$_} } @reasons;
my @watchingrel = map { REL_NAMES->{$_} } @reasons_watch;
push(@headerrel, 'None') unless @headerrel;
push(@watchingrel, 'None') unless @watchingrel;
push @watchingrel, map { user_id_to_login($_) } @$watchingRef;
my $sitespec = '@' . Bugzilla->params->{'urlbase'};
$sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
$sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
if ($2) {
$sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
}
my $threadingmarker;
if ($isnew) {
$threadingmarker = "Message-ID: <bug-$id-" . $user->id . "$sitespec>";
} else {
$threadingmarker = "In-Reply-To: <bug-$id-" . $user->id . "$sitespec>" .
"\nReferences: <bug-$id-" . $user->id . "$sitespec>";
}
my $vars = {
neworchanged => $isnew ? 'New: ' : '',
to => $user->email,
bugid => $id,
product => $values{'product'},
comp => $values{'component'},
keywords => $values{'keywords'},
severity => $values{'bug_severity'},
status => $values{'bug_status'},
priority => $values{'priority'},
assignedto => $values{'assigned_to'},
targetmilestone => $values{'target_milestone'},
changedfields => $values{'changed_fields'},
summary => $values{'short_desc'},
reasons => \@reasons,
reasons_watch => \@reasons_watch,
reasonsheader => join(" ", @headerrel),
reasonswatchheader => join(" ", @watchingrel),
changer => $values{'changer'},
changername => $values{'changername'},
diffs => $diffs,
threadingmarker => $threadingmarker
};
my $msg;
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
$template->process("email/newchangedmail.txt.tmpl", $vars, \$msg)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($msg);
return 1;
}
# Get bug comments for the given period.
sub get_comments_by_bug {
my ($id, $start, $end) = @_;
my $dbh = Bugzilla->dbh;
my $result = "";
my $count = 0;
my $anyprivate = 0;
# $start will be undef for new bugs, and defined for pre-existing bugs.
if ($start) {
# If $start is not NULL, obtain the count-index
# of this comment for the leading "Comment #xxx" line.
$count = $dbh->selectrow_array('SELECT COUNT(*) FROM longdescs
WHERE bug_id = ? AND bug_when <= ?',
undef, ($id, $start));
}
my $raw = 1; # Do not format comments which are not of type CMT_NORMAL.
my $comments = Bugzilla::Bug::GetComments($id, "oldest_to_newest", $start, $end, $raw);
if (Bugzilla->params->{'insidergroup'}) {
$anyprivate = 1 if scalar(grep {$_->{'isprivate'} > 0} @$comments);
}
return ($comments, $anyprivate, $count);
}
# Prepare comments for the given language.
sub prepare_comments {
my ($raw_comments, $count) = @_;
my $result = "";
foreach my $comment (@$raw_comments) {
if ($count) {
$result .= "\n\n--- Comment #$count from ";
if ($comment->{'name'} eq $comment->{'email'}) {
$result .= $comment->{'email'};
} else {
$result .= $comment->{'name'} . " <" . $comment->{'email'} . ">";
}
$result .= " " . format_time($comment->{'time'}) . " ---\n";
}
# Format language specific comments. We don't update $comment->{'body'}
# directly, otherwise it would grow everytime you call format_comment()
# with a different language as some text may be appended to the existing one.
my $body = Bugzilla::Bug::format_comment($comment);
$result .= ($comment->{'already_wrapped'} ? $body : wrap_comment($body));
$count++;
}
return $result;
}
1;

View File

@@ -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): Bradley Baetz <bbaetz@student.usyd.edu.au>
# Byron Jones <bugzilla@glob.com.au>
# Marc Schumann <wurblzap@gmail.com>
use strict;
package Bugzilla::CGI;
BEGIN {
if ($^O =~ /MSWin32/i) {
# Help CGI find the correct temp directory as the default list
# isn't Windows friendly (Bug 248988)
$ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
}
}
use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH);
use base qw(CGI);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
# We need to disable output buffering - see bug 179174
$| = 1;
# Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
# their browser window while a script is running, the webserver sends these
# signals, and we don't want to die half way through a write.
$::SIG{TERM} = 'IGNORE';
$::SIG{PIPE} = 'IGNORE';
# CGI.pm uses AUTOLOAD, but explicitly defines a DESTROY sub.
# We need to do so, too, otherwise perl dies when the object is destroyed
# and we don't have a DESTROY method (because CGI.pm's AUTOLOAD will |die|
# on getting an unknown sub to try to call)
sub DESTROY {
my $self = shift;
$self->SUPER::DESTROY(@_);
};
sub new {
my ($invocant, @args) = @_;
my $class = ref($invocant) || $invocant;
my $self = $class->SUPER::new(@args);
if (Bugzilla->error_mode eq ERROR_MODE_WEBPAGE) {
# This happens here so that command-line scripts don't spit out
# their errors in HTML format.
require CGI::Carp;
import CGI::Carp qw(fatalsToBrowser);
}
# Make sure our outgoing cookie list is empty on each invocation
$self->{Bugzilla_cookie_list} = [];
# Send appropriate charset
$self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
# Redirect to SSL if required
if (Bugzilla->params->{'sslbase'} ne ''
&& Bugzilla->params->{'ssl'} eq 'always'
&& i_am_cgi())
{
$self->require_https(Bugzilla->params->{'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) {
# Note that this error block is only triggered by CGI.pm for malformed
# multipart requests, and so should never happen unless there is a
# browser bug.
print $self->header(-status => $err);
# ThrowCodeError wants to print the header, so it grabs Bugzilla->cgi
# which creates a new Bugzilla::CGI object, which fails again, which
# ends up here, and calls ThrowCodeError, and then recurses forever.
# So don't use it.
# In fact, we can't use templates at all, because we need a CGI object
# to determine the template lang as well as the current url (from the
# template)
# Since this is an internal error which indicates a severe browser bug,
# just die.
die "CGI parsing error: $err";
}
return $self;
}
# We want this sorted plus the ability to exclude certain params
sub canonicalise_query {
my ($self, @exclude) = @_;
# Reconstruct the URL by concatenating the sorted param=value pairs
my @parameters;
foreach my $key (sort($self->param())) {
# Leave this key out if it's in the exclude list
next if lsearch(\@exclude, $key) != -1;
my $esc_key = url_quote($key);
foreach my $value ($self->param($key)) {
if (defined($value)) {
my $esc_value = url_quote($value);
push(@parameters, "$esc_key=$esc_value");
}
}
}
return join("&", @parameters);
}
sub clean_search_url {
my $self = shift;
# Delete any empty URL parameter
my @cgi_params = $self->param;
foreach my $param (@cgi_params) {
if (defined $self->param($param) && $self->param($param) eq '') {
$self->delete($param);
$self->delete("${param}_type");
}
# Boolean Chart stuff is empty if it's "noop"
if ($param =~ /\d-\d-\d/ && defined $self->param($param)
&& $self->param($param) eq 'noop')
{
$self->delete($param);
}
}
# Delete certain parameters if the associated parameter is empty.
$self->delete('bugidtype') if !$self->param('bug_id');
$self->delete('emailtype1') if !$self->param('email1');
$self->delete('emailtype2') if !$self->param('email2');
}
# Overwrite to ensure nph doesn't get set, and unset HEADERS_ONCE
sub multipart_init {
my $self = shift;
# Keys are case-insensitive, map to lowercase
my %args = @_;
my %param;
foreach my $key (keys %args) {
$param{lc $key} = $args{$key};
}
# Set the MIME boundary and content-type
my $boundary = $param{'-boundary'} || '------- =_aaaaaaaaaa0';
delete $param{'-boundary'};
$self->{'separator'} = "\r\n--$boundary\r\n";
$self->{'final_separator'} = "\r\n--$boundary--\r\n";
$param{'-type'} = SERVER_PUSH($boundary);
# Note: CGI.pm::multipart_init up to v3.04 explicitly set nph to 0
# CGI.pm::multipart_init v3.05 explicitly sets nph to 1
# CGI.pm's header() sets nph according to a param or $CGI::NPH, which
# is the desired behaviour.
return $self->header(
%param,
) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
}
# Have to add the cookies in.
sub multipart_start {
my $self = shift;
my %args = @_;
# CGI.pm::multipart_start doesn't accept a -charset parameter, so
# we do it ourselves here
if (defined $args{-charset} && defined $args{-type}) {
# Remove any existing charset specifier
$args{-type} =~ s/;.*$//;
# and add the specified one
$args{-type} .= "; charset=$args{-charset}";
}
my $headers = $self->SUPER::multipart_start(%args);
# Eliminate the one extra CRLF at the end.
$headers =~ s/$CGI::CRLF$//;
# Add the cookies. We have to do it this way instead of
# passing them to multpart_start, because CGI.pm's multipart_start
# doesn't understand a '-cookie' argument pointing to an arrayref.
foreach my $cookie (@{$self->{Bugzilla_cookie_list}}) {
$headers .= "Set-Cookie: ${cookie}${CGI::CRLF}";
}
$headers .= $CGI::CRLF;
return $headers;
}
# Override header so we can add the cookies in
sub header {
my $self = shift;
# Add the cookies in if we have any
if (scalar(@{$self->{Bugzilla_cookie_list}})) {
if (scalar(@_) == 1) {
# if there's only one parameter, then it's a Content-Type.
# Since we're adding parameters we have to name it.
unshift(@_, '-type' => shift(@_));
}
unshift(@_, '-cookie' => $self->{Bugzilla_cookie_list});
}
return $self->SUPER::header(@_) || "";
}
# The various parts of Bugzilla which create cookies don't want to have to
# pass them around to all of the callers. Instead, store them locally here,
# and then output as required from |header|.
sub send_cookie {
my $self = shift;
# Move the param list into a hash for easier handling.
my %paramhash;
my @paramlist;
my ($key, $value);
while ($key = shift) {
$value = shift;
$paramhash{$key} = $value;
}
# Complain if -value is not given or empty (bug 268146).
if (!exists($paramhash{'-value'}) || !$paramhash{'-value'}) {
ThrowCodeError('cookies_need_value');
}
# Add the default path and the domain in.
$paramhash{'-path'} = Bugzilla->params->{'cookiepath'};
$paramhash{'-domain'} = Bugzilla->params->{'cookiedomain'}
if Bugzilla->params->{'cookiedomain'};
# Move the param list back into an array for the call to cookie().
foreach (keys(%paramhash)) {
unshift(@paramlist, $_ => $paramhash{$_});
}
push(@{$self->{'Bugzilla_cookie_list'}}, $self->cookie(@paramlist));
}
# Cookies are removed by setting an expiry date in the past.
# This method is a send_cookie wrapper doing exactly this.
sub remove_cookie {
my $self = shift;
my ($cookiename) = (@_);
# Expire the cookie, giving a non-empty dummy value (bug 268146).
$self->send_cookie('-name' => $cookiename,
'-expires' => 'Tue, 15-Sep-1998 21:49:00 GMT',
'-value' => 'X');
}
# Redirect to https if required
sub require_https {
my $self = 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>

View File

@@ -1,447 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
# Albert Ting <altlst@sonic.net>
# A. Karl Kornel <karl@kornel.name>
use strict;
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::Error;
use Bugzilla::Util;
use Bugzilla::Series;
use Date::Format;
use Date::Parse;
use List::Util qw(max);
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
# Create a ref to an empty hash and bless it
my $self = {};
bless($self, $class);
if ($#_ == 0) {
# Construct from a CGI object.
$self->init($_[0]);
}
else {
die("CGI object not passed in - invalid number of args \($#_\)($_)");
}
return $self;
}
sub init {
my $self = shift;
my $cgi = shift;
# The data structure is a list of lists (lines) of Series objects.
# There is a separate list for the labels.
#
# The URL encoding is:
# line0=67&line0=73&line1=81&line2=67...
# &label0=B+/+R+/+NEW&label1=...
# &select0=1&select3=1...
# &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
# &gt=1&labelgt=Grand+Total
foreach my $param ($cgi->param()) {
# Store all the lines
if ($param =~ /^line(\d+)$/) {
foreach my $series_id ($cgi->param($param)) {
detaint_natural($series_id)
|| ThrowCodeError("invalid_series_id");
my $series = new Bugzilla::Series($series_id);
push(@{$self->{'lines'}[$1]}, $series) if $series;
}
}
# Store all the labels
if ($param =~ /^label(\d+)$/) {
$self->{'labels'}[$1] = $cgi->param($param);
}
}
# Store the miscellaneous metadata
$self->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0;
$self->{'gt'} = $cgi->param('gt') ? 1 : 0;
$self->{'labelgt'} = $cgi->param('labelgt');
$self->{'datefrom'} = $cgi->param('datefrom');
$self->{'dateto'} = $cgi->param('dateto');
# If we are cumulating, a grand total makes no sense
$self->{'gt'} = 0 if $self->{'cumulate'};
# Make sure the dates are ones we are able to interpret
foreach my $date ('datefrom', 'dateto') {
if ($self->{$date}) {
$self->{$date} = str2time($self->{$date})
|| ThrowUserError("illegal_date", { date => $self->{$date}});
}
}
# datefrom can't be after dateto
if ($self->{'datefrom'} && $self->{'dateto'} &&
$self->{'datefrom'} > $self->{'dateto'})
{
ThrowUserError("misarranged_dates",
{'datefrom' => $cgi->param('datefrom'),
'dateto' => $cgi->param('dateto')});
}
}
# Alter Chart so that the selected series are added to it.
sub add {
my $self = shift;
my @series_ids = @_;
# Get the current size of the series; required for adding Grand Total later
my $current_size = scalar($self->getSeriesIDs());
# Count the number of added series
my $added = 0;
# Create new Series and push them on to the list of lines.
# Note that new lines have no label; the display template is responsible
# for inventing something sensible.
foreach my $series_id (@series_ids) {
my $series = new Bugzilla::Series($series_id);
if ($series) {
push(@{$self->{'lines'}}, [$series]);
push(@{$self->{'labels'}}, "");
$added++;
}
}
# If we are going from < 2 to >= 2 series, add the Grand Total line.
if (!$self->{'gt'}) {
if ($current_size < 2 &&
$current_size + $added >= 2)
{
$self->{'gt'} = 1;
}
}
}
# Alter Chart so that the selections are removed from it.
sub remove {
my $self = shift;
my @line_ids = @_;
foreach my $line_id (@line_ids) {
if ($line_id == 65536) {
# Magic value - delete Grand Total.
$self->{'gt'} = 0;
}
else {
delete($self->{'lines'}->[$line_id]);
delete($self->{'labels'}->[$line_id]);
}
}
}
# Alter Chart so that the selections are summed.
sub sum {
my $self = shift;
my @line_ids = @_;
# We can't add the Grand Total to things.
@line_ids = grep(!/^65536$/, @line_ids);
# We can't add less than two things.
return if scalar(@line_ids) < 2;
my @series;
my $label = "";
my $biggestlength = 0;
# We rescue the Series objects of all the series involved in the sum.
foreach my $line_id (@line_ids) {
my @line = @{$self->{'lines'}->[$line_id]};
foreach my $series (@line) {
push(@series, $series);
}
# We keep the label that labels the line with the most series.
if (scalar(@line) > $biggestlength) {
$biggestlength = scalar(@line);
$label = $self->{'labels'}->[$line_id];
}
}
$self->remove(@line_ids);
push(@{$self->{'lines'}}, \@series);
push(@{$self->{'labels'}}, $label);
}
sub data {
my $self = shift;
$self->{'_data'} ||= $self->readData();
return $self->{'_data'};
}
# Convert the Chart's data into a plottable form in $self->{'_data'}.
sub readData {
my $self = shift;
my @data;
my @maxvals;
# Note: you get a bad image if getSeriesIDs returns nothing
# We need to handle errors better.
my $series_ids = join(",", $self->getSeriesIDs());
return [] unless $series_ids;
# Work out the date boundaries for our data.
my $dbh = Bugzilla->dbh;
# The date used is the one given if it's in a sensible range; otherwise,
# it's the earliest or latest date in the database as appropriate.
my $datefrom = $dbh->selectrow_array("SELECT MIN(series_date) " .
"FROM series_data " .
"WHERE series_id IN ($series_ids)");
$datefrom = str2time($datefrom);
if ($self->{'datefrom'} && $self->{'datefrom'} > $datefrom) {
$datefrom = $self->{'datefrom'};
}
my $dateto = $dbh->selectrow_array("SELECT MAX(series_date) " .
"FROM series_data " .
"WHERE series_id IN ($series_ids)");
$dateto = str2time($dateto);
if ($self->{'dateto'} && $self->{'dateto'} < $dateto) {
$dateto = $self->{'dateto'};
}
# Convert UNIX times back to a date format usable for SQL queries.
my $sql_from = time2str('%Y-%m-%d', $datefrom);
my $sql_to = time2str('%Y-%m-%d', $dateto);
# Prepare the query which retrieves the data for each series
my $query = "SELECT " . $dbh->sql_to_days('series_date') . " - " .
$dbh->sql_to_days('?') . ", series_value " .
"FROM series_data " .
"WHERE series_id = ? " .
"AND series_date >= ?";
if ($dateto) {
$query .= " AND series_date <= ?";
}
my $sth = $dbh->prepare($query);
my $gt_index = $self->{'gt'} ? scalar(@{$self->{'lines'}}) : undef;
my $line_index = 0;
$maxvals[$gt_index] = 0 if $gt_index;
my @datediff_total;
foreach my $line (@{$self->{'lines'}}) {
# Even if we end up with no data, we need an empty arrayref to prevent
# errors in the PNG-generating code
$data[$line_index] = [];
$maxvals[$line_index] = 0;
foreach my $series (@$line) {
# Get the data for this series and add it on
if ($dateto) {
$sth->execute($sql_from, $series->{'series_id'}, $sql_from, $sql_to);
}
else {
$sth->execute($sql_from, $series->{'series_id'}, $sql_from);
}
my $points = $sth->fetchall_arrayref();
foreach my $point (@$points) {
my ($datediff, $value) = @$point;
$data[$line_index][$datediff] ||= 0;
$data[$line_index][$datediff] += $value;
if ($data[$line_index][$datediff] > $maxvals[$line_index]) {
$maxvals[$line_index] = $data[$line_index][$datediff];
}
$datediff_total[$datediff] += $value;
# Add to the grand total, if we are doing that
if ($gt_index) {
$data[$gt_index][$datediff] ||= 0;
$data[$gt_index][$datediff] += $value;
if ($data[$gt_index][$datediff] > $maxvals[$gt_index]) {
$maxvals[$gt_index] = $data[$gt_index][$datediff];
}
}
}
}
# We are done with the series making up this line, go to the next one
$line_index++;
}
# calculate maximum y value
if ($self->{'cumulate'}) {
# Make sure we do not try to take the max of an array with undef values
my @processed_datediff;
while (@datediff_total) {
my $datediff = shift @datediff_total;
push @processed_datediff, $datediff if defined($datediff);
}
$self->{'y_max_value'} = max(@processed_datediff);
}
else {
$self->{'y_max_value'} = max(@maxvals);
}
$self->{'y_max_value'} |= 1; # For log()
# Align the max y value:
# For one- or two-digit numbers, increase y_max_value until divisible by 8
# For larger numbers, see the comments below to figure out what's going on
if ($self->{'y_max_value'} < 100) {
do {
++$self->{'y_max_value'};
} while ($self->{'y_max_value'} % 8 != 0);
}
else {
# First, get the # of digits in the y_max_value
my $num_digits = 1+int(log($self->{'y_max_value'})/log(10));
# We want to zero out all but the top 2 digits
my $mask_length = $num_digits - 2;
$self->{'y_max_value'} /= 10**$mask_length;
$self->{'y_max_value'} = int($self->{'y_max_value'});
$self->{'y_max_value'} *= 10**$mask_length;
# Add 10^$mask_length to the max value
# Continue to increase until it's divisible by 8 * 10^($mask_length-1)
# (Throwing in the -1 keeps at least the smallest digit at zero)
do {
$self->{'y_max_value'} += 10**$mask_length;
} while ($self->{'y_max_value'} % (8*(10**($mask_length-1))) != 0);
}
# Add the x-axis labels into the data structure
my $date_progression = generateDateProgression($datefrom, $dateto);
unshift(@data, $date_progression);
if ($self->{'gt'}) {
# Add Grand Total to label list
push(@{$self->{'labels'}}, $self->{'labelgt'});
$data[$gt_index] ||= [];
}
return \@data;
}
# Flatten the data structure into a list of series_ids
sub getSeriesIDs {
my $self = shift;
my @series_ids;
foreach my $line (@{$self->{'lines'}}) {
foreach my $series (@$line) {
push(@series_ids, $series->{'series_id'});
}
}
return @series_ids;
}
# Class method to get the data necessary to populate the "select series"
# widgets on various pages.
sub getVisibleSeries {
my %cats;
# List of groups the user is in; use -1 to make sure it's not empty.
my $grouplist = join(", ", (-1, values(%{Bugzilla->user->groups})));
# Get all visible series
my $dbh = Bugzilla->dbh;
my $serieses = $dbh->selectall_arrayref("SELECT cc1.name, cc2.name, " .
"series.name, series.series_id " .
"FROM series " .
"INNER JOIN series_categories AS cc1 " .
" ON series.category = cc1.id " .
"INNER JOIN series_categories AS cc2 " .
" ON series.subcategory = cc2.id " .
"LEFT JOIN category_group_map AS cgm " .
" ON series.category = cgm.category_id " .
" AND cgm.group_id NOT IN($grouplist) " .
"WHERE creator = " . Bugzilla->user->id . " OR " .
" cgm.category_id IS NULL " .
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
'series.name'));
foreach my $series (@$serieses) {
my ($cat, $subcat, $name, $series_id) = @$series;
$cats{$cat}{$subcat}{$name} = $series_id;
}
return \%cats;
}
sub generateDateProgression {
my ($datefrom, $dateto) = @_;
my @progression;
$dateto = $dateto || time();
my $oneday = 60 * 60 * 24;
# When the from and to dates are converted by str2time(), you end up with
# a time figure representing midnight at the beginning of that day. We
# adjust the times by 1/3 and 2/3 of a day respectively to prevent
# edge conditions in time2str().
$datefrom += $oneday / 3;
$dateto += (2 * $oneday) / 3;
while ($datefrom < $dateto) {
push (@progression, time2str("%Y-%m-%d", $datefrom));
$datefrom += $oneday;
}
return \@progression;
}
sub dump {
my $self = shift;
# Make sure we've read in our data
my $data = $self->data;
require Data::Dumper;
print "<pre>Bugzilla::Chart object:\n";
print Data::Dumper::Dumper($self);
print "</pre>";
}
1;

View File

@@ -1,252 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
#
use strict;
package Bugzilla::Classification;
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::Product;
###############################
#### Initialization ####
###############################
use constant DB_COLUMNS => qw(
classifications.id
classifications.name
classifications.description
classifications.sortkey
);
our $columns = join(", ", DB_COLUMNS);
###############################
#### Methods ####
###############################
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $self = {};
bless($self, $class);
return $self->_init(@_);
}
sub _init {
my $self = shift;
my ($param) = @_;
my $dbh = Bugzilla->dbh;
my $id = $param unless (ref $param eq 'HASH');
my $classification;
if (defined $id) {
detaint_natural($id)
|| ThrowCodeError('param_must_be_numeric',
{function => 'Bugzilla::Classification::_init'});
$classification = $dbh->selectrow_hashref(qq{
SELECT $columns FROM classifications
WHERE id = ?}, undef, $id);
} elsif (defined $param->{'name'}) {
trick_taint($param->{'name'});
$classification = $dbh->selectrow_hashref(qq{
SELECT $columns FROM classifications
WHERE name = ?}, undef, $param->{'name'});
} else {
ThrowCodeError('bad_arg',
{argument => 'param',
function => 'Bugzilla::Classification::_init'});
}
return undef unless (defined $classification);
foreach my $field (keys %$classification) {
$self->{$field} = $classification->{$field};
}
return $self;
}
sub product_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'product_count'}) {
$self->{'product_count'} = $dbh->selectrow_array(q{
SELECT COUNT(*) FROM products
WHERE classification_id = ?}, undef, $self->id) || 0;
}
return $self->{'product_count'};
}
sub products {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!$self->{'products'}) {
my $product_ids = $dbh->selectcol_arrayref(q{
SELECT id FROM products
WHERE classification_id = ?
ORDER BY name}, undef, $self->id);
$self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
}
return $self->{'products'};
}
###############################
#### Accessors ####
###############################
sub id { return $_[0]->{'id'}; }
sub name { return $_[0]->{'name'}; }
sub description { return $_[0]->{'description'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
###############################
#### Subroutines ####
###############################
sub get_all_classifications {
my $dbh = Bugzilla->dbh;
my $ids = $dbh->selectcol_arrayref(q{
SELECT id FROM classifications ORDER BY sortkey, name});
my @classifications;
foreach my $id (@$ids) {
push @classifications, new Bugzilla::Classification($id);
}
return @classifications;
}
sub check_classification {
my ($class_name) = @_;
unless ($class_name) {
ThrowUserError("classification_not_specified");
}
my $classification =
new Bugzilla::Classification({name => $class_name});
unless ($classification) {
ThrowUserError("classification_doesnt_exist",
{ name => $class_name });
}
return $classification;
}
1;
__END__
=head1 NAME
Bugzilla::Classification - Bugzilla classification class.
=head1 SYNOPSIS
use Bugzilla::Classification;
my $classification = new Bugzilla::Classification(1);
my $classification = new Bugzilla::Classification({name => 'Acme'});
my $id = $classification->id;
my $name = $classification->name;
my $description = $classification->description;
my $product_count = $classification->product_count;
my $products = $classification->products;
my $hash_ref = Bugzilla::Classification::get_all_classifications();
my $classification = $hash_ref->{1};
my $classification =
Bugzilla::Classification::check_classification('AcmeClass');
=head1 DESCRIPTION
Classification.pm represents a Classification object.
A Classification is a higher-level grouping of Products.
=head1 METHODS
=over
=item C<new($param)>
Description: The constructor is used to load an existing
classification by passing a classification
id or classification name using a hash.
Params: $param - If you pass an integer, the integer is the
classification_id from the database that we
want to read in. If you pass in a hash with
'name' key, then the value of the name key
is the name of a classification from the DB.
Returns: A Bugzilla::Classification object.
=item C<product_count()>
Description: Returns the total number of products that belong to
the classification.
Params: none.
Returns: Integer - The total of products inside the classification.
=item C<products>
Description: Returns all products of the classification.
Params: none.
Returns: A reference to an array of Bugzilla::Product objects.
=back
=head1 SUBROUTINES
=over
=item C<get_all_classifications()>
Description: Returns all classifications.
Params: none.
Returns: Bugzilla::Classification object list.
=item C<check_classification($classification_name)>
Description: Checks if the classification name passed in is a
valid classification.
Params: $classification_name - String with a classification name.
Returns: Bugzilla::Classification object.
=back
=cut

View File

@@ -1,329 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
# Frédéric Buclin <LpSolit@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Akamai Technologies <bugzilla-dev@akamai.com>
use strict;
package Bugzilla::Component;
use base qw(Bugzilla::Object);
use Bugzilla::Util;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::FlagType;
###############################
#### Initialization ####
###############################
use constant DB_TABLE => 'components';
use constant DB_COLUMNS => qw(
id
name
product_id
initialowner
initialqacontact
description
);
###############################
#### Methods ####
###############################
sub new {
my $class = shift;
my $param = shift;
my $dbh = Bugzilla->dbh;
my $product;
if (ref $param) {
$product = $param->{product};
my $name = $param->{name};
if (!defined $product) {
ThrowCodeError('bad_arg',
{argument => 'product',
function => "${class}::new"});
}
if (!defined $name) {
ThrowCodeError('bad_arg',
{argument => 'name',
function => "${class}::new"});
}
my $condition = 'product_id = ? AND name = ?';
my @values = ($product->id, $name);
$param = { condition => $condition, values => \@values };
}
unshift @_, $param;
my $component = $class->SUPER::new(@_);
# Add the product object as attribute only if the component exists.
$component->{product} = $product if ($component && $product);
return $component;
}
sub bug_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'bug_count'}) {
$self->{'bug_count'} = $dbh->selectrow_array(q{
SELECT COUNT(*) FROM bugs
WHERE component_id = ?}, undef, $self->id) || 0;
}
return $self->{'bug_count'};
}
sub bug_ids {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'bugs_ids'}) {
$self->{'bugs_ids'} = $dbh->selectcol_arrayref(q{
SELECT bug_id FROM bugs
WHERE component_id = ?}, undef, $self->id);
}
return $self->{'bugs_ids'};
}
sub default_assignee {
my $self = shift;
if (!defined $self->{'default_assignee'}) {
$self->{'default_assignee'} =
new Bugzilla::User($self->{'initialowner'});
}
return $self->{'default_assignee'};
}
sub default_qa_contact {
my $self = shift;
if (!defined $self->{'default_qa_contact'}) {
$self->{'default_qa_contact'} =
new Bugzilla::User($self->{'initialqacontact'});
}
return $self->{'default_qa_contact'};
}
sub flag_types {
my $self = shift;
if (!defined $self->{'flag_types'}) {
$self->{'flag_types'} = {};
$self->{'flag_types'}->{'bug'} =
Bugzilla::FlagType::match({ 'target_type' => 'bug',
'product_id' => $self->product_id,
'component_id' => $self->id });
$self->{'flag_types'}->{'attachment'} =
Bugzilla::FlagType::match({ 'target_type' => 'attachment',
'product_id' => $self->product_id,
'component_id' => $self->id });
}
return $self->{'flag_types'};
}
sub initial_cc {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'initial_cc'}) {
my $cc_ids = $dbh->selectcol_arrayref(
"SELECT user_id FROM component_cc WHERE component_id = ?",
undef, $self->id);
my $initial_cc = Bugzilla::User->new_from_list($cc_ids);
$self->{'initial_cc'} = $initial_cc;
}
return $self->{'initial_cc'};
}
sub product {
my $self = shift;
if (!defined $self->{'product'}) {
require Bugzilla::Product; # We cannot |use| it.
$self->{'product'} = new Bugzilla::Product($self->product_id);
}
return $self->{'product'};
}
###############################
#### Accessors ####
###############################
sub id { return $_[0]->{'id'}; }
sub name { return $_[0]->{'name'}; }
sub description { return $_[0]->{'description'}; }
sub product_id { return $_[0]->{'product_id'}; }
###############################
#### Subroutines ####
###############################
sub check_component {
my ($product, $comp_name) = @_;
$comp_name || ThrowUserError('component_blank_name');
if (length($comp_name) > 64) {
ThrowUserError('component_name_too_long',
{'name' => $comp_name});
}
my $component =
new Bugzilla::Component({product => $product,
name => $comp_name});
unless ($component) {
ThrowUserError('component_not_valid',
{'product' => $product->name,
'name' => $comp_name});
}
return $component;
}
1;
__END__
=head1 NAME
Bugzilla::Component - Bugzilla product component class.
=head1 SYNOPSIS
use Bugzilla::Component;
my $component = new Bugzilla::Component(1);
my $component = new Bugzilla::Component({product => $product,
name => 'AcmeComp'});
my $bug_count = $component->bug_count();
my $bug_ids = $component->bug_ids();
my $id = $component->id;
my $name = $component->name;
my $description = $component->description;
my $product_id = $component->product_id;
my $default_assignee = $component->default_assignee;
my $default_qa_contact = $component->default_qa_contact;
my $initial_cc = $component->initial_cc;
my $product = $component->product;
my $bug_flag_types = $component->flag_types->{'bug'};
my $attach_flag_types = $component->flag_types->{'attachment'};
my $component = Bugzilla::Component::check_component($product, 'AcmeComp');
=head1 DESCRIPTION
Component.pm represents a Product Component object.
=head1 METHODS
=over
=item C<new($param)>
Description: The constructor is used to load an existing component
by passing a component id or a hash with the product
id and the component name.
Params: $param - If you pass an integer, the integer is the
component id from the database that we want to
read in. If you pass in a hash with 'name' key,
then the value of the name key is the name of a
component from the DB.
Returns: A Bugzilla::Component object.
=item C<bug_count()>
Description: Returns the total of bugs that belong to the component.
Params: none.
Returns: Integer with the number of bugs.
=item C<bugs_ids()>
Description: Returns all bug IDs that belong to the component.
Params: none.
Returns: A reference to an array of bug IDs.
=item C<default_assignee()>
Description: Returns a user object that represents the default assignee for
the component.
Params: none.
Returns: A Bugzilla::User object.
=item C<default_qa_contact()>
Description: Returns a user object that represents the default QA contact for
the component.
Params: none.
Returns: A Bugzilla::User object.
=item C<initial_cc>
Returns an arrayref of L<Bugzilla::User> objects representing the
Initial CC List.
=item C<flag_types()>
Description: Returns all bug and attachment flagtypes available for
the component.
Params: none.
Returns: Two references to an array of flagtype objects.
=item C<product()>
Description: Returns the product the component belongs to.
Params: none.
Returns: A Bugzilla::Product object.
=back
=head1 SUBROUTINES
=over
=item C<check_component($product, $comp_name)>
Description: Checks if the component name was passed in and if it is a valid
component.
Params: $product - A Bugzilla::Product object.
$comp_name - String with a component name.
Returns: Bugzilla::Component object.
=back
=cut

View File

@@ -1,397 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jake <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Christopher Aillon <christopher@aillon.com>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
package Bugzilla::Config;
use strict;
use base qw(Exporter);
use Bugzilla::Constants;
use Data::Dumper;
use File::Temp;
# Don't export localvars by default - people should have to explicitly
# ask for it, as a (probably futile) attempt to stop code using it
# when it shouldn't
%Bugzilla::Config::EXPORT_TAGS =
(
admin => [qw(update_params SetParam write_params)],
);
Exporter::export_ok_tags('admin');
use vars qw(@param_list);
# INITIALISATION CODE
# Perl throws a warning if we use bz_locations() directly after do.
our %params;
# Load in the param definitions
sub _load_params {
foreach my $module (param_panels()) {
eval("require Bugzilla::Config::$module") || die $@;
my @new_param_list = "Bugzilla::Config::$module"->get_param_list();
foreach my $item (@new_param_list) {
$params{$item->{'name'}} = $item;
}
push(@param_list, @new_param_list);
}
}
# END INIT CODE
# Subroutines go here
sub param_panels {
my @param_panels;
my $libpath = bz_locations()->{'libpath'};
foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
$item =~ m#/([^/]+)\.pm$#;
my $module = $1;
push(@param_panels, $module) unless $module eq 'Common';
}
return @param_panels;
}
sub SetParam {
my ($name, $value) = @_;
_load_params unless %params;
die "Unknown param $name" unless (exists $params{$name});
my $entry = $params{$name};
# sanity check the value
# XXX - This runs the checks. Which would be good, except that
# check_shadowdb creates the database as a side effect, and so the
# checker fails the second time around...
if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
my $err = $entry->{'checker'}->($value, $entry);
die "Param $name is not valid: $err" unless $err eq '';
}
Bugzilla->params->{$name} = $value;
}
sub update_params {
my ($params) = @_;
my $answer = Bugzilla->installation_answers;
my $param = read_param_file();
# If we didn't return any param values, then this is a new installation.
my $new_install = !(keys %$param);
# --- UPDATE OLD PARAMS ---
# Old Bugzilla versions stored the version number in the params file
# We don't want it, so get rid of it
delete $param->{'version'};
# Change from usebrowserinfo to defaultplatform/defaultopsys combo
if (exists $param->{'usebrowserinfo'}) {
if (!$param->{'usebrowserinfo'}) {
if (!exists $param->{'defaultplatform'}) {
$param->{'defaultplatform'} = 'Other';
}
if (!exists $param->{'defaultopsys'}) {
$param->{'defaultopsys'} = 'Other';
}
}
delete $param->{'usebrowserinfo'};
}
# Change from a boolean for quips to multi-state
if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
$param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
delete $param->{'usequip'};
}
# Change from old product groups to controls for group_control_map
# 2002-10-14 bug 147275 bugreport@peshkin.net
if (exists $param->{'usebuggroups'} &&
!exists $param->{'makeproductgroups'})
{
$param->{'makeproductgroups'} = $param->{'usebuggroups'};
}
if (exists $param->{'usebuggroupsentry'}
&& !exists $param->{'useentrygroupdefault'}) {
$param->{'useentrygroupdefault'} = $param->{'usebuggroupsentry'};
}
# Modularise auth code
if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
$param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
}
# set verify method to whatever loginmethod was
if (exists $param->{'loginmethod'}
&& !exists $param->{'user_verify_class'})
{
$param->{'user_verify_class'} = $param->{'loginmethod'};
delete $param->{'loginmethod'};
}
# Remove quip-display control from parameters
# and give it to users via User Settings (Bug 41972)
if ( exists $param->{'enablequips'}
&& !exists $param->{'quip_list_entry_control'})
{
my $new_value;
($param->{'enablequips'} eq 'on') && do {$new_value = 'open';};
($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
($param->{'enablequips'} eq 'frozen') && do {$new_value = 'closed';};
($param->{'enablequips'} eq 'off') && do {$new_value = 'closed';};
$param->{'quip_list_entry_control'} = $new_value;
delete $param->{'enablequips'};
}
# Old mail_delivery_method choices contained no uppercase characters
if (exists $param->{'mail_delivery_method'}
&& $param->{'mail_delivery_method'} !~ /[A-Z]/) {
my $method = $param->{'mail_delivery_method'};
my %translation = (
'sendmail' => 'Sendmail',
'smtp' => 'SMTP',
'qmail' => 'Qmail',
'testfile' => 'Test',
'none' => 'None');
$param->{'mail_delivery_method'} = $translation{$method};
}
# --- DEFAULTS FOR NEW PARAMS ---
_load_params unless %params;
foreach my $item (@param_list) {
my $name = $item->{'name'};
unless (exists $param->{$name}) {
print "New parameter: $name\n" unless $new_install;
$param->{$name} = $answer->{$name} || $item->{'default'};
}
}
$param->{'utf8'} = 1 if $new_install;
# --- REMOVE OLD PARAMS ---
my @oldparams;
# Remove any old params, put them in old-params.txt
foreach my $item (keys %$param) {
if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Indent = 0;
push (@oldparams, [$item, Data::Dumper->Dump([$param->{$item}])]);
delete $param->{$item};
}
}
if (@oldparams) {
my $op_file = new IO::File('old-params.txt', '>>', 0600)
|| die "old-params.txt: $!";
print "The following parameters are no longer used in Bugzilla,",
" and so have been\nmoved from your parameters file into",
" old-params.txt:\n";
foreach my $p (@oldparams) {
my ($item, $value) = @$p;
print $op_file "\n\n$item:\n$value\n";
print $item;
print ", " unless $item eq $oldparams[$#oldparams]->[0];
}
print "\n";
$op_file->close;
}
if (ON_WINDOWS && !-e SENDMAIL_EXE
&& $param->{'mail_delivery_method'} eq 'Sendmail')
{
my $smtp = $answer->{'SMTP_SERVER'};
if (!$smtp) {
print "\nBugzilla requires an SMTP server to function on",
" Windows.\nPlease enter your SMTP server's hostname: ";
$smtp = <STDIN>;
chomp $smtp;
if ($smtp) {
$param->{'smtpserver'} = $smtp;
}
else {
print "\nWarning: No SMTP Server provided, defaulting to",
" localhost\n";
}
}
$param->{'mail_delivery_method'} = 'SMTP';
}
write_params($param);
}
sub write_params {
my ($param_data) = @_;
$param_data ||= Bugzilla->params;
my $datadir = bz_locations()->{'datadir'};
my $param_file = "$datadir/params";
# 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;
my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
DIR => $datadir );
print $fh (Data::Dumper->Dump([$param_data], ['*param']))
|| die "Can't write param file: $!";
close $fh;
rename $tmpname, $param_file
|| die "Can't rename $tmpname to $param_file: $!";
ChmodDataFile($param_file, 0666);
# And now we have to reset the params cache so that Bugzilla will re-read
# them.
delete Bugzilla->request_cache->{params};
}
# Some files in the data directory must be world readable if and only if
# we don't have a webserver group. Call this function to do this.
# This will become a private function once all the datafile handling stuff
# moves into this package
# This sub is not perldoc'd for that reason - noone should know about it
sub ChmodDataFile {
my ($file, $mask) = @_;
my $perm = 0770;
if ((stat(bz_locations()->{'datadir'}))[2] & 0002) {
$perm = 0777;
}
$perm = $perm & $mask;
chmod $perm,$file;
}
sub read_param_file {
my %params;
my $datadir = bz_locations()->{'datadir'};
if (-e "$datadir/params") {
# Note that checksetup.pl sets file permissions on '$datadir/params'
# Using Safe mode is _not_ a guarantee of safety if someone does
# manage to write to the file. However, it won't hurt...
# See bug 165144 for not needing to eval this at all
my $s = new Safe;
$s->rdo("$datadir/params");
die "Error reading $datadir/params: $!" if $!;
die "Error evaluating $datadir/params: $@" if $@;
# Now read the param back out from the sandbox
%params = %{$s->varglob('param')};
}
elsif ($ENV{'SERVER_SOFTWARE'}) {
# We're in a CGI, but the params file doesn't exist. We can't
# Template Toolkit, or even install_string, since checksetup
# might not have thrown an error. Bugzilla::CGI->new
# hasn't even been called yet, so we manually use CGI::Carp here
# so that the user sees the error.
require CGI::Carp;
CGI::Carp->import('fatalsToBrowser');
die "The $datadir/params file does not exist."
. ' You probably need to run checksetup.pl.',
}
return \%params;
}
1;
__END__
=head1 NAME
Bugzilla::Config - Configuration parameters for Bugzilla
=head1 SYNOPSIS
# Administration functions
use Bugzilla::Config qw(:admin);
update_params();
SetParam($param, $value);
write_params();
=head1 DESCRIPTION
This package contains ways to access Bugzilla configuration parameters.
=head1 FUNCTIONS
=head2 Parameters
Parameters can be set, retrieved, and updated.
=over 4
=item C<SetParam($name, $value)>
Sets the param named $name to $value. Values are checked using the checker
function for the given param if one exists.
=item C<update_params()>
Updates the parameters, by transitioning old params to new formats, setting
defaults for new params, and removing obsolete ones. Used by F<checksetup.pl>
in the process of an installation or upgrade.
Prints out information about what it's doing, if it makes any changes.
May prompt the user for input, if certain required parameters are not
specified.
=item C<write_params($params)>
Description: Writes the parameters to disk.
Params: C<$params> (optional) - A hashref to write to the disk
instead of C<Bugzilla->params>. Used only by
C<update_params>.
Returns: nothing
=item C<read_param_file()>
Description: Most callers should never need this. This is used
by C<Bugzilla->params> to directly read C<$datadir/params>
and load it into memory. Use C<Bugzilla->params> instead.
Params: none
Returns: A hashref containing the current params in C<$datadir/params>.
=back

View File

@@ -1,69 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::Admin;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::Admin::sortkey = "01";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'allowbugdeletion',
type => 'b',
default => 0
},
{
name => 'allowemailchange',
type => 'b',
default => 0
},
{
name => 'allowuserdeletion',
type => 'b',
default => 0
},
{
name => 'supportwatchers',
type => 'b',
default => 0
} );
return @param_list;
}
1;

View File

@@ -1,91 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::Attachment;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::Attachment::sortkey = "025";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'allow_attachment_deletion',
type => 'b',
default => 0
},
{
name => 'allow_attach_url',
type => 'b',
default => 0
},
{
name => 'maxpatchsize',
type => 't',
default => '1000',
checker => \&check_numeric
},
{
name => 'maxattachmentsize',
type => 't',
default => '1000',
checker => \&check_numeric
},
# The maximum size (in bytes) for patches and non-patch attachments.
# The default limit is 1000KB, which is 24KB less than mysql's default
# maximum packet size (which determines how much data can be sent in a
# single mysql packet and thus how much data can be inserted into the
# database) to provide breathing space for the data in other fields of
# the attachment record as well as any mysql packet overhead (I don't
# know of any, but I suspect there may be some.)
{
name => 'maxlocalattachment',
type => 't',
default => '0',
checker => \&check_numeric
},
{
name => 'convert_uncompressed_images',
type => 'b',
default => 0,
checker => \&check_image_converter
} );
return @param_list;
}
1;

View File

@@ -1,135 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::Auth;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::Auth::sortkey = "02";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'auth_env_id',
type => 't',
default => '',
},
{
name => 'auth_env_email',
type => 't',
default => '',
},
{
name => 'auth_env_realname',
type => 't',
default => '',
},
# XXX in the future:
#
# user_verify_class and user_info_class should have choices gathered from
# whatever sits in their respective directories
#
# rather than comma-separated lists, these two should eventually become
# arrays, but that requires alterations to editparams first
{
name => 'user_info_class',
type => 's',
choices => [ 'CGI', 'Env', 'Env,CGI', 'ROSCMS' ],
default => 'CGI',
checker => \&check_multi
},
{
name => 'user_verify_class',
type => 's',
choices => [ 'DB', 'LDAP', 'DB,LDAP', 'LDAP,DB', 'ROSCMS' ],
default => 'DB',
checker => \&check_user_verify_class
},
{
name => 'rememberlogin',
type => 's',
choices => ['on', 'defaulton', 'defaultoff', 'off'],
default => 'on',
checker => \&check_multi
},
{
name => 'loginnetmask',
type => 't',
default => '0',
checker => \&check_netmask
},
{
name => 'requirelogin',
type => 'b',
default => '0'
},
{
name => 'emailregexp',
type => 't',
default => q:^[\\w\\.\\+\\-=]+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
checker => \&check_regexp
},
{
name => 'emailregexpdesc',
type => 'l',
default => 'A legal address must contain exactly one \'@\', and at least ' .
'one \'.\' after the @.'
},
{
name => 'emailsuffix',
type => 't',
default => ''
},
{
name => 'createemailregexp',
type => 't',
default => q:.*:,
checker => \&check_regexp
} );
return @param_list;
}
1;

View File

@@ -1,135 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::BugChange;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::BugChange::sortkey = "03";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'letsubmitterchoosepriority',
type => 'b',
default => 1
},
{
name => 'letsubmitterchoosemilestone',
type => 'b',
default => 1
},
{
name => 'musthavemilestoneonaccept',
type => 'b',
default => 0
},
{
name => 'commentoncreate',
type => 'b',
default => 0
},
{
name => 'commentonaccept',
type => 'b',
default => 0
},
{
name => 'commentonclearresolution',
type => 'b',
default => 0
},
{
name => 'commentonconfirm',
type => 'b',
default => 0
},
{
name => 'commentonresolve',
type => 'b',
default => 0
},
{
name => 'commentonreassign',
type => 'b',
default => 0
},
{
name => 'commentonreassignbycomponent',
type => 'b',
default => 0
},
{
name => 'commentonreopen',
type => 'b',
default => 0
},
{
name => 'commentonverify',
type => 'b',
default => 0
},
{
name => 'commentonclose',
type => 'b',
default => 0
},
{
name => 'commentonduplicate',
type => 'b',
default => 0
},
{
name => 'noresolveonopenblockers',
type => 'b',
default => 0,
} );
return @param_list;
}
1;

View File

@@ -1,126 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::BugFields;
use strict;
use Bugzilla::Config::Common;
use Bugzilla::Field;
$Bugzilla::Config::BugFields::sortkey = "04";
sub get_param_list {
my $class = shift;
my @legal_priorities = @{get_legal_field_values('priority')};
my @legal_severities = @{get_legal_field_values('bug_severity')};
my @legal_platforms = @{get_legal_field_values('rep_platform')};
my @legal_OS = @{get_legal_field_values('op_sys')};
my @param_list = (
{
name => 'useclassification',
type => 'b',
default => 0
},
{
name => 'showallproducts',
type => 'b',
default => 0
},
{
name => 'usetargetmilestone',
type => 'b',
default => 0
},
{
name => 'useqacontact',
type => 'b',
default => 0
},
{
name => 'usestatuswhiteboard',
type => 'b',
default => 0
},
{
name => 'usevotes',
type => 'b',
default => 1
},
{
name => 'usebugaliases',
type => 'b',
default => 0
},
{
name => 'defaultpriority',
type => 's',
choices => \@legal_priorities,
default => $legal_priorities[-1],
checker => \&check_priority
},
{
name => 'defaultseverity',
type => 's',
choices => \@legal_severities,
default => $legal_severities[-1],
checker => \&check_severity
},
{
name => 'defaultplatform',
type => 's',
choices => ['', @legal_platforms],
default => '',
checker => \&check_platform
},
{
name => 'defaultopsys',
type => 's',
choices => ['', @legal_OS],
default => '',
checker => \&check_opsys
} );
return @param_list;
}
1;

View File

@@ -1,93 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::BugMove;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::BugMove::sortkey = "05";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'move-enabled',
type => 'b',
default => 0
},
{
name => 'move-button-text',
type => 't',
default => 'Move To Bugscape'
},
{
name => 'move-to-url',
type => 't',
default => ''
},
{
name => 'move-to-address',
type => 't',
default => 'bugzilla-import'
},
{
name => 'moved-from-address',
type => 't',
default => 'bugzilla-admin'
},
{
name => 'movers',
type => 't',
default => ''
},
{
name => 'moved-default-product',
type => 't',
default => ''
},
{
name => 'moved-default-component',
type => 't',
default => ''
} );
return @param_list;
}
1;

View File

@@ -1,437 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::Common;
use strict;
use Socket;
use Time::Zone;
use Bugzilla::Util;
use Bugzilla::Constants;
use Bugzilla::Field;
use Bugzilla::Group;
use base qw(Exporter);
@Bugzilla::Config::Common::EXPORT =
qw(check_multi check_numeric check_regexp check_url check_group
check_sslbase check_priority check_severity check_platform
check_opsys check_shadowdb check_urlbase check_webdotbase
check_netmask check_user_verify_class check_image_converter
check_languages check_mail_delivery_method check_notification
check_timezone check_utf8
);
# Checking functions for the various values
sub check_multi {
my ($value, $param) = (@_);
if ($param->{'type'} eq "s") {
unless (scalar(grep {$_ eq $value} (@{$param->{'choices'}}))) {
return "Invalid choice '$value' for single-select list param '$param->{'name'}'";
}
return "";
}
elsif ($param->{'type'} eq "m") {
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 $@;
}
sub check_sslbase {
my $url = shift;
if ($url ne '') {
if ($url !~ m#^https://([^/]+).*/$#) {
return "must be a legal URL, that starts with https and ends with a slash.";
}
my $host = $1;
if ($host =~ /:\d+$/) {
return "must not contain a port.";
}
local *SOCK;
my $proto = getprotobyname('tcp');
socket(SOCK, PF_INET, SOCK_STREAM, $proto);
my $sin = sockaddr_in(443, inet_aton($host));
if (!connect(SOCK, $sin)) {
return "Failed to connect to " . html_quote($host) .
":443, unable to enable SSL.";
}
}
return "";
}
sub check_utf8 {
my $utf8 = shift;
# You cannot turn off the UTF-8 parameter if you've already converted
# your tables to utf-8.
my $dbh = Bugzilla->dbh;
if ($dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8) {
return "You cannot disable UTF-8 support, because your MySQL database"
. " is encoded in UTF-8";
}
return "";
}
sub check_priority {
my ($value) = (@_);
my $legal_priorities = get_legal_field_values('priority');
if (lsearch($legal_priorities, $value) < 0) {
return "Must be a legal priority value: one of " .
join(", ", @$legal_priorities);
}
return "";
}
sub check_severity {
my ($value) = (@_);
my $legal_severities = get_legal_field_values('bug_severity');
if (lsearch($legal_severities, $value) < 0) {
return "Must be a legal severity value: one of " .
join(", ", @$legal_severities);
}
return "";
}
sub check_platform {
my ($value) = (@_);
my $legal_platforms = get_legal_field_values('rep_platform');
if (lsearch(['', @$legal_platforms], $value) < 0) {
return "Must be empty or a legal platform value: one of " .
join(", ", @$legal_platforms);
}
return "";
}
sub check_opsys {
my ($value) = (@_);
my $legal_OS = get_legal_field_values('op_sys');
if (lsearch(['', @$legal_OS], $value) < 0) {
return "Must be empty or a legal operating system value: one of " .
join(", ", @$legal_OS);
}
return "";
}
sub check_group {
my $group_name = shift;
return "" unless $group_name;
my $group = new Bugzilla::Group({'name' => $group_name});
unless (defined $group) {
return "Must be an existing group name";
}
return "";
}
sub check_shadowdb {
my ($value) = (@_);
$value = trim($value);
if ($value eq "") {
return "";
}
if (!Bugzilla->params->{'shadowdbhost'}) {
return "You need to specify a host when using a shadow database";
}
# Can't test existence of this because ConnectToDatabase uses the param,
# but we can't set this before testing....
# This can really only be fixed after we can use the DBI more openly
return "";
}
sub check_urlbase {
my ($url) = (@_);
if ($url && $url !~ m:^http.*/$:) {
return "must be a legal URL, that starts with http and ends with a slash.";
}
return "";
}
sub check_url {
my ($url) = (@_);
return '' if $url eq ''; # Allow empty URLs
if ($url !~ m:/$:) {
return 'must be a legal URL, absolute or relative, ending with a slash.';
}
return '';
}
sub check_webdotbase {
my ($value) = (@_);
$value = trim($value);
if ($value eq "") {
return "";
}
if($value !~ /^https?:/) {
if(! -x $value) {
return "The file path \"$value\" is not a valid executable. Please specify the complete file path to 'dot' if you intend to generate graphs locally.";
}
# Check .htaccess allows access to generated images
my $webdotdir = bz_locations()->{'webdotdir'};
if(-e "$webdotdir/.htaccess") {
open HTACCESS, "$webdotdir/.htaccess";
if(! grep(/ \\\.png\$/,<HTACCESS>)) {
return "Dependency graph images are not accessible.\nAssuming that you have not modified the file, delete $webdotdir/.htaccess and re-run checksetup.pl to rectify.\n";
}
close HTACCESS;
}
}
return "";
}
sub check_netmask {
my ($mask) = @_;
my $res = check_numeric($mask);
return $res if $res;
if ($mask < 0 || $mask > 32) {
return "an IPv4 netmask must be between 0 and 32 bits";
}
# Note that if we changed the netmask from anything apart from 32, then
# existing logincookies which aren't for a single IP won't work
# any more. We can't know which ones they are, though, so they'll just
# take space until they're periodically cleared, later.
return "";
}
sub check_user_verify_class {
# doeditparams traverses the list of params, and for each one it checks,
# then updates. This means that if one param checker wants to look at
# other params, it must be below that other one. So you can't have two
# params mutually dependent on each other.
# This means that if someone clears the LDAP config params after setting
# the login method as LDAP, we won't notice, but all logins will fail.
# So don't do that.
my ($list, $entry) = @_;
for my $class (split /,\s*/, $list) {
my $res = check_multi($class, $entry);
return $res if $res;
if ($class eq 'DB') {
# No params
} elsif ($class eq 'LDAP') {
eval "require Net::LDAP";
return "Error requiring Net::LDAP: '$@'" if $@;
return "LDAP servername is missing" unless Bugzilla->params->{"LDAPserver"};
return "LDAPBaseDN is empty" unless Bugzilla->params->{"LDAPBaseDN"};
} elsif ($class eq 'ROSCMS') {
# No params
} else {
return "Unknown user_verify_class '$class' in check_user_verify_class";
}
}
return "";
}
sub check_image_converter {
my ($value, $hash) = @_;
if ($value == 1){
eval "require Image::Magick";
return "Error requiring Image::Magick: '$@'" if $@;
}
return "";
}
sub check_languages {
my ($lang, $param) = @_;
my @languages = split(/[,\s]+/, trim($lang));
if(!scalar(@languages)) {
return "You need to specify a language tag."
}
if (scalar(@languages) > 1 && $param && $param->{'name'} eq 'defaultlanguage') {
return "You can only specify one language tag";
}
my $templatedir = bz_locations()->{'templatedir'};
my %lang_seen;
my @validated_languages;
foreach my $language (@languages) {
if( ! -d "$templatedir/$language/custom"
&& ! -d "$templatedir/$language/default") {
return "The template directory for $language does not exist";
}
push(@validated_languages, $language) unless $lang_seen{$language}++;
}
# Rebuild the list of language tags, avoiding duplicates.
$_[0] = join(', ', @validated_languages);
return "";
}
sub check_mail_delivery_method {
my $check = check_multi(@_);
return $check if $check;
my $mailer = shift;
if ($mailer eq 'sendmail' && $^O =~ /MSWin32/i) {
# look for sendmail.exe
return "Failed to locate " . SENDMAIL_EXE
unless -e SENDMAIL_EXE;
}
return "";
}
sub check_notification {
my $option = shift;
my @current_version =
(BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
if ($current_version[1] % 2 && $option eq 'stable_branch_release') {
return "You are currently running a development snapshot, and so your " .
"installation is not based on a branch. If you want to be notified " .
"about the next stable release, you should select " .
"'latest_stable_release' instead";
}
return "";
}
sub check_timezone {
my $tz = shift;
unless (defined(tz_offset($tz))) {
return "must be empty or a legal timezone name, such as PDT or JST";
}
return "";
}
# OK, here are the parameter definitions themselves.
#
# Each definition is a hash with keys:
#
# name - name of the param
# desc - description of the param (for editparams.cgi)
# type - see below
# choices - (optional) see below
# default - default value for the param
# checker - (optional) checking function for validating parameter entry
# It is called with the value of the param as the first arg and a
# reference to the param's hash as the second argument
#
# The type value can be one of the following:
#
# t -- A short text entry field (suitable for a single line)
# l -- A long text field (suitable for many lines)
# b -- A boolean value (either 1 or 0)
# m -- A list of values, with many selectable (shows up as a select box)
# To specify the list of values, make the 'choices' key be an array
# reference of the valid choices. The 'default' key should be an array
# reference for the list of selected values (which must appear in the
# first anonymous array), i.e.:
# {
# name => 'multiselect',
# desc => 'A list of options, choose many',
# type => 'm',
# choices => [ 'a', 'b', 'c', 'd' ],
# default => [ 'a', 'd' ],
# checker => \&check_multi
# }
#
# Here, 'a' and 'd' are the default options, and the user may pick any
# combination of a, b, c, and d as valid options.
#
# &check_multi should always be used as the param verification function
# for list (single and multiple) parameter types.
#
# s -- A list of values, with one selectable (shows up as a select box)
# To specify the list of values, make the 'choices' key be an array
# reference of the valid choices. The 'default' key should be one of
# those values, i.e.:
# {
# name => 'singleselect',
# desc => 'A list of options, choose one',
# type => 's',
# choices => [ 'a', 'b', 'c' ],
# default => 'b',
# checker => \&check_multi
# }
#
# Here, 'b' is the default option, and 'a' and 'c' are other possible
# options, but only one at a time!
#
# &check_multi should always be used as the param verification function
# for list (single and multiple) parameter types.
sub get_param_list {
return;
}
1;
__END__
=head1 NAME
Bugzilla::Config::Common - Parameter checking functions
=head1 DESCRIPTION
All parameter checking functions are called with two parameters:
=head2 Functions
=over
=item C<check_multi>
Checks that a multi-valued parameter (ie type C<s> or type C<m>) satisfies
its contraints.
=item C<check_numeric>
Checks that the value is a valid number
=item C<check_regexp>
Checks that the value is a valid regexp
=back

View File

@@ -1,133 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::Core;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::Core::sortkey = "00";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'maintainer',
type => 't',
default => 'THE MAINTAINER HAS NOT YET BEEN SET'
},
{
name => 'urlbase',
type => 't',
default => '',
checker => \&check_urlbase
},
{
name => 'docs_urlbase',
type => 't',
default => 'docs/html/',
checker => \&check_url
},
{
name => 'sslbase',
type => 't',
default => '',
checker => \&check_sslbase
},
{
name => 'ssl',
type => 's',
choices => ['never', 'authenticated sessions', 'always'],
default => 'never'
},
{
name => 'cookiedomain',
type => 't',
default => ''
},
{
name => 'cookiepath',
type => 't',
default => '/'
},
{
name => 'timezone',
type => 't',
default => '',
checker => \&check_timezone
},
{
name => 'utf8',
type => 'b',
default => '0',
checker => \&check_utf8
},
{
name => 'shutdownhtml',
type => 'l',
default => ''
},
{
name => 'announcehtml',
type => 'l',
default => ''
},
{
name => 'proxy_url',
type => 't',
default => ''
},
{
name => 'upgrade_notification',
type => 's',
choices => ['development_snapshot', 'latest_stable_release',
'stable_branch_release', 'disabled'],
default => 'latest_stable_release',
checker => \&check_notification
} );
return @param_list;
}
1;

View File

@@ -1,52 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::DependencyGraph;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::DependencyGraph::sortkey = "06";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'webdotbase',
type => 't',
default => 'http://www.research.att.com/~north/cgi-bin/webdot.cgi/%urlbase%',
checker => \&check_webdotbase
} );
return @param_list;
}
1;

View File

@@ -1,108 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::GroupSecurity;
use strict;
use Bugzilla::Config::Common;
use Bugzilla::Group;
$Bugzilla::Config::GroupSecurity::sortkey = "07";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'makeproductgroups',
type => 'b',
default => 0
},
{
name => 'useentrygroupdefault',
type => 'b',
default => 0
},
{
name => 'chartgroup',
type => 's',
choices => \&_get_all_group_names,
default => 'editbugs',
checker => \&check_group
},
{
name => 'insidergroup',
type => 's',
choices => \&_get_all_group_names,
default => '',
checker => \&check_group
},
{
name => 'timetrackinggroup',
type => 's',
choices => \&_get_all_group_names,
default => 'editbugs',
checker => \&check_group
},
{
name => 'querysharegroup',
type => 's',
choices => \&_get_all_group_names,
default => 'editbugs',
checker => \&check_group
},
{
name => 'usevisibilitygroups',
type => 'b',
default => 0
},
{
name => 'strict_isolation',
type => 'b',
default => 0
} );
return @param_list;
}
sub _get_all_group_names {
my @group_names = map {$_->name} Bugzilla::Group->get_all;
unshift(@group_names, '');
return \@group_names;
}
1;

View File

@@ -1,79 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::L10n;
use strict;
use File::Spec; # for find_languages
use Bugzilla::Constants;
use Bugzilla::Config::Common;
$Bugzilla::Config::L10n::sortkey = "08";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'languages' ,
extra_desc => { available_languages => find_languages() },
type => 't' ,
default => 'en' ,
checker => \&check_languages
},
{
name => 'defaultlanguage',
type => 't' ,
default => 'en' ,
checker => \&check_languages
} );
return @param_list;
}
sub find_languages {
my @languages = ();
opendir(DIR, bz_locations()->{'templatedir'})
|| return "Can't open 'template' directory: $!";
foreach my $dir (readdir(DIR)) {
next unless $dir =~ /^([a-z-]+)$/i;
my $lang = $1;
next if($lang =~ /^CVS$/i);
my $deft_path = File::Spec->catdir('template', $lang, 'default');
my $cust_path = File::Spec->catdir('template', $lang, 'custom');
push(@languages, $lang) if(-d $deft_path or -d $cust_path);
}
closedir DIR;
return join(', ', @languages);
}
1;

View File

@@ -1,87 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::LDAP;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::LDAP::sortkey = "09";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'LDAPserver',
type => 't',
default => ''
},
{
name => 'LDAPstarttls',
type => 'b',
default => 0
},
{
name => 'LDAPbinddn',
type => 't',
default => ''
},
{
name => 'LDAPBaseDN',
type => 't',
default => ''
},
{
name => 'LDAPuidattribute',
type => 't',
default => 'uid'
},
{
name => 'LDAPmailattribute',
type => 't',
default => 'mail'
},
{
name => 'LDAPfilter',
type => 't',
default => '',
} );
return @param_list;
}
1;

View File

@@ -1,91 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::MTA;
use strict;
use Bugzilla::Config::Common;
use Email::Send;
$Bugzilla::Config::MTA::sortkey = "10";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'mail_delivery_method',
type => 's',
# Bugzilla is not ready yet to send mails to newsgroups, and 'IO'
# is of no use for now as we already have our own 'Test' mode.
choices => [grep {$_ ne 'NNTP' && $_ ne 'IO'} Email::Send->new()->all_mailers(), 'None'],
default => 'Sendmail',
checker => \&check_mail_delivery_method
},
{
name => 'mailfrom',
type => 't',
default => 'bugzilla-daemon'
},
{
name => 'sendmailnow',
type => 'b',
default => 1
},
{
name => 'smtpserver',
type => 't',
default => 'localhost'
},
{
name => 'smtp_debug',
type => 'b',
default => 0
},
{
name => 'whinedays',
type => 't',
default => 7,
checker => \&check_numeric
},
{
name => 'globalwatchers',
type => 't',
default => '',
}, );
return @param_list;
}
1;

View File

@@ -1,75 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::PatchViewer;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::PatchViewer::sortkey = "11";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'cvsroot',
type => 't',
default => '',
},
{
name => 'cvsroot_get',
type => 't',
default => '',
},
{
name => 'bonsai_url',
type => 't',
default => ''
},
{
name => 'lxr_url',
type => 't',
default => ''
},
{
name => 'lxr_root',
type => 't',
default => '',
} );
return @param_list;
}
1;

View File

@@ -1,87 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::Query;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::Query::sortkey = "12";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'quip_list_entry_control',
type => 's',
choices => ['open', 'moderated', 'closed'],
default => 'open',
checker => \&check_multi
},
{
name => 'mostfreqthreshold',
type => 't',
default => '2',
checker => \&check_numeric
},
{
name => 'mybugstemplate',
type => 't',
default => 'buglist.cgi?bug_status=UNCONFIRMED&amp;bug_status=NEW&amp;bug_status=ASSIGNED&amp;bug_status=REOPENED&amp;emailassigned_to1=1&amp;emailreporter1=1&amp;emailtype1=exact&amp;email1=%userid%&amp;field0-0-0=bug_status&amp;type0-0-0=notequals&amp;value0-0-0=UNCONFIRMED&amp;field0-0-1=reporter&amp;type0-0-1=equals&amp;value0-0-1=%userid%'
},
{
name => 'defaultquery',
type => 't',
default => 'bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&order=Importance&long_desc_type=substring'
},
{
name => 'quicksearch_comment_cutoff',
type => 't',
default => '4',
checker => \&check_numeric
},
{
name => 'specific_search_allow_empty_words',
type => 'b',
default => 0
}
);
return @param_list;
}
1;

View File

@@ -1,73 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::ShadowDB;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::ShadowDB::sortkey = "13";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'shadowdbhost',
type => 't',
default => '',
},
{
name => 'shadowdbport',
type => 't',
default => '3306',
checker => \&check_numeric,
},
{
name => 'shadowdbsock',
type => 't',
default => '',
},
# This entry must be _after_ the shadowdb{host,port,sock} settings so that
# they can be used in the validation here
{
name => 'shadowdb',
type => 't',
default => '',
checker => \&check_shadowdb
} );
return @param_list;
}
1;

View File

@@ -1,71 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jacob Steenhagen <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Joseph Heenan <joseph@heenan.me.uk>
# Erik Stambaugh <erik@dasbistro.com>
# Frédéric Buclin <LpSolit@gmail.com>
#
package Bugzilla::Config::UserMatch;
use strict;
use Bugzilla::Config::Common;
$Bugzilla::Config::UserMatch::sortkey = "14";
sub get_param_list {
my $class = shift;
my @param_list = (
{
name => 'usemenuforusers',
type => 'b',
default => '0'
},
{
name => 'usermatchmode',
type => 's',
choices => ['off', 'wildcard', 'search'],
default => 'off'
},
{
name => 'maxusermatches',
type => 't',
default => '1000',
checker => \&check_numeric
},
{
name => 'confirmuniqueusermatch',
type => 'b',
default => 1,
} );
return @param_list;
}
1;

View File

@@ -1,446 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dawn Endico <endico@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Joe Robins <jmrobins@tgix.com>
# Jake <jake@bugzilla.org>
# J. Paul Reed <preed@sigkill.com>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Christopher Aillon <christopher@aillon.com>
# Shane H. W. Travis <travis@sedsystems.ca>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Marc Schumann <wurblzap@gmail.com>
package Bugzilla::Constants;
use strict;
use base qw(Exporter);
# For bz_locations
use File::Basename;
@Bugzilla::Constants::EXPORT = qw(
BUGZILLA_VERSION
bz_locations
CONTROLMAPNA
CONTROLMAPSHOWN
CONTROLMAPDEFAULT
CONTROLMAPMANDATORY
AUTH_OK
AUTH_NODATA
AUTH_ERROR
AUTH_LOGINFAILED
AUTH_DISABLED
AUTH_NO_SUCH_USER
USER_PASSWORD_MIN_LENGTH
USER_PASSWORD_MAX_LENGTH
LOGIN_OPTIONAL
LOGIN_NORMAL
LOGIN_REQUIRED
LOGOUT_ALL
LOGOUT_CURRENT
LOGOUT_KEEP_CURRENT
GRANT_DIRECT
GRANT_REGEXP
GROUP_MEMBERSHIP
GROUP_BLESS
GROUP_VISIBLE
MAILTO_USER
MAILTO_GROUP
DEFAULT_COLUMN_LIST
DEFAULT_QUERY_NAME
QUERY_LIST
LIST_OF_BUGS
COMMENT_COLS
CMT_NORMAL
CMT_DUPE_OF
CMT_HAS_DUPE
CMT_POPULAR_VOTES
CMT_MOVED_TO
UNLOCK_ABORT
THROW_ERROR
RELATIONSHIPS
REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER REL_GLOBAL_WATCHER
REL_ANY
POS_EVENTS
EVT_OTHER EVT_ADDED_REMOVED EVT_COMMENT EVT_ATTACHMENT EVT_ATTACHMENT_DATA
EVT_PROJ_MANAGEMENT EVT_OPENED_CLOSED EVT_KEYWORD EVT_CC EVT_DEPEND_BLOCK
NEG_EVENTS
EVT_UNCONFIRMED EVT_CHANGED_BY_ME
GLOBAL_EVENTS
EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
FULLTEXT_BUGLIST_LIMIT
ADMIN_GROUP_NAME
PER_PRODUCT_PRIVILEGES
SENDMAIL_EXE
SENDMAIL_PATH
FIELD_TYPE_UNKNOWN
FIELD_TYPE_FREETEXT
FIELD_TYPE_SINGLE_SELECT
BUG_STATE_OPEN
USAGE_MODE_BROWSER
USAGE_MODE_CMDLINE
USAGE_MODE_WEBSERVICE
USAGE_MODE_EMAIL
ERROR_MODE_WEBPAGE
ERROR_MODE_DIE
ERROR_MODE_DIE_SOAP_FAULT
INSTALLATION_MODE_INTERACTIVE
INSTALLATION_MODE_NON_INTERACTIVE
DB_MODULE
ROOT_USER
ON_WINDOWS
MAX_TOKEN_AGE
SAFE_PROTOCOLS
MAX_LEN_QUERY_NAME
MAX_FREETEXT_LENGTH
);
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
# CONSTANTS
#
# Bugzilla version
use constant BUGZILLA_VERSION => "3.0.4";
#
# ControlMap constants for group_control_map.
# membercontol:othercontrol => meaning
# Na:Na => Bugs in this product may not be restricted to this
# group.
# Shown:Na => Members of the group may restrict bugs
# in this product to this group.
# Shown:Shown => Members of the group may restrict bugs
# in this product to this group.
# Anyone who can enter bugs in this product may initially
# restrict bugs in this product to this group.
# Shown:Mandatory => Members of the group may restrict bugs
# in this product to this group.
# Non-members who can enter bug in this product
# will be forced to restrict it.
# Default:Na => Members of the group may restrict bugs in this
# product to this group and do so by default.
# Default:Default => Members of the group may restrict bugs in this
# product to this group and do so by default and
# nonmembers have this option on entry.
# Default:Mandatory => Members of the group may restrict bugs in this
# product to this group and do so by default.
# Non-members who can enter bug in this product
# will be forced to restrict it.
# Mandatory:Mandatory => Bug will be forced into this group regardless.
# All other combinations are illegal.
use constant CONTROLMAPNA => 0;
use constant CONTROLMAPSHOWN => 1;
use constant CONTROLMAPDEFAULT => 2;
use constant CONTROLMAPMANDATORY => 3;
# See Bugzilla::Auth for docs on AUTH_*, LOGIN_* and LOGOUT_*
use constant AUTH_OK => 0;
use constant AUTH_NODATA => 1;
use constant AUTH_ERROR => 2;
use constant AUTH_LOGINFAILED => 3;
use constant AUTH_DISABLED => 4;
use constant AUTH_NO_SUCH_USER => 5;
# The minimum and maximum lengths a password must have.
use constant USER_PASSWORD_MIN_LENGTH => 3;
use constant USER_PASSWORD_MAX_LENGTH => 16;
use constant LOGIN_OPTIONAL => 0;
use constant LOGIN_NORMAL => 1;
use constant LOGIN_REQUIRED => 2;
use constant LOGOUT_ALL => 0;
use constant LOGOUT_CURRENT => 1;
use constant LOGOUT_KEEP_CURRENT => 2;
use constant contenttypes =>
{
"html"=> "text/html" ,
"rdf" => "application/rdf+xml" ,
"atom"=> "application/atom+xml" ,
"xml" => "application/xml" ,
"js" => "application/x-javascript" ,
"csv" => "text/csv" ,
"png" => "image/png" ,
"ics" => "text/calendar" ,
};
use constant GRANT_DIRECT => 0;
use constant GRANT_REGEXP => 2;
use constant GROUP_MEMBERSHIP => 0;
use constant GROUP_BLESS => 1;
use constant GROUP_VISIBLE => 2;
use constant MAILTO_USER => 0;
use constant MAILTO_GROUP => 1;
# The default list of columns for buglist.cgi
use constant DEFAULT_COLUMN_LIST => (
"bug_severity", "priority", "op_sys","assigned_to",
"bug_status", "resolution", "short_desc"
);
# Used by query.cgi and buglist.cgi as the named-query name
# for the default settings.
use constant DEFAULT_QUERY_NAME => '(Default query)';
# The possible types for saved searches.
use constant QUERY_LIST => 0;
use constant LIST_OF_BUGS => 1;
# The column length for displayed (and wrapped) bug comments.
use constant COMMENT_COLS => 80;
# The type of bug comments.
use constant CMT_NORMAL => 0;
use constant CMT_DUPE_OF => 1;
use constant CMT_HAS_DUPE => 2;
use constant CMT_POPULAR_VOTES => 3;
use constant CMT_MOVED_TO => 4;
# used by Bugzilla::DB to indicate that tables are being unlocked
# because of error
use constant UNLOCK_ABORT => 1;
# Determine whether a validation routine should return 0 or throw
# an error when the validation fails.
use constant THROW_ERROR => 1;
use constant REL_ASSIGNEE => 0;
use constant REL_QA => 1;
use constant REL_REPORTER => 2;
use constant REL_CC => 3;
use constant REL_VOTER => 4;
use constant REL_GLOBAL_WATCHER => 5;
use constant RELATIONSHIPS => REL_ASSIGNEE, REL_QA, REL_REPORTER, REL_CC,
REL_VOTER, REL_GLOBAL_WATCHER;
# Used for global events like EVT_FLAG_REQUESTED
use constant REL_ANY => 100;
# There are two sorts of event - positive and negative. Positive events are
# those for which the user says "I want mail if this happens." Negative events
# are those for which the user says "I don't want mail if this happens."
#
# Exactly when each event fires is defined in wants_bug_mail() in User.pm; I'm
# not commenting them here in case the comments and the code get out of sync.
use constant EVT_OTHER => 0;
use constant EVT_ADDED_REMOVED => 1;
use constant EVT_COMMENT => 2;
use constant EVT_ATTACHMENT => 3;
use constant EVT_ATTACHMENT_DATA => 4;
use constant EVT_PROJ_MANAGEMENT => 5;
use constant EVT_OPENED_CLOSED => 6;
use constant EVT_KEYWORD => 7;
use constant EVT_CC => 8;
use constant EVT_DEPEND_BLOCK => 9;
use constant POS_EVENTS => EVT_OTHER, EVT_ADDED_REMOVED, EVT_COMMENT,
EVT_ATTACHMENT, EVT_ATTACHMENT_DATA,
EVT_PROJ_MANAGEMENT, EVT_OPENED_CLOSED, EVT_KEYWORD,
EVT_CC, EVT_DEPEND_BLOCK;
use constant EVT_UNCONFIRMED => 50;
use constant EVT_CHANGED_BY_ME => 51;
use constant NEG_EVENTS => EVT_UNCONFIRMED, EVT_CHANGED_BY_ME;
# These are the "global" flags, which aren't tied to a particular relationship.
# and so use REL_ANY.
use constant EVT_FLAG_REQUESTED => 100; # Flag has been requested of me
use constant EVT_REQUESTED_FLAG => 101; # I have requested a flag
use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
# Number of bugs to return in a buglist when performing
# a fulltext search.
use constant FULLTEXT_BUGLIST_LIMIT => 200;
# Default administration group name.
use constant ADMIN_GROUP_NAME => 'admin';
# Privileges which can be per-product.
use constant PER_PRODUCT_PRIVILEGES => ('editcomponents', 'editbugs', 'canconfirm');
# Path to sendmail.exe (Windows only)
use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
# Paths to search for the sendmail binary (non-Windows)
use constant SENDMAIL_PATH => '/usr/lib:/usr/sbin:/usr/ucblib';
# Field types. Match values in fielddefs.type column. These are purposely
# not named after database column types, since Bugzilla fields comprise not
# only storage but also logic. For example, we might add a "user" field type
# whose values are stored in an integer column in the database but for which
# we do more than we would do for a standard integer type (f.e. we might
# display a user picker).
use constant FIELD_TYPE_UNKNOWN => 0;
use constant FIELD_TYPE_FREETEXT => 1;
use constant FIELD_TYPE_SINGLE_SELECT => 2;
# The maximum number of days a token will remain valid.
use constant MAX_TOKEN_AGE => 3;
# Protocols which are considered as safe.
use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
'view-source', 'wais');
# States that are considered to be "open" for bugs.
use constant BUG_STATE_OPEN => ('NEW', 'REOPENED', 'ASSIGNED',
'UNCONFIRMED');
# Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
use constant USAGE_MODE_BROWSER => 0;
use constant USAGE_MODE_CMDLINE => 1;
use constant USAGE_MODE_WEBSERVICE => 2;
use constant USAGE_MODE_EMAIL => 3;
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
# usually). Use with Bugzilla->error_mode.
use constant ERROR_MODE_WEBPAGE => 0;
use constant ERROR_MODE_DIE => 1;
use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
# The various modes that checksetup.pl can run in.
use constant INSTALLATION_MODE_INTERACTIVE => 0;
use constant INSTALLATION_MODE_NON_INTERACTIVE => 1;
# Data about what we require for different databases.
use constant DB_MODULE => {
'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '4.1.2',
dbd => {
package => 'DBD-mysql',
module => 'DBD::mysql',
version => '2.9003',
# Certain versions are broken, development versions are
# always disallowed.
blacklist => ['^3\.000[3-6]', '_'],
},
name => 'MySQL'},
'pg' => {db => 'Bugzilla::DB::Pg', db_version => '8.00.0000',
dbd => {
package => 'DBD-Pg',
module => 'DBD::Pg',
version => '1.45',
},
name => 'PostgreSQL'},
};
# The user who should be considered "root" when we're giving
# instructions to Bugzilla administrators.
use constant ROOT_USER => $^O =~ /MSWin32/i ? 'Administrator' : 'root';
# True if we're on Win32.
use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
# The longest that a saved search name can be.
use constant MAX_LEN_QUERY_NAME => 64;
# Maximum length allowed for free text fields.
use constant MAX_FREETEXT_LENGTH => 255;
sub bz_locations {
# We know that Bugzilla/Constants.pm must be in %INC at this point.
# So the only question is, what's the name of the directory
# above it? This is the most reliable way to get our current working
# directory under both mod_cgi and mod_perl. We call dirname twice
# to get the name of the directory above the "Bugzilla/" directory.
#
# Calling dirname twice like that won't work on VMS or AmigaOS
# but I doubt anybody runs Bugzilla on those.
#
# On mod_cgi this will be a relative path. On mod_perl it will be an
# absolute path.
my $libpath = dirname(dirname($INC{'Bugzilla/Constants.pm'}));
# We have to detaint $libpath, but we can't use Bugzilla::Util here.
$libpath =~ /(.*)/;
$libpath = $1;
my ($project, $localconfig, $datadir);
if ($ENV{'PROJECT'} && $ENV{'PROJECT'} =~ /^(\w+)$/) {
$project = $1;
$datadir = "data/$project";
} else {
$datadir = "data";
}
# The "localconfig" file has another path on the ReactOS Web Server
$localconfig = "../../www.reactos.org_config/bugzilla-config";
# We have to return absolute paths for mod_perl.
# That means that if you modify these paths, they must be absolute paths.
return {
'libpath' => $libpath,
# If you put the libraries in a different location than the CGIs,
# make sure this still points to the CGIs.
'cgi_path' => $libpath,
'templatedir' => "$libpath/template",
'project' => $project,
'localconfig' => "$libpath/$localconfig",
'datadir' => "$libpath/$datadir",
'attachdir' => "$libpath/$datadir/attachments",
'skinsdir' => "$libpath/skins",
# $webdotdir must be in the webtree somewhere. Even if you use a
# local dot, we output images to there. Also, if $webdotdir is
# not relative to the bugzilla root directory, you'll need to
# change showdependencygraph.cgi to set image_url to the correct
# location.
# The script should really generate these graphs directly...
'webdotdir' => "$libpath/$datadir/webdot",
'extensionsdir' => "$libpath/extensions",
};
}
1;

File diff suppressed because it is too large Load Diff

View File

@@ -1,887 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Dave Miller <davem00@aol.com>
# Gayathri Swaminath <gayathrik00@aol.com>
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
# Dave Lawrence <dkl@redhat.com>
# Tomas Kopal <Tomas.Kopal@altap.cz>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Lance Larsh <lance.larsh@oracle.com>
=head1 NAME
Bugzilla::DB::Mysql - Bugzilla database compatibility layer for MySQL
=head1 DESCRIPTION
This module overrides methods of the Bugzilla::DB module with MySQL specific
implementation. It is instantiated by the Bugzilla::DB module and should never
be used directly.
For interface details see L<Bugzilla::DB> and L<DBI>.
=cut
package Bugzilla::DB::Mysql;
use strict;
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Error;
# This module extends the DB interface via inheritance
use base qw(Bugzilla::DB);
sub new {
my ($class, $user, $pass, $host, $dbname, $port, $sock) = @_;
# construct the DSN from the parameters we got
my $dsn = "DBI:mysql:host=$host;database=$dbname";
$dsn .= ";port=$port" if $port;
$dsn .= ";mysql_socket=$sock" if $sock;
my $self = $class->db_new($dsn, $user, $pass);
# This makes sure that if the tables are encoded as UTF-8, we
# return their data correctly.
$self->do("SET NAMES utf8") if Bugzilla->params->{'utf8'};
# all class local variables stored in DBI derived class needs to have
# a prefix 'private_'. See DBI documentation.
$self->{private_bz_tables_locked} = "";
bless ($self, $class);
# Bug 321645 - disable MySQL strict mode, if set
my ($var, $sql_mode) = $self->selectrow_array(
"SHOW VARIABLES LIKE 'sql\\_mode'");
if ($sql_mode) {
# STRICT_TRANS_TABLE or STRICT_ALL_TABLES enable MySQL strict mode,
# causing bug 321645. TRADITIONAL sets these modes (among others) as
# well, so it has to be stipped as well
my $new_sql_mode =
join(",", grep {$_ !~ /^STRICT_(?:TRANS|ALL)_TABLES|TRADITIONAL$/}
split(/,/, $sql_mode));
if ($sql_mode ne $new_sql_mode) {
$self->do("SET SESSION sql_mode = ?", undef, $new_sql_mode);
}
}
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 {
my ($self, $expr, $pattern) = @_;
return "$expr REGEXP $pattern";
}
sub sql_not_regexp {
my ($self, $expr, $pattern) = @_;
return "$expr NOT REGEXP $pattern";
}
sub sql_limit {
my ($self, $limit, $offset) = @_;
if (defined($offset)) {
return "LIMIT $offset, $limit";
} else {
return "LIMIT $limit";
}
}
sub sql_string_concat {
my ($self, @params) = @_;
return 'CONCAT(' . join(', ', @params) . ')';
}
sub sql_fulltext_search {
my ($self, $column, $text) = @_;
# Add the boolean mode modifier if the search string contains
# boolean operators.
my $mode = ($text =~ /[+-<>()~*"]/ ? "IN BOOLEAN MODE" : "");
# quote the text for use in the MATCH AGAINST expression
$text = $self->quote($text);
# untaint the text, since it's safe to use now that we've quoted it
trick_taint($text);
return "MATCH($column) AGAINST($text $mode)";
}
sub sql_istring {
my ($self, $string) = @_;
return $string;
}
sub sql_from_days {
my ($self, $days) = @_;
return "FROM_DAYS($days)";
}
sub sql_to_days {
my ($self, $date) = @_;
return "TO_DAYS($date)";
}
sub sql_date_format {
my ($self, $date, $format) = @_;
$format = "%Y.%m.%d %H:%i:%s" if !$format;
return "DATE_FORMAT($date, " . $self->quote($format) . ")";
}
sub sql_interval {
my ($self, $interval, $units) = @_;
return "INTERVAL $interval $units";
}
sub sql_position {
my ($self, $fragment, $text) = @_;
# mysql 4.0.1 and lower do not support CAST
# (checksetup has a check for unsupported versions)
my $server_version = $self->bz_server_version;
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 support
# 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";
}
my @tables = $self->bz_table_list_real();
$self->_after_table_status(\@tables);
# Versions of Bugzilla before the existence of Bugzilla::DB::Schema did
# not provide explicit names for the table indexes. This means
# that our upgrades will not be reliable, because we look for the name
# of the index, not what fields it is on, when doing upgrades.
# (using the name is much better for cross-database compatibility
# and general reliability). It's also very important that our
# Schema object be consistent with what is on the disk.
#
# While we're at it, we also fix some inconsistent index naming
# from the original checkin of Bugzilla::DB::Schema.
# We check for the existence of a particular "short name" index that
# has existed at least since Bugzilla 2.8, and probably earlier.
# For fixing the inconsistent naming of Schema indexes,
# we also check for one of those inconsistently-named indexes.
if (grep($_ eq 'bugs', @tables)
&& ($self->bz_index_info_real('bugs', 'assigned_to')
|| $self->bz_index_info_real('flags', 'flags_bidattid_idx')) )
{
# This is a check unrelated to the indexes, to see if people are
# upgrading from 2.18 or below, but somehow have a bz_schema table
# already. This only happens if they have done a mysqldump into
# a database without doing a DROP DATABASE first.
# We just do the check here since this check is a reliable way
# of telling that we are upgrading from a version pre-2.20.
if (grep($_ eq 'bz_schema', $self->bz_table_list_real())) {
die("\nYou are upgrading from a version before 2.20, but the"
. " bz_schema\ntable already exists. This means that you"
. " restored a mysqldump into\nthe Bugzilla database without"
. " first dropping the already-existing\nBugzilla database,"
. " at some point. Whenever you restore a Bugzilla\ndatabase"
. " backup, you must always drop the entire database first.\n\n"
. "Please drop your Bugzilla database and restore it from a"
. " backup that\ndoes not contain the bz_schema table. If for"
. " some reason you cannot\ndo this, you can connect to your"
. " MySQL database and drop the bz_schema\ntable, as a last"
. " resort.\n");
}
my $bug_count = $self->selectrow_array("SELECT COUNT(*) FROM bugs");
# We estimate one minute for each 3000 bugs, plus 3 minutes just
# to handle basic MySQL stuff.
my $rename_time = int($bug_count / 3000) + 3;
# And 45 minutes for every 15,000 attachments, per some experiments.
my ($attachment_count) =
$self->selectrow_array("SELECT COUNT(*) FROM attachments");
$rename_time += int(($attachment_count * 45) / 15000);
# If we're going to take longer than 5 minutes, we let the user know
# and allow them to abort.
if ($rename_time > 5) {
print "\nWe are about to rename old indexes.\n"
. "The estimated time to complete renaming is "
. "$rename_time minutes.\n"
. "You cannot interrupt this action once it has begun.\n"
. "If you would like to cancel, press Ctrl-C now..."
. " (Waiting 45 seconds...)\n\n";
# Wait 45 seconds for them to respond.
sleep(45) unless Bugzilla->installation_answers->{NO_PAUSE};
}
print "Renaming indexes...\n";
# We can't be interrupted, because of how the "if"
# works above.
local $SIG{INT} = 'IGNORE';
local $SIG{TERM} = 'IGNORE';
local $SIG{PIPE} = 'IGNORE';
# Certain indexes had names in Schema that did not easily conform
# to a standard. We store those names here, so that they
# can be properly renamed.
# Also, sometimes an old mysqldump would incorrectly rename
# unique indexes to "PRIMARY", so we address that here, also.
my $bad_names = {
# 'when' is a possible leftover from Bugzillas before 2.8
bugs_activity => ['when', 'bugs_activity_bugid_idx',
'bugs_activity_bugwhen_idx'],
cc => ['PRIMARY'],
longdescs => ['longdescs_bugid_idx',
'longdescs_bugwhen_idx'],
flags => ['flags_bidattid_idx'],
flaginclusions => ['flaginclusions_tpcid_idx'],
flagexclusions => ['flagexclusions_tpc_id_idx'],
keywords => ['PRIMARY'],
milestones => ['PRIMARY'],
profiles_activity => ['profiles_activity_when_idx'],
group_control_map => ['group_control_map_gid_idx', 'PRIMARY'],
user_group_map => ['PRIMARY'],
group_group_map => ['PRIMARY'],
email_setting => ['PRIMARY'],
bug_group_map => ['PRIMARY'],
category_group_map => ['PRIMARY'],
watch => ['PRIMARY'],
namedqueries => ['PRIMARY'],
series_data => ['PRIMARY'],
# series_categories is dealt with below, not here.
};
# The series table is broken and needs to have one index
# dropped before we begin the renaming, because it had a
# useless index on it that would cause a naming conflict here.
if (grep($_ eq 'series', @tables)) {
my $dropname;
# This is what the bad index was called before Schema.
if ($self->bz_index_info_real('series', 'creator_2')) {
$dropname = 'creator_2';
}
# This is what the bad index is called in Schema.
elsif ($self->bz_index_info_real('series', 'series_creator_idx')) {
$dropname = 'series_creator_idx';
}
$self->bz_drop_index_raw('series', $dropname) if $dropname;
}
# The email_setting table also had the same problem.
if( grep($_ eq 'email_setting', @tables)
&& $self->bz_index_info_real('email_setting',
'email_settings_user_id_idx') )
{
$self->bz_drop_index_raw('email_setting',
'email_settings_user_id_idx');
}
# Go through all the tables.
foreach my $table (@tables) {
# Will contain the names of old indexes as keys, and the
# definition of the new indexes as a value. The values
# include an extra hash key, NAME, with the new name of
# the index.
my %rename_indexes;
# And go through all the columns on each table.
my @columns = $self->bz_table_columns_real($table);
# We also want to fix the silly naming of unique indexes
# that happened when we first checked-in Bugzilla::DB::Schema.
if ($table eq 'series_categories') {
# The series_categories index had a nonstandard name.
push(@columns, 'series_cats_unique_idx');
}
elsif ($table eq 'email_setting') {
# The email_setting table had a similar problem.
push(@columns, 'email_settings_unique_idx');
}
else {
push(@columns, "${table}_unique_idx");
}
# And this is how we fix the other inconsistent Schema naming.
push(@columns, @{$bad_names->{$table}})
if (exists $bad_names->{$table});
foreach my $column (@columns) {
# If we have an index named after this column, it's an
# old-style-name index.
if (my $index = $self->bz_index_info_real($table, $column)) {
# Fix the name to fit in with the new naming scheme.
$index->{NAME} = $table . "_" .
$index->{FIELDS}->[0] . "_idx";
print "Renaming index $column to "
. $index->{NAME} . "...\n";
$rename_indexes{$column} = $index;
} # if
} # foreach column
my @rename_sql = $self->_bz_schema->get_rename_indexes_ddl(
$table, %rename_indexes);
$self->do($_) foreach (@rename_sql);
} # foreach table
} # if old-name indexes
# If there are no tables, but the DB isn't utf8 and it should be,
# then we should alter the database to be utf8. We know it should be
# if the utf8 parameter is true or there are no params at all.
# This kind of situation happens when people create the database
# themselves, and if we don't do this they will get the big
# scary WARNING statement about conversion to UTF8.
if ( !$self->bz_db_is_utf8 && !@tables
&& (Bugzilla->params->{'utf8'} || !scalar keys %{Bugzilla->params}) )
{
$self->_alter_db_charset_to_utf8();
}
# And now we create the tables and the Schema object.
$self->SUPER::bz_setup_database();
# The old timestamp fields need to be adjusted here instead of in
# checksetup. Otherwise the UPDATE statements inside of bz_add_column
# will cause accidental timestamp updates.
# The code that does this was moved here from checksetup.
# 2002-08-14 - bbaetz@student.usyd.edu.au - bug 153578
# attachments creation time needs to be a datetime, not a timestamp
my $attach_creation =
$self->bz_column_info("attachments", "creation_ts");
if ($attach_creation && $attach_creation->{TYPE} =~ /^TIMESTAMP/i) {
print "Fixing creation time on attachments...\n";
my $sth = $self->prepare("SELECT COUNT(attach_id) FROM attachments");
$sth->execute();
my ($attach_count) = $sth->fetchrow_array();
if ($attach_count > 1000) {
print "This may take a while...\n";
}
my $i = 0;
# This isn't just as simple as changing the field type, because
# the creation_ts was previously updated when an attachment was made
# obsolete from the attachment creation screen. So we have to go
# and recreate these times from the comments..
$sth = $self->prepare("SELECT bug_id, attach_id, submitter_id " .
"FROM attachments");
$sth->execute();
# Restrict this as much as possible in order to avoid false
# positives, and keep the db search time down
my $sth2 = $self->prepare("SELECT bug_when FROM longdescs
WHERE bug_id=? AND who=?
AND thetext LIKE ?
ORDER BY bug_when " . $self->sql_limit(1));
while (my ($bug_id, $attach_id, $submitter_id)
= $sth->fetchrow_array())
{
$sth2->execute($bug_id, $submitter_id,
"Created an attachment (id=$attach_id)%");
my ($when) = $sth2->fetchrow_array();
if ($when) {
$self->do("UPDATE attachments " .
"SET creation_ts='$when' " .
"WHERE attach_id=$attach_id");
} else {
print "Warning - could not determine correct creation"
. " time for attachment $attach_id on bug $bug_id\n";
}
++$i;
print "Converted $i of $attach_count attachments\n" if !($i % 1000);
}
print "Done - converted $i attachments\n";
$self->bz_alter_column("attachments", "creation_ts",
{TYPE => 'DATETIME', NOTNULL => 1});
}
# 2004-08-29 - Tomas.Kopal@altap.cz, bug 257303
# Change logincookies.lastused type from timestamp to datetime
my $login_lastused = $self->bz_column_info("logincookies", "lastused");
if ($login_lastused && $login_lastused->{TYPE} =~ /^TIMESTAMP/i) {
$self->bz_alter_column('logincookies', 'lastused',
{ TYPE => 'DATETIME', NOTNULL => 1});
}
# 2005-01-17 - Tomas.Kopal@altap.cz, bug 257315
# Change bugs.delta_ts type from timestamp to datetime
my $bugs_deltats = $self->bz_column_info("bugs", "delta_ts");
if ($bugs_deltats && $bugs_deltats->{TYPE} =~ /^TIMESTAMP/i) {
$self->bz_alter_column('bugs', 'delta_ts',
{TYPE => 'DATETIME', NOTNULL => 1});
}
# 2005-09-24 - bugreport@peshkin.net, bug 307602
# Make sure that default 4G table limit is overridden
my $row = $self->selectrow_hashref("SHOW TABLE STATUS LIKE 'attach_data'");
if ($$row{'Create_options'} !~ /MAX_ROWS/i) {
print "Converting attach_data maximum size to 100G...\n";
$self->do("ALTER TABLE attach_data
AVG_ROW_LENGTH=1000000,
MAX_ROWS=100000");
}
# Convert the database to UTF-8 if the utf8 parameter is on.
# We check if any table isn't utf8, because lots of crazy
# partial-conversion situations can happen, and this handles anything
# that could come up (including having the DB charset be utf8 but not
# the table charsets.
my $utf_table_status =
$self->selectall_arrayref("SHOW TABLE STATUS", {Slice=>{}});
$self->_after_table_status([map($_->{Name}, @$utf_table_status)]);
my @non_utf8_tables = grep($_->{Collation} !~ /^utf8/, @$utf_table_status);
if (Bugzilla->params->{'utf8'} && scalar @non_utf8_tables) {
print <<EOT;
WARNING: We are about to convert your table storage format to UTF8. This
allows Bugzilla to correctly store and sort international characters.
However, if you have any non-UTF-8 data in your database,
it ***WILL BE DELETED*** by this process. So, before
you continue with checksetup.pl, if you have any non-UTF-8
data (or even if you're not sure) you should press Ctrl-C now
to interrupt checksetup.pl, and run contrib/recode.pl to make all
the data in your database into UTF-8. You should also back up your
database before continuing. This will affect every single table
in the database, even non-Bugzilla tables.
If you ever used a version of Bugzilla before 2.22, we STRONGLY
recommend that you stop checksetup.pl NOW and run contrib/recode.pl.
EOT
if (!Bugzilla->installation_answers->{NO_PAUSE}) {
if (Bugzilla->installation_mode ==
INSTALLATION_MODE_NON_INTERACTIVE)
{
print <<EOT;
Re-run checksetup.pl in interactive mode (without an 'answers' file)
to continue.
EOT
exit;
}
else {
print " Press Enter to continue or Ctrl-C to exit...";
getc;
}
}
print "Converting table storage format to UTF-8. This may take a",
" while.\n";
foreach my $table ($self->bz_table_list_real) {
my $info_sth = $self->prepare("SHOW FULL COLUMNS FROM $table");
$info_sth->execute();
while (my $column = $info_sth->fetchrow_hashref) {
# Our conversion code doesn't work on enum fields, but they
# all go away later in checksetup anyway.
next if $column->{Type} =~ /enum/i;
# If this particular column isn't stored in utf-8
if ($column->{Collation}
&& $column->{Collation} ne 'NULL'
&& $column->{Collation} !~ /utf8/)
{
my $name = $column->{Field};
# The code below doesn't work on a field with a FULLTEXT
# index. So we drop it. The upgrade code will re-create
# it later.
if ($table eq 'longdescs' && $name eq 'thetext') {
$self->bz_drop_index('longdescs',
'longdescs_thetext_idx');
}
if ($table eq 'bugs' && $name eq 'short_desc') {
$self->bz_drop_index('bugs', 'bugs_short_desc_idx');
}
print "Converting $table.$name to be stored as UTF-8...\n";
my $col_info =
$self->bz_column_info_real($table, $name);
# CHANGE COLUMN doesn't take PRIMARY KEY
delete $col_info->{PRIMARYKEY};
my $sql_def = $self->_bz_schema->get_type_ddl($col_info);
# We don't want MySQL to actually try to *convert*
# from our current charset to UTF-8, we just want to
# transfer the bytes directly. This is how we do that.
# The CHARACTER SET part of the definition has to come
# right after the type, which will always come first.
my ($binary, $utf8) = ($sql_def, $sql_def);
my $type = $self->_bz_schema->convert_type($col_info->{TYPE});
$binary =~ s/(\Q$type\E)/$1 CHARACTER SET binary/;
$utf8 =~ s/(\Q$type\E)/$1 CHARACTER SET utf8/;
$self->do("ALTER TABLE $table CHANGE COLUMN $name $name
$binary");
$self->do("ALTER TABLE $table CHANGE COLUMN $name $name
$utf8");
}
}
$self->do("ALTER TABLE $table DEFAULT CHARACTER SET utf8");
} # foreach my $table (@tables)
}
# Sometimes you can have a situation where all the tables are utf8,
# but the database isn't. (This tends to happen when you've done
# a mysqldump.) So we have this change outside of the above block,
# so that it just happens silently if no actual *table* conversion
# needs to happen.
if (Bugzilla->params->{'utf8'} && !$self->bz_db_is_utf8) {
$self->_alter_db_charset_to_utf8();
}
}
# There is a bug in MySQL 4.1.0 - 4.1.15 that makes certain SELECT
# statements fail after a SHOW TABLE STATUS:
# http://bugs.mysql.com/bug.php?id=13535
# This is a workaround, a dummy SELECT to reset the LAST_INSERT_ID.
sub _after_table_status {
my ($self, $tables) = @_;
if (grep($_ eq 'bugs', @$tables)
&& $self->bz_column_info_real("bugs", "bug_id"))
{
$self->do('SELECT 1 FROM bugs WHERE bug_id IS NULL');
}
}
sub _alter_db_charset_to_utf8 {
my $self = shift;
my $db_name = Bugzilla->localconfig->{db_name};
$self->do("ALTER DATABASE $db_name CHARACTER SET utf8");
}
sub bz_db_is_utf8 {
my $self = shift;
my $db_collation = $self->selectrow_arrayref(
"SHOW VARIABLES LIKE 'character_set_database'");
# First column holds the variable name, second column holds the value.
return $db_collation->[1] =~ /utf8/ ? 1 : 0;
}
sub bz_enum_initial_values {
my ($self) = @_;
my %enum_values = %{$self->ENUM_DEFAULTS};
# Get a complete description of the 'bugs' table; with DBD::MySQL
# there isn't a column-by-column way of doing this. Could use
# $dbh->column_info, but it would go slower and we would have to
# use the undocumented mysql_type_name accessor to get the type
# of each row.
my $sth = $self->prepare("DESCRIBE bugs");
$sth->execute();
# Look for the particular columns we are interested in.
while (my ($thiscol, $thistype) = $sth->fetchrow_array()) {
if (defined $enum_values{$thiscol}) {
# this is a column of interest.
my @value_list;
if ($thistype and ($thistype =~ /^enum\(/)) {
# it has an enum type; get the set of values.
while ($thistype =~ /'([^']*)'(.*)/) {
push(@value_list, $1);
$thistype = $2;
}
}
if (@value_list) {
# record the enum values found.
$enum_values{$thiscol} = \@value_list;
}
}
}
return \%enum_values;
}
#####################################################################
# MySQL-specific Database-Reading Methods
#####################################################################
=begin private
=head1 MYSQL-SPECIFIC DATABASE-READING METHODS
These methods read information about the database from the disk,
instead of from a Schema object. They are only reliable for MySQL
(see bug 285111 for the reasons why not all DBs use/have functions
like this), but that's OK because we only need them for
backwards-compatibility anyway, for versions of Bugzilla before 2.20.
=over 4
=item C<bz_column_info_real($table, $column)>
Description: Returns an abstract column definition for a column
as it actually exists on disk in the database.
Params: $table - The name of the table the column is on.
$column - The name of the column you want info about.
Returns: An abstract column definition.
If the column does not exist, returns undef.
=cut
sub bz_column_info_real {
my ($self, $table, $column) = @_;
# DBD::mysql does not support selecting a specific column,
# so we have to get all the columns on the table and find
# the one we want.
my $info_sth = $self->column_info(undef, undef, $table, '%');
# Don't use fetchall_hashref as there's a Win32 DBI bug (292821)
my $col_data;
while ($col_data = $info_sth->fetchrow_hashref) {
last if $col_data->{'COLUMN_NAME'} eq $column;
}
if (!defined $col_data) {
return undef;
}
return $self->_bz_schema->column_info_to_column($col_data);
}
=item C<bz_index_info_real($table, $index)>
Description: Returns information about an index on a table in the database.
Params: $table = name of table containing the index
$index = name of an index
Returns: An abstract index definition, always in hashref format.
If the index does not exist, the function returns undef.
=cut
sub bz_index_info_real {
my ($self, $table, $index) = @_;
my $sth = $self->prepare("SHOW INDEX FROM $table");
$sth->execute;
my @fields;
my $index_type;
# $raw_def will be an arrayref containing the following information:
# 0 = name of the table that the index is on
# 1 = 0 if unique, 1 if not unique
# 2 = name of the index
# 3 = seq_in_index (The order of the current field in the index).
# 4 = Name of ONE column that the index is on
# 5 = 'Collation' of the index. Usually 'A'.
# 6 = Cardinality. Either a number or undef.
# 7 = sub_part. Usually undef. Sometimes 1.
# 8 = "packed". Usually undef.
# 9 = Null. Sometimes undef, sometimes 'YES'.
# 10 = Index_type. The type of the index. Usually either 'BTREE' or 'FULLTEXT'
# 11 = 'Comment.' Usually undef.
while (my $raw_def = $sth->fetchrow_arrayref) {
if ($raw_def->[2] eq $index) {
push(@fields, $raw_def->[4]);
# No index can be both UNIQUE and FULLTEXT, that's why
# this is written this way.
$index_type = $raw_def->[1] ? '' : 'UNIQUE';
$index_type = $raw_def->[10] eq 'FULLTEXT'
? 'FULLTEXT' : $index_type;
}
}
my $retval;
if (scalar(@fields)) {
$retval = {FIELDS => \@fields, TYPE => $index_type};
}
return $retval;
}
=item C<bz_index_list_real($table)>
Description: Returns a list of index names on a table in
the database, as it actually exists on disk.
Params: $table - The name of the table you want info about.
Returns: An array of index names.
=cut
sub bz_index_list_real {
my ($self, $table) = @_;
my $sth = $self->prepare("SHOW INDEX FROM $table");
# Column 3 of a SHOW INDEX statement contains the name of the index.
return @{ $self->selectcol_arrayref($sth, {Columns => [3]}) };
}
#####################################################################
# MySQL-Specific "Schema Builder"
#####################################################################
=back
=head1 MYSQL-SPECIFIC "SCHEMA BUILDER"
MySQL needs to be able to read in a legacy database (from before
Schema existed) and create a Schema object out of it. That's what
this code does.
=end private
=cut
# This sub itself is actually written generically, but the subroutines
# that it depends on are database-specific. In particular, the
# bz_column_info_real function would be very difficult to create
# properly for any other DB besides MySQL.
sub _bz_build_schema_from_disk {
my ($self) = @_;
print "Building Schema object from database...\n";
my $schema = $self->_bz_schema->get_empty_schema();
my @tables = $self->bz_table_list_real();
foreach my $table (@tables) {
$schema->add_table($table);
my @columns = $self->bz_table_columns_real($table);
foreach my $column (@columns) {
my $type_info = $self->bz_column_info_real($table, $column);
$schema->set_column($table, $column, $type_info);
}
my @indexes = $self->bz_index_list_real($table);
foreach my $index (@indexes) {
unless ($index eq 'PRIMARY') {
my $index_info = $self->bz_index_info_real($table, $index);
($index_info = $index_info->{FIELDS})
if (!$index_info->{TYPE});
$schema->set_index($table, $index, $index_info);
}
}
}
return $schema;
}
1;

View File

@@ -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): Dave Miller <davem00@aol.com>
# Gayathri Swaminath <gayathrik00@aol.com>
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
# Dave Lawrence <dkl@redhat.com>
# Tomas Kopal <Tomas.Kopal@altap.cz>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Lance Larsh <lance.larsh@oracle.com>
=head1 NAME
Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL
=head1 DESCRIPTION
This module overrides methods of the Bugzilla::DB module with PostgreSQL
specific implementation. It is instantiated by the Bugzilla::DB module
and should never be used directly.
For interface details see L<Bugzilla::DB> and L<DBI>.
=cut
package Bugzilla::DB::Pg;
use strict;
use Bugzilla::Error;
use DBD::Pg;
# This module extends the DB interface via inheritance
use base qw(Bugzilla::DB);
use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
sub new {
my ($class, $user, $pass, $host, $dbname, $port) = @_;
# The default database name for PostgreSQL. We have
# to connect to SOME database, even if we have
# no $dbname parameter.
$dbname ||= 'template1';
# construct the DSN from the parameters we got
my $dsn = "DBI:Pg:dbname=$dbname";
$dsn .= ";host=$host" if $host;
$dsn .= ";port=$port" if $port;
# This stops Pg from printing out lots of "NOTICE" messages when
# creating tables.
$dsn .= ";options='-c client_min_messages=warning'";
my $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 {
my ($self, $expr, $pattern) = @_;
return "$expr ~* $pattern";
}
sub sql_not_regexp {
my ($self, $expr, $pattern) = @_;
return "$expr !~* $pattern"
}
sub sql_limit {
my ($self, $limit, $offset) = @_;
if (defined($offset)) {
return "LIMIT $limit OFFSET $offset";
} else {
return "LIMIT $limit";
}
}
sub sql_from_days {
my ($self, $days) = @_;
return "TO_TIMESTAMP(${days}::int, 'J')::date";
}
sub sql_to_days {
my ($self, $date) = @_;
return "TO_CHAR(${date}::date, 'J')::int";
}
sub sql_date_format {
my ($self, $date, $format) = @_;
$format = "%Y.%m.%d %H:%i:%s" if !$format;
$format =~ s/\%Y/YYYY/g;
$format =~ s/\%y/YY/g;
$format =~ s/\%m/MM/g;
$format =~ s/\%d/DD/g;
$format =~ s/\%a/Dy/g;
$format =~ s/\%H/HH24/g;
$format =~ s/\%i/MI/g;
$format =~ s/\%s/SS/g;
return "TO_CHAR($date, " . $self->quote($format) . ")";
}
sub sql_interval {
my ($self, $interval, $units) = @_;
return "$interval * INTERVAL '1 $units'";
}
sub sql_string_concat {
my ($self, @params) = @_;
# Postgres 7.3 does not support concatenating of different types, so we
# need to cast both parameters to text. Version 7.4 seems to handle this
# properly, so when we stop support 7.3, this can be removed.
return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
}
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();
}
}
}
# Tell us whether or not a particular sequence exists in the DB.
sub bz_sequence_exists {
my ($self, $seq_name) = @_;
my $exists = $self->selectrow_array(
'SELECT 1 FROM pg_statio_user_sequences WHERE relname = ?',
undef, $seq_name);
return $exists || 0;
}
#####################################################################
# Custom Database Setup
#####################################################################
sub bz_setup_database {
my $self = shift;
$self->SUPER::bz_setup_database(@_);
# PostgreSQL doesn't like having *any* index on the thetext
# field, because it can't have index data longer than 2770
# characters on that field.
$self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
# PostgreSQL also wants an index for calling LOWER on
# login_name, which we do with sql_istrcmp all over the place.
$self->bz_add_index('profiles', 'profiles_login_name_lower_idx',
{FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'});
# Now that Bugzilla::Object uses sql_istrcmp, other tables
# also need a LOWER() index.
_fix_case_differences('fielddefs', 'name');
$self->bz_add_index('fielddefs', 'fielddefs_name_lower_idx',
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
_fix_case_differences('keyworddefs', 'name');
$self->bz_add_index('keyworddefs', 'keyworddefs_name_lower_idx',
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
_fix_case_differences('products', 'name');
$self->bz_add_index('products', 'products_name_lower_idx',
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
# bz_rename_column didn't correctly rename the sequence.
if ($self->bz_column_info('fielddefs', 'id')
&& $self->bz_sequence_exists('fielddefs_fieldid_seq'))
{
print "Fixing fielddefs_fieldid_seq sequence...\n";
$self->do("ALTER TABLE fielddefs_fieldid_seq RENAME TO fielddefs_id_seq");
$self->do("ALTER TABLE fielddefs ALTER COLUMN id
SET DEFAULT NEXTVAL('fielddefs_id_seq')");
}
}
# Renames things that differ only in case.
sub _fix_case_differences {
my ($table, $field) = @_;
my $dbh = Bugzilla->dbh;
my $duplicates = $dbh->selectcol_arrayref(
"SELECT DISTINCT LOWER($field) FROM $table
GROUP BY LOWER($field) HAVING COUNT(LOWER($field)) > 1");
foreach my $name (@$duplicates) {
my $dups = $dbh->selectcol_arrayref(
"SELECT $field FROM $table WHERE LOWER($field) = ?",
undef, $name);
my $primary = shift @$dups;
foreach my $dup (@$dups) {
my $new_name = "${dup}_";
# Make sure the new name isn't *also* a duplicate.
while (1) {
last if (!$dbh->selectrow_array(
"SELECT 1 FROM $table WHERE LOWER($field) = ?",
undef, lc($new_name)));
$new_name .= "_";
}
print "$table '$primary' and '$dup' have names that differ",
" only in case.\nRenaming '$dup' to '$new_name'...\n";
$dbh->do("UPDATE $table SET $field = ? WHERE $field = ?",
undef, $new_name, $dup);
}
}
}
#####################################################################
# Custom Schema Information Functions
#####################################################################
# Pg includes the PostgreSQL system tables in table_list_real, so
# we need to remove those.
sub bz_table_list_real {
my $self = shift;
my @full_table_list = $self->SUPER::bz_table_list_real(@_);
# All PostgreSQL system tables start with "pg_" or "sql_"
my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
return @table_list;
}
1;

File diff suppressed because it is too large Load Diff

View File

@@ -1,347 +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) = @_;
my $charset = Bugzilla->dbh->bz_db_is_utf8 ? "CHARACTER SET utf8" : '';
return($self->SUPER::_get_create_table_ddl($table)
. " ENGINE = MYISAM $charset");
} #eosub--_get_create_table_ddl
#------------------------------------------------------------------------------
sub _get_create_index_ddl {
# Extend superclass method to create FULLTEXT indexes on text fields.
# Returns a "create index" SQL statement.
my($self, $table_name, $index_name, $index_fields, $index_type) = @_;
my $sql = "CREATE ";
$sql .= "$index_type " if ($index_type eq 'UNIQUE'
|| $index_type eq 'FULLTEXT');
$sql .= "INDEX \`$index_name\` ON $table_name \(" .
join(", ", @$index_fields) . "\)";
return($sql);
} #eosub--_get_create_index_ddl
#--------------------------------------------------------------------
sub get_create_database_sql {
my ($self, $name) = @_;
# We only create as utf8 if we have no params (meaning we're doing
# a new installation) or if the utf8 param is on.
my $create_utf8 = Bugzilla->params->{'utf8'}
|| !defined Bugzilla->params->{'utf8'};
my $charset = $create_utf8 ? "CHARACTER SET utf8" : '';
return ("CREATE DATABASE $name $charset");
}
# MySQL has a simpler ALTER TABLE syntax than ANSI.
sub get_alter_column_ddl {
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
my $old_def = $self->get_column($table, $column);
my %new_def_copy = %$new_def;
if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
# If a column stays a primary key do NOT specify PRIMARY KEY in the
# ALTER TABLE statement. This avoids a MySQL error that two primary
# keys are not allowed.
delete $new_def_copy{PRIMARYKEY};
}
my $new_ddl = $self->get_type_ddl(\%new_def_copy);
my @statements;
push(@statements, "UPDATE $table SET $column = $set_nulls_to
WHERE $column IS NULL") if defined $set_nulls_to;
push(@statements, "ALTER TABLE $table CHANGE COLUMN
$column $column $new_ddl");
if ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
# Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
}
return @statements;
}
sub get_drop_index_ddl {
my ($self, $table, $name) = @_;
return ("DROP INDEX \`$name\` ON $table");
}
# A special function for MySQL, for renaming a lot of indexes.
# Index renames is a hash, where the key is a string - the
# old names of the index, and the value is a hash - the index
# definition that we're renaming to, with an extra key of "NAME"
# that contains the new index name.
# The indexes in %indexes must be in hashref format.
sub get_rename_indexes_ddl {
my ($self, $table, %indexes) = @_;
my @keys = keys %indexes or return ();
my $sql = "ALTER TABLE $table ";
foreach my $old_name (@keys) {
my $name = $indexes{$old_name}->{NAME};
my $type = $indexes{$old_name}->{TYPE};
$type ||= 'INDEX';
my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
# $old_name needs to be escaped, sometimes, because it was
# a reserved word.
$old_name = '`' . $old_name . '`';
$sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
}
# Remove the last comma.
chop($sql);
return ($sql);
}
# Converts a DBI column_info output to an abstract column definition.
# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk,
# although there's a chance that it will also work properly if called
# elsewhere.
sub column_info_to_column {
my ($self, $column_info) = @_;
# Unfortunately, we have to break Schema's normal "no database"
# barrier a few times in this function.
my $dbh = Bugzilla->dbh;
my $table = $column_info->{TABLE_NAME};
my $col_name = $column_info->{COLUMN_NAME};
my $column = {};
($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
if ($column_info->{mysql_is_pri_key}) {
# In MySQL, if a table has no PK, but it has a UNIQUE index,
# that index will show up as the PK. So we have to eliminate
# that possibility.
# Unfortunately, the only way to definitely solve this is
# to break Schema's standard of not touching the live database
# and check if the index called PRIMARY is on that field.
my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
if ( $pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}}) ) {
$column->{PRIMARYKEY} = 1;
}
}
# MySQL frequently defines a default for a field even when we
# didn't explicitly set one. So we have to have some special
# hacks to determine whether or not we should actually put
# a default in the abstract schema for this field.
if (defined $column_info->{COLUMN_DEF}) {
# The defaults that MySQL inputs automatically are usually
# something that would be considered "false" by perl, either
# a 0 or an empty string. (Except for datetime and decimal
# fields, which have their own special auto-defaults.)
#
# Here's how we handle this: If it exists in the schema
# without a default, then we don't use the default. If it
# doesn't exist in the schema, then we're either going to
# be dropping it soon, or it's a custom end-user column, in which
# case having a bogus default won't harm anything.
my $schema_column = $self->get_column($table, $col_name);
unless ( (!$column_info->{COLUMN_DEF}
|| $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
|| $column_info->{COLUMN_DEF} eq '0.00')
&& $schema_column
&& !exists $schema_column->{DEFAULT}) {
my $default = $column_info->{COLUMN_DEF};
# Schema uses '0' for the defaults for decimal fields.
$default = 0 if $default =~ /^0\.0+$/;
# If we're not a number, we're a string and need to be
# quoted.
$default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
$column->{DEFAULT} = $default;
}
}
my $type = $column_info->{TYPE_NAME};
# Certain types of columns need the size/precision appended.
if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
# This is nicely lowercase and has the size/precision appended.
$type = $column_info->{mysql_type_name};
}
# If we're a tinyint, we could be either a BOOLEAN or an INT1.
# Only the BOOLEAN_MAP knows the difference.
elsif ($type eq 'TINYINT' && exists BOOLEAN_MAP->{$table}
&& exists BOOLEAN_MAP->{$table}->{$col_name}) {
$type = 'BOOLEAN';
if (exists $column->{DEFAULT}) {
$column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
}
}
# We also need to check if we're an auto_increment field.
elsif ($type =~ /INT/) {
# Unfortunately, the only way to do this in DBI is to query the
# database, so we have to break the rule here that Schema normally
# doesn't touch the live DB.
my $ref_sth = $dbh->prepare(
"SELECT $col_name FROM $table LIMIT 1");
$ref_sth->execute;
if ($ref_sth->{mysql_is_auto_increment}->[0]) {
if ($type eq 'MEDIUMINT') {
$type = 'MEDIUMSERIAL';
}
elsif ($type eq 'SMALLINT') {
$type = 'SMALLSERIAL';
}
else {
$type = 'INTSERIAL';
}
}
$ref_sth->finish;
}
# For all other db-specific types, check if they exist in
# REVERSE_MAPPING and use the type found there.
if (exists REVERSE_MAPPING->{$type}) {
$type = REVERSE_MAPPING->{$type};
}
$column->{TYPE} = $type;
#print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
return $column;
}
sub get_rename_column_ddl {
my ($self, $table, $old_name, $new_name) = @_;
my $def = $self->get_type_ddl($self->get_column($table, $old_name));
# MySQL doesn't like having the PRIMARY KEY statement in a rename.
$def =~ s/PRIMARY KEY//i;
return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
}
1;

View File

@@ -1,166 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
# Edward J. Sabol <edwardjsabol@iname.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::DB::Schema::Pg;
###############################################################################
#
# DB::Schema implementation for PostgreSQL
#
###############################################################################
use strict;
use base qw(Bugzilla::DB::Schema);
use Storable qw(dclone);
#------------------------------------------------------------------------------
sub _initialize {
my $self = shift;
$self = $self->SUPER::_initialize(@_);
# Remove FULLTEXT index types from the schemas.
foreach my $table (keys %{ $self->{schema} }) {
if ($self->{schema}{$table}{INDEXES}) {
foreach my $index (@{ $self->{schema}{$table}{INDEXES} }) {
if (ref($index) eq 'HASH') {
delete($index->{TYPE}) if (exists $index->{TYPE}
&& $index->{TYPE} eq 'FULLTEXT');
}
}
foreach my $index (@{ $self->{abstract_schema}{$table}{INDEXES} }) {
if (ref($index) eq 'HASH') {
delete($index->{TYPE}) if (exists $index->{TYPE}
&& $index->{TYPE} eq 'FULLTEXT');
}
}
}
}
$self->{db_specific} = {
BOOLEAN => 'smallint',
FALSE => '0',
TRUE => '1',
INT1 => 'integer',
INT2 => 'integer',
INT3 => 'integer',
INT4 => 'integer',
SMALLSERIAL => 'serial unique',
MEDIUMSERIAL => 'serial unique',
INTSERIAL => 'serial unique',
TINYTEXT => 'varchar(255)',
MEDIUMTEXT => 'text',
TEXT => 'text',
LONGBLOB => 'bytea',
DATETIME => 'timestamp(0) without time zone',
};
$self->_adjust_schema;
return $self;
} #eosub--_initialize
#--------------------------------------------------------------------
sub get_rename_column_ddl {
my ($self, $table, $old_name, $new_name) = @_;
if (lc($old_name) eq lc($new_name)) {
# if the only change is a case change, return an empty list, since Pg
# is case-insensitive and will return an error about a duplicate name
return ();
}
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
my $def = $self->get_column_abstract($table, $old_name);
if ($def->{TYPE} =~ /SERIAL/i) {
# We have to rename the series also, and fix the default of the series.
push(@sql, "ALTER TABLE ${table}_${old_name}_seq
RENAME TO ${table}_${new_name}_seq");
push(@sql, "ALTER TABLE $table ALTER COLUMN $new_name
SET DEFAULT NEXTVAL('${table}_${new_name}_seq')");
}
return @sql;
}
sub get_rename_table_sql {
my ($self, $old_name, $new_name) = @_;
if (lc($old_name) eq lc($new_name)) {
# if the only change is a case change, return an empty list, since Pg
# is case-insensitive and will return an error about a duplicate name
return ();
}
return ("ALTER TABLE $old_name RENAME TO $new_name");
}
sub _get_alter_type_sql {
my ($self, $table, $column, $new_def, $old_def) = @_;
my @statements;
my $type = $new_def->{TYPE};
$type = $self->{db_specific}->{$type}
if exists $self->{db_specific}->{$type};
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
die("You cannot specify a DEFAULT on a SERIAL-type column.")
if $new_def->{DEFAULT};
$type =~ s/serial/integer/i;
}
# On Pg, you don't need UNIQUE if you're a PK--it creates
# two identical indexes otherwise.
$type =~ s/unique//i if $new_def->{PRIMARYKEY};
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
TYPE $type");
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
push(@statements, "CREATE SEQUENCE ${table}_${column}_seq");
push(@statements, "SELECT setval('${table}_${column}_seq',
MAX($table.$column))
FROM $table");
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
SET DEFAULT nextval('${table}_${column}_seq')");
}
# If this column is no longer SERIAL, we need to drop the sequence
# that went along with it.
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
DROP DEFAULT");
# XXX Pg actually won't let us drop the sequence, even though it's
# no longer in use. So we harmlessly leave behind a sequence
# that does nothing.
#push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
}
return @statements;
}
1;

View File

@@ -1,230 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
# Marc Schumann <wurblzap@gmail.com>
# Frédéric Buclin <LpSolit@gmail.com>
package Bugzilla::Error;
use strict;
use base qw(Exporter);
@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError);
use Bugzilla::Constants;
use Bugzilla::WebService::Constants;
use Bugzilla::Util;
use Date::Format;
# We cannot use $^S to detect if we are in an eval(), because mod_perl
# already eval'uates everything, so $^S = 1 in all cases under mod_perl!
sub _in_eval {
my $in_eval = 0;
for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) {
last if $sub =~ /^ModPerl/;
$in_eval = 1 if $sub =~ /^\(eval\)/;
}
return $in_eval;
}
sub _throw_error {
my ($name, $error, $vars) = @_;
$vars ||= {};
$vars->{error} = $error;
# Make sure any locked tables are unlocked
# and the transaction is rolled back (if supported)
# If we are within an eval(), do not unlock tables as we are
# eval'uating some test on purpose.
Bugzilla->dbh->bz_unlock_tables(UNLOCK_ABORT) unless _in_eval();
my $datadir = bz_locations()->{'datadir'};
# If a writable $datadir/errorlog exists, log error details there.
if (-w "$datadir/errorlog") {
require Data::Dumper;
my $mesg = "";
for (1..75) { $mesg .= "-"; };
$mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
$mesg .= "$name $error ";
$mesg .= "$ENV{REMOTE_ADDR} " if $ENV{REMOTE_ADDR};
$mesg .= Bugzilla->user->login;
$mesg .= (' actually ' . Bugzilla->sudoer->login) if Bugzilla->sudoer;
$mesg .= "\n";
my %params = Bugzilla->cgi->Vars;
$Data::Dumper::Useqq = 1;
for my $param (sort keys %params) {
my $val = $params{$param};
# obscure passwords
$val = "*****" if $param =~ /password/i;
# limit line length
$val =~ s/^(.{512}).*$/$1\[CHOP\]/;
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["param($param)"]);
}
for my $var (sort keys %ENV) {
my $val = $ENV{$var};
$val = "*****" if $val =~ /password|http_pass/i;
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["env($var)"]);
}
open(ERRORLOGFID, ">>$datadir/errorlog");
print ERRORLOGFID "$mesg\n";
close ERRORLOGFID;
}
my $template = Bugzilla->template;
if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
print Bugzilla->cgi->header();
$template->process($name, $vars)
|| ThrowTemplateError($template->error());
}
else {
my $message;
$template->process($name, $vars, \$message)
|| ThrowTemplateError($template->error());
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
die("$message\n");
}
elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
my $code = WS_ERROR_CODE->{$error};
if (!$code) {
$code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
$code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
}
die SOAP::Fault->faultcode($code)->faultstring($message);
}
}
exit;
}
sub ThrowUserError {
_throw_error("global/user-error.html.tmpl", @_);
}
sub ThrowCodeError {
_throw_error("global/code-error.html.tmpl", @_);
}
sub ThrowTemplateError {
my ($template_err) = @_;
# 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->error_mode == ERROR_MODE_DIE) {
die("error: template error: $template_err");
}
$vars->{'template_error_msg'} = $template_err;
$vars->{'error'} = "template_error";
my $template = Bugzilla->template;
# Try a template first; but if this one fails too, fall back
# on plain old print statements.
if (!$template->process("global/code-error.html.tmpl", $vars)) {
my $maintainer = Bugzilla->params->{'maintainer'};
my $error = html_quote($vars->{'template_error_msg'});
my $error2 = html_quote($template->error());
print <<END;
<tt>
<p>
Bugzilla has suffered an internal error. Please save this page and
send it to $maintainer with details of what you were doing at the
time this message appeared.
</p>
<script type="text/javascript"> <!--
document.write("<p>URL: " +
document.location.href.replace(/&/g,"&amp;")
.replace(/</g,"&lt;")
.replace(/>/g,"&gt;") + "</p>");
// -->
</script>
<p>Template->process() failed twice.<br>
First error: $error<br>
Second error: $error2</p>
</tt>
END
}
exit;
}
1;
__END__
=head1 NAME
Bugzilla::Error - Error handling utilities for Bugzilla
=head1 SYNOPSIS
use Bugzilla::Error;
ThrowUserError("error_tag",
{ foo => 'bar' });
=head1 DESCRIPTION
Various places throughout the Bugzilla codebase need to report errors to the
user. The C<Throw*Error> family of functions allow this to be done in a
generic and localizable manner.
These functions automatically unlock the database tables, if there were any
locked. They will also roll back the transaction, if it is supported by
the underlying DB.
=head1 FUNCTIONS
=over 4
=item C<ThrowUserError>
This function takes an error tag as the first argument, and an optional hashref
of variables as a second argument. These are used by the
I<global/user-error.html.tmpl> template to format the error, using the passed
in variables as required.
=item C<ThrowCodeError>
This function is used when an internal check detects an error of some sort.
This usually indicates a bug in Bugzilla, although it can occur if the user
manually constructs urls without correct parameters.
This function's behaviour is similar to C<ThrowUserError>, except that the
template used to display errors is I<global/code-error.html.tmpl>. In addition
if the hashref used as the optional second argument contains a key I<variables>
then the contents of the hashref (which is expected to be another hashref) will
be displayed after the error message, as a debugging aid.
=item C<ThrowTemplateError>
This function should only be called if a C<template-<gt>process()> fails.
It tries another template first, because often one template being
broken or missing doesn't mean that they all are. But it falls back to
a print statement as a last-ditch error.
=back
=head1 SEE ALSO
L<Bugzilla|Bugzilla>

View File

@@ -1,789 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Dan Mosedale <dmose@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
# Myk Melez <myk@mozilla.org>
=head1 NAME
Bugzilla::Field - a particular piece of information about bugs
and useful routines for form field manipulation
=head1 SYNOPSIS
use Bugzilla;
use Data::Dumper;
# Display information about all fields.
print Dumper(Bugzilla->get_fields());
# Display information about non-obsolete custom fields.
print Dumper(Bugzilla->get_fields({ obsolete => 1, custom => 1 }));
# Display a list of the names of non-obsolete custom fields.
print Bugzilla->custom_field_names;
use Bugzilla::Field;
# Display information about non-obsolete custom fields.
# Bugzilla->get_fields() is a wrapper around Bugzilla::Field->match(),
# so both methods take the same arguments.
print Dumper(Bugzilla::Field->match({ obsolete => 1, custom => 1 }));
# Create or update a custom field or field definition.
my $field = Bugzilla::Field->create(
{name => 'cf_silly', description => 'Silly', custom => 1});
# Instantiate a Field object for an existing field.
my $field = new Bugzilla::Field({name => 'qacontact_accessible'});
if ($field->obsolete) {
print $field->description . " is obsolete\n";
}
# Validation Routines
check_field($name, $value, \@legal_values, $no_warn);
$fieldid = get_field_id($fieldname);
=head1 DESCRIPTION
Field.pm defines field objects, which represent the particular pieces
of information that Bugzilla stores about bugs.
This package also provides functions for dealing with CGI form fields.
C<Bugzilla::Field> is an implementation of L<Bugzilla::Object>, and
so provides all of the methods available in L<Bugzilla::Object>,
in addition to what is documented here.
=cut
package Bugzilla::Field;
use strict;
use base qw(Exporter Bugzilla::Object);
@Bugzilla::Field::EXPORT = qw(check_field get_field_id get_legal_field_values);
use Bugzilla::Util;
use Bugzilla::Constants;
use Bugzilla::Error;
###############################
#### Initialization ####
###############################
use constant DB_TABLE => 'fielddefs';
use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => (
'id',
'name',
'description',
'type',
'custom',
'mailhead',
'sortkey',
'obsolete',
'enter_bug',
);
use constant REQUIRED_CREATE_FIELDS => qw(name description);
use constant VALIDATORS => {
custom => \&_check_custom,
description => \&_check_description,
enter_bug => \&_check_enter_bug,
mailhead => \&_check_mailhead,
obsolete => \&_check_obsolete,
sortkey => \&_check_sortkey,
type => \&_check_type,
};
use constant UPDATE_COLUMNS => qw(
description
mailhead
sortkey
obsolete
enter_bug
);
# How various field types translate into SQL data definitions.
use constant SQL_DEFINITIONS => {
# Using commas because these are constants and they shouldn't
# be auto-quoted by the "=>" operator.
FIELD_TYPE_FREETEXT, { TYPE => 'varchar(255)' },
FIELD_TYPE_SINGLE_SELECT, { TYPE => 'varchar(64)', NOTNULL => 1,
DEFAULT => "'---'" },
};
# Field definitions for the fields that ship with Bugzilla.
# These are used by populate_field_definitions to populate
# the fielddefs table.
use constant DEFAULT_FIELDS => (
{name => 'bug_id', desc => 'Bug #', in_new_bugmail => 1},
{name => 'short_desc', desc => 'Summary', in_new_bugmail => 1},
{name => 'classification', desc => 'Classification', in_new_bugmail => 1},
{name => 'product', desc => 'Product', in_new_bugmail => 1},
{name => 'version', desc => 'Version', in_new_bugmail => 1},
{name => 'rep_platform', desc => 'Platform', in_new_bugmail => 1},
{name => 'bug_file_loc', desc => 'URL', in_new_bugmail => 1},
{name => 'op_sys', desc => 'OS/Version', in_new_bugmail => 1},
{name => 'bug_status', desc => 'Status', in_new_bugmail => 1},
{name => 'status_whiteboard', desc => 'Status Whiteboard',
in_new_bugmail => 1},
{name => 'keywords', desc => 'Keywords', in_new_bugmail => 1},
{name => 'resolution', desc => 'Resolution'},
{name => 'bug_severity', desc => 'Severity', in_new_bugmail => 1},
{name => 'priority', desc => 'Priority', in_new_bugmail => 1},
{name => 'component', desc => 'Component', in_new_bugmail => 1},
{name => 'assigned_to', desc => 'AssignedTo', in_new_bugmail => 1},
{name => 'reporter', desc => 'ReportedBy', in_new_bugmail => 1},
{name => 'votes', desc => 'Votes'},
{name => 'qa_contact', desc => 'QAContact', in_new_bugmail => 1},
{name => 'cc', desc => 'CC', in_new_bugmail => 1},
{name => 'dependson', desc => 'Depends on', in_new_bugmail => 1},
{name => 'blocked', desc => 'Blocks', in_new_bugmail => 1},
{name => 'attachments.description', desc => 'Attachment description'},
{name => 'attachments.filename', desc => 'Attachment filename'},
{name => 'attachments.mimetype', desc => 'Attachment mime type'},
{name => 'attachments.ispatch', desc => 'Attachment is patch'},
{name => 'attachments.isobsolete', desc => 'Attachment is obsolete'},
{name => 'attachments.isprivate', desc => 'Attachment is private'},
{name => 'attachments.submitter', desc => 'Attachment creator'},
{name => 'target_milestone', desc => 'Target Milestone'},
{name => 'creation_ts', desc => 'Creation date', in_new_bugmail => 1},
{name => 'delta_ts', desc => 'Last changed date', in_new_bugmail => 1},
{name => 'longdesc', desc => 'Comment'},
{name => 'longdescs.isprivate', desc => 'Comment is private'},
{name => 'alias', desc => 'Alias'},
{name => 'everconfirmed', desc => 'Ever Confirmed'},
{name => 'reporter_accessible', desc => 'Reporter Accessible'},
{name => 'cclist_accessible', desc => 'CC Accessible'},
{name => 'bug_group', desc => 'Group'},
{name => 'estimated_time', desc => 'Estimated Hours', in_new_bugmail => 1},
{name => 'remaining_time', desc => 'Remaining Hours'},
{name => 'deadline', desc => 'Deadline', in_new_bugmail => 1},
{name => 'commenter', desc => 'Commenter'},
{name => 'flagtypes.name', desc => 'Flag'},
{name => 'requestees.login_name', desc => 'Flag Requestee'},
{name => 'setters.login_name', desc => 'Flag Setter'},
{name => 'work_time', desc => 'Hours Worked'},
{name => 'percentage_complete', desc => 'Percentage Complete'},
{name => 'content', desc => 'Content'},
{name => 'attach_data.thedata', desc => 'Attachment data'},
{name => 'attachments.isurl', desc => 'Attachment is a URL'},
{name => "owner_idle_time", desc => "Time Since Assignee Touched"},
);
##############
# Validators #
##############
sub _check_custom { return $_[1] ? 1 : 0; }
sub _check_description {
my ($invocant, $desc) = @_;
$desc = clean_text($desc);
$desc || ThrowUserError('field_missing_description');
return $desc;
}
sub _check_enter_bug { return $_[1] ? 1 : 0; }
sub _check_mailhead { return $_[1] ? 1 : 0; }
sub _check_name {
my ($invocant, $name, $is_custom) = @_;
$name = lc(clean_text($name));
$name || ThrowUserError('field_missing_name');
# Don't want to allow a name that might mess up SQL.
my $name_regex = qr/^[\w\.]+$/;
# Custom fields have more restrictive name requirements than
# standard fields.
$name_regex = qr/^\w+$/ if $is_custom;
# Custom fields can't be named just "cf_", and there is no normal
# field named just "cf_".
($name =~ $name_regex && $name ne "cf_")
|| ThrowUserError('field_invalid_name', { name => $name });
# If it's custom, prepend cf_ to the custom field name to distinguish
# it from standard fields.
if ($name !~ /^cf_/ && $is_custom) {
$name = 'cf_' . $name;
}
# Assure the name is unique. Names can't be changed, so we don't have
# to worry about what to do on updates.
my $field = new Bugzilla::Field({ name => $name });
ThrowUserError('field_already_exists', {'field' => $field }) if $field;
return $name;
}
sub _check_obsolete { return $_[1] ? 1 : 0; }
sub _check_sortkey {
my ($invocant, $sortkey) = @_;
my $skey = $sortkey;
if (!defined $skey || $skey eq '') {
($sortkey) = Bugzilla->dbh->selectrow_array(
'SELECT MAX(sortkey) + 100 FROM fielddefs') || 100;
}
detaint_natural($sortkey)
|| ThrowUserError('field_invalid_sortkey', { sortkey => $skey });
return $sortkey;
}
sub _check_type {
my ($invocant, $type) = @_;
my $saved_type = $type;
# FIELD_TYPE_SINGLE_SELECT here should be updated every time a new,
# higher field type is added.
(detaint_natural($type) && $type <= FIELD_TYPE_SINGLE_SELECT)
|| ThrowCodeError('invalid_customfield_type', { type => $saved_type });
return $type;
}
=pod
=head2 Instance Properties
=over
=item C<name>
the name of the field in the database; begins with "cf_" if field
is a custom field, but test the value of the boolean "custom" property
to determine if a given field is a custom field;
=item C<description>
a short string describing the field; displayed to Bugzilla users
in several places within Bugzilla's UI, f.e. as the form field label
on the "show bug" page;
=back
=cut
sub description { return $_[0]->{description} }
=over
=item C<type>
an integer specifying the kind of field this is; values correspond to
the FIELD_TYPE_* constants in Constants.pm
=back
=cut
sub type { return $_[0]->{type} }
=over
=item C<custom>
a boolean specifying whether or not the field is a custom field;
if true, field name should start "cf_", but use this property to determine
which fields are custom fields;
=back
=cut
sub custom { return $_[0]->{custom} }
=over
=item C<in_new_bugmail>
a boolean specifying whether or not the field is displayed in bugmail
for newly-created bugs;
=back
=cut
sub in_new_bugmail { return $_[0]->{mailhead} }
=over
=item C<sortkey>
an integer specifying the sortkey of the field.
=back
=cut
sub sortkey { return $_[0]->{sortkey} }
=over
=item C<obsolete>
a boolean specifying whether or not the field is obsolete;
=back
=cut
sub obsolete { return $_[0]->{obsolete} }
=over
=item C<enter_bug>
A boolean specifying whether or not this field should appear on
enter_bug.cgi
=back
=cut
sub enter_bug { return $_[0]->{enter_bug} }
=over
=item C<legal_values>
A reference to an array with valid active values for this field.
=back
=cut
sub legal_values {
my $self = shift;
if (!defined $self->{'legal_values'}) {
$self->{'legal_values'} = get_legal_field_values($self->name);
}
return $self->{'legal_values'};
}
=pod
=head2 Instance Mutators
These set the particular field that they are named after.
They take a single value--the new value for that field.
They will throw an error if you try to set the values to something invalid.
=over
=item C<set_description>
=item C<set_enter_bug>
=item C<set_obsolete>
=item C<set_sortkey>
=item C<set_in_new_bugmail>
=back
=cut
sub set_description { $_[0]->set('description', $_[1]); }
sub set_enter_bug { $_[0]->set('enter_bug', $_[1]); }
sub set_obsolete { $_[0]->set('obsolete', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub set_in_new_bugmail { $_[0]->set('mailhead', $_[1]); }
=pod
=head2 Class Methods
=over
=item C<create>
Just like L<Bugzilla::Object/create>. Takes the following parameters:
=over
=item C<name> B<Required> - The name of the field.
=item C<description> B<Required> - The field label to display in the UI.
=item C<mailhead> - boolean - Whether this field appears at the
top of the bugmail for a newly-filed bug. Defaults to 0.
=item C<custom> - boolean - True if this is a Custom Field. The field
will be added to the C<bugs> table if it does not exist. Defaults to 0.
=item C<sortkey> - integer - The sortkey of the field. Defaults to 0.
=item C<enter_bug> - boolean - Whether this field is
editable on the bug creation form. Defaults to 0.
C<obsolete> - boolean - Whether this field is obsolete. Defaults to 0.
=back
=back
=cut
sub create {
my $class = shift;
my $field = $class->SUPER::create(@_);
my $dbh = Bugzilla->dbh;
if ($field->custom) {
my $name = $field->name;
my $type = $field->type;
# Create the database column that stores the data for this field.
$dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type});
if ($type == FIELD_TYPE_SINGLE_SELECT) {
# Create the table that holds the legal values for this field.
$dbh->bz_add_field_table($name);
# And insert a default value of "---" into it.
$dbh->do("INSERT INTO $name (value) VALUES ('---')");
}
}
return $field;
}
sub run_create_validators {
my $class = shift;
my $dbh = Bugzilla->dbh;
my $params = $class->SUPER::run_create_validators(@_);
$params->{name} = $class->_check_name($params->{name}, $params->{custom});
if (!exists $params->{sortkey}) {
$params->{sortkey} = $dbh->selectrow_array(
"SELECT MAX(sortkey) + 100 FROM fielddefs") || 100;
}
return $params;
}
=pod
=over
=item C<match>
=over
=item B<Description>
Returns a list of fields that match the specified criteria.
You should be using L<Bugzilla/get_fields> and
L<Bugzilla/get_custom_field_names> instead of this function.
=item B<Params>
Takes named parameters in a hashref:
=over
=item C<name> - The name of the field.
=item C<custom> - Boolean. True to only return custom fields. False
to only return non-custom fields.
=item C<obsolete> - Boolean. True to only return obsolete fields.
False to not return obsolete fields.
=item C<type> - The type of the field. A C<FIELD_TYPE> constant from
L<Bugzilla::Constants>.
=item C<enter_bug> - Boolean. True to only return fields that appear
on F<enter_bug.cgi>. False to only return fields that I<don't> appear
on F<enter_bug.cgi>.
=back
=item B<Returns>
A reference to an array of C<Bugzilla::Field> objects.
=back
=back
=cut
sub match {
my ($class, $criteria) = @_;
my @terms;
if (defined $criteria->{name}) {
push(@terms, "name=" . Bugzilla->dbh->quote($criteria->{name}));
}
if (defined $criteria->{custom}) {
push(@terms, "custom=" . ($criteria->{custom} ? "1" : "0"));
}
if (defined $criteria->{obsolete}) {
push(@terms, "obsolete=" . ($criteria->{obsolete} ? "1" : "0"));
}
if (defined $criteria->{enter_bug}) {
push(@terms, "enter_bug=" . ($criteria->{enter_bug} ? '1' : '0'));
}
if (defined $criteria->{type}) {
push(@terms, "type = " . $criteria->{type});
}
my $where = (scalar(@terms) > 0) ? "WHERE " . join(" AND ", @terms) : "";
my $ids = Bugzilla->dbh->selectcol_arrayref(
"SELECT id FROM fielddefs $where", {Slice => {}});
return $class->new_from_list($ids);
}
=pod
=over
=item C<get_legal_field_values($field)>
Description: returns all the legal values for a field that has a
list of legal values, like rep_platform or resolution.
The table where these values are stored must at least have
the following columns: value, isactive, sortkey.
Params: C<$field> - Name of the table where valid values are.
Returns: a reference to a list of valid values.
=back
=cut
sub get_legal_field_values {
my ($field) = @_;
my $dbh = Bugzilla->dbh;
my $result_ref = $dbh->selectcol_arrayref(
"SELECT value FROM $field
WHERE isactive = ?
ORDER BY sortkey, value", undef, (1));
return $result_ref;
}
=over
=item C<populate_field_definitions()>
Description: Populates the fielddefs table during an installation
or upgrade.
Params: none
Returns: nothing
=back
=cut
sub populate_field_definitions {
my $dbh = Bugzilla->dbh;
# ADD and UPDATE field definitions
foreach my $def (DEFAULT_FIELDS) {
my $field = new Bugzilla::Field({ name => $def->{name} });
if ($field) {
$field->set_description($def->{desc});
$field->set_in_new_bugmail($def->{in_new_bugmail});
$field->update();
}
else {
if (exists $def->{in_new_bugmail}) {
$def->{mailhead} = $def->{in_new_bugmail};
delete $def->{in_new_bugmail};
}
$def->{description} = $def->{desc};
delete $def->{desc};
Bugzilla::Field->create($def);
}
}
# DELETE fields which were added only accidentally, or which
# were never tracked in bugs_activity. Note that you can never
# delete fields which are used by bugs_activity.
# Oops. Bug 163299
$dbh->do("DELETE FROM fielddefs WHERE name='cc_accessible'");
# Oops. Bug 215319
$dbh->do("DELETE FROM fielddefs WHERE name='requesters.login_name'");
# This field was never tracked in bugs_activity, so it's safe to delete.
$dbh->do("DELETE FROM fielddefs WHERE name='attachments.thedata'");
# MODIFY old field definitions
# 2005-11-13 LpSolit@gmail.com - Bug 302599
# One of the field names was a fragment of SQL code, which is DB dependent.
# We have to rename it to a real name, which is DB independent.
my $new_field_name = 'days_elapsed';
my $field_description = 'Days since bug changed';
my ($old_field_id, $old_field_name) =
$dbh->selectrow_array('SELECT id, name FROM fielddefs
WHERE description = ?',
undef, $field_description);
if ($old_field_id && ($old_field_name ne $new_field_name)) {
print "SQL fragment found in the 'fielddefs' table...\n";
print "Old field name: " . $old_field_name . "\n";
# We have to fix saved searches first. Queries have been escaped
# before being saved. We have to do the same here to find them.
$old_field_name = url_quote($old_field_name);
my $broken_named_queries =
$dbh->selectall_arrayref('SELECT userid, name, query
FROM namedqueries WHERE ' .
$dbh->sql_istrcmp('query', '?', 'LIKE'),
undef, "%=$old_field_name%");
my $sth_UpdateQueries = $dbh->prepare('UPDATE namedqueries SET query = ?
WHERE userid = ? AND name = ?');
print "Fixing saved searches...\n" if scalar(@$broken_named_queries);
foreach my $named_query (@$broken_named_queries) {
my ($userid, $name, $query) = @$named_query;
$query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
$sth_UpdateQueries->execute($query, $userid, $name);
}
# We now do the same with saved chart series.
my $broken_series =
$dbh->selectall_arrayref('SELECT series_id, query
FROM series WHERE ' .
$dbh->sql_istrcmp('query', '?', 'LIKE'),
undef, "%=$old_field_name%");
my $sth_UpdateSeries = $dbh->prepare('UPDATE series SET query = ?
WHERE series_id = ?');
print "Fixing saved chart series...\n" if scalar(@$broken_series);
foreach my $series (@$broken_series) {
my ($series_id, $query) = @$series;
$query =~ s/=\Q$old_field_name\E(&|$)/=$new_field_name$1/gi;
$sth_UpdateSeries->execute($query, $series_id);
}
# Now that saved searches have been fixed, we can fix the field name.
print "Fixing the 'fielddefs' table...\n";
print "New field name: " . $new_field_name . "\n";
$dbh->do('UPDATE fielddefs SET name = ? WHERE id = ?',
undef, ($new_field_name, $old_field_id));
}
# This field has to be created separately, or the above upgrade code
# might not run properly.
Bugzilla::Field->create({ name => $new_field_name,
description => $field_description })
unless new Bugzilla::Field({ name => $new_field_name });
}
=head2 Data Validation
=over
=item C<check_field($name, $value, \@legal_values, $no_warn)>
Description: Makes sure the field $name is defined and its $value
is non empty. If @legal_values is defined, this routine
checks whether its value is one of the legal values
associated with this field, else it checks against
the default valid values for this field obtained by
C<get_legal_field_values($name)>. If the test is successful,
the function returns 1. If the test fails, an error
is thrown (by default), unless $no_warn is true, in which
case the function returns 0.
Params: $name - the field name
$value - the field value
@legal_values - (optional) list of legal values
$no_warn - (optional) do not throw an error if true
Returns: 1 on success; 0 on failure if $no_warn is true (else an
error is thrown).
=back
=cut
sub check_field {
my ($name, $value, $legalsRef, $no_warn) = @_;
my $dbh = Bugzilla->dbh;
# If $legalsRef is undefined, we use the default valid values.
unless (defined $legalsRef) {
$legalsRef = get_legal_field_values($name);
}
if (!defined($value)
|| trim($value) eq ""
|| lsearch($legalsRef, $value) < 0)
{
return 0 if $no_warn; # We don't want an error to be thrown; return.
trick_taint($name);
my $field = new Bugzilla::Field({ name => $name });
my $field_desc = $field ? $field->description : $name;
ThrowCodeError('illegal_field', { field => $field_desc });
}
return 1;
}
=pod
=over
=item C<get_field_id($fieldname)>
Description: Returns the ID of the specified field name and throws
an error if this field does not exist.
Params: $name - a field name
Returns: the corresponding field ID or an error if the field name
does not exist.
=back
=cut
sub get_field_id {
my ($name) = @_;
my $dbh = Bugzilla->dbh;
trick_taint($name);
my $id = $dbh->selectrow_array('SELECT id FROM fielddefs
WHERE name = ?', undef, $name);
ThrowCodeError('invalid_field_name', {field => $name}) unless $id;
return $id
}
1;
__END__

File diff suppressed because it is too large Load Diff

View File

@@ -1,464 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
use strict;
package Bugzilla::FlagType;
=head1 NAME
Bugzilla::FlagType - A module to deal with Bugzilla flag types.
=head1 SYNOPSIS
FlagType.pm provides an interface to flag types as stored in Bugzilla.
See below for more information.
=head1 NOTES
=over
=item *
Use of private functions/variables outside this module may lead to
unexpected results after an upgrade. Please avoid using private
functions in other files/modules. Private functions are functions
whose names start with _ or are specifically noted as being private.
=back
=cut
use Bugzilla::User;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Group;
use base qw(Bugzilla::Object);
###############################
#### Initialization ####
###############################
=begin private
=head1 PRIVATE VARIABLES/CONSTANTS
=over
=item C<DB_COLUMNS>
basic sets of columns and tables for getting flag types from the
database.
=back
=cut
use constant DB_COLUMNS => qw(
flagtypes.id
flagtypes.name
flagtypes.description
flagtypes.cc_list
flagtypes.target_type
flagtypes.sortkey
flagtypes.is_active
flagtypes.is_requestable
flagtypes.is_requesteeble
flagtypes.is_multiplicable
flagtypes.grant_group_id
flagtypes.request_group_id
);
=pod
=over
=item C<DB_TABLE>
Which database(s) is the data coming from?
Note: when adding tables to DB_TABLE, make sure to include the separator
(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take
multiple separators based on the join type, and therefore it is not possible
to join them later using a single known separator.
=back
=end private
=cut
use constant DB_TABLE => 'flagtypes';
use constant LIST_ORDER => 'flagtypes.sortkey, flagtypes.name';
###############################
#### Accessors ######
###############################
=head2 METHODS
=over
=item C<id>
Returns the ID of the flagtype.
=item C<name>
Returns the name of the flagtype.
=item C<description>
Returns the description of the flagtype.
=item C<cc_list>
Returns the concatenated CC list for the flagtype, as a single string.
=item C<target_type>
Returns whether the flagtype applies to bugs or attachments.
=item C<is_active>
Returns whether the flagtype is active or disabled. Flags being
in a disabled flagtype are not deleted. It only prevents you from
adding new flags to it.
=item C<is_requestable>
Returns whether you can request for the given flagtype
(i.e. whether the '?' flag is available or not).
=item C<is_requesteeble>
Returns whether you can ask someone specifically or not.
=item C<is_multiplicable>
Returns whether you can have more than one flag for the given
flagtype in a given bug/attachment.
=item C<sortkey>
Returns the sortkey of the flagtype.
=back
=cut
sub id { return $_[0]->{'id'}; }
sub name { return $_[0]->{'name'}; }
sub description { return $_[0]->{'description'}; }
sub cc_list { return $_[0]->{'cc_list'}; }
sub target_type { return $_[0]->{'target_type'} eq 'b' ? 'bug' : 'attachment'; }
sub is_active { return $_[0]->{'is_active'}; }
sub is_requestable { return $_[0]->{'is_requestable'}; }
sub is_requesteeble { return $_[0]->{'is_requesteeble'}; }
sub is_multiplicable { return $_[0]->{'is_multiplicable'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
###############################
#### Methods ####
###############################
=pod
=over
=item C<grant_group>
Returns the group (as a Bugzilla::Group object) in which a user
must be in order to grant or deny a request.
=item C<request_group>
Returns the group (as a Bugzilla::Group object) in which a user
must be in order to request or clear a flag.
=item C<flag_count>
Returns the number of flags belonging to the flagtype.
=item C<inclusions>
Return a hash of product/component IDs and names
explicitly associated with the flagtype.
=item C<exclusions>
Return a hash of product/component IDs and names
explicitly excluded from the flagtype.
=back
=cut
sub grant_group {
my $self = shift;
if (!defined $self->{'grant_group'} && $self->{'grant_group_id'}) {
$self->{'grant_group'} = new Bugzilla::Group($self->{'grant_group_id'});
}
return $self->{'grant_group'};
}
sub request_group {
my $self = shift;
if (!defined $self->{'request_group'} && $self->{'request_group_id'}) {
$self->{'request_group'} = new Bugzilla::Group($self->{'request_group_id'});
}
return $self->{'request_group'};
}
sub flag_count {
my $self = shift;
if (!defined $self->{'flag_count'}) {
$self->{'flag_count'} =
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM flags
WHERE type_id = ?', undef, $self->{'id'});
}
return $self->{'flag_count'};
}
sub inclusions {
my $self = shift;
$self->{'inclusions'} ||= get_clusions($self->id, 'in');
return $self->{'inclusions'};
}
sub exclusions {
my $self = shift;
$self->{'exclusions'} ||= get_clusions($self->id, 'ex');
return $self->{'exclusions'};
}
######################################################################
# Public Functions
######################################################################
=pod
=head1 PUBLIC FUNCTIONS/METHODS
=over
=item C<get_clusions($id, $type)>
Return a hash of product/component IDs and names
associated with the flagtype:
$clusions{'product_name:component_name'} = "product_ID:component_ID"
=back
=cut
sub get_clusions {
my ($id, $type) = @_;
my $dbh = Bugzilla->dbh;
my $list =
$dbh->selectall_arrayref("SELECT products.id, products.name, " .
" components.id, components.name " .
"FROM flagtypes, flag${type}clusions " .
"LEFT OUTER JOIN products " .
" ON flag${type}clusions.product_id = products.id " .
"LEFT OUTER JOIN components " .
" ON flag${type}clusions.component_id = components.id " .
"WHERE flagtypes.id = ? " .
" AND flag${type}clusions.type_id = flagtypes.id",
undef, $id);
my %clusions;
foreach my $data (@$list) {
my ($product_id, $product_name, $component_id, $component_name) = @$data;
$product_id ||= 0;
$product_name ||= "__Any__";
$component_id ||= 0;
$component_name ||= "__Any__";
$clusions{"$product_name:$component_name"} = "$product_id:$component_id";
}
return \%clusions;
}
=pod
=over
=item C<match($criteria)>
Queries the database for flag types matching the given criteria
and returns a list of matching flagtype objects.
=back
=cut
sub match {
my ($criteria) = @_;
my $dbh = Bugzilla->dbh;
# Depending on the criteria, we may have to append additional tables.
my $tables = [DB_TABLE];
my @criteria = sqlify_criteria($criteria, $tables);
$tables = join(' ', @$tables);
$criteria = join(' AND ', @criteria);
my $flagtype_ids = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria");
return Bugzilla::FlagType->new_from_list($flagtype_ids);
}
=pod
=over
=item C<count($criteria)>
Returns the total number of flag types matching the given criteria.
=back
=cut
sub count {
my ($criteria) = @_;
my $dbh = Bugzilla->dbh;
# Depending on the criteria, we may have to append additional tables.
my $tables = [DB_TABLE];
my @criteria = sqlify_criteria($criteria, $tables);
$tables = join(' ', @$tables);
$criteria = join(' AND ', @criteria);
my $count = $dbh->selectrow_array("SELECT COUNT(flagtypes.id)
FROM $tables WHERE $criteria");
return $count;
}
######################################################################
# Private Functions
######################################################################
=begin private
=head1 PRIVATE FUNCTIONS
=over
=item C<sqlify_criteria($criteria, $tables)>
Converts a hash of criteria into a list of SQL criteria.
$criteria is a reference to the criteria (field => value),
$tables is a reference to an array of tables being accessed
by the query.
=back
=cut
sub sqlify_criteria {
my ($criteria, $tables) = @_;
my $dbh = Bugzilla->dbh;
# the generated list of SQL criteria; "1=1" is a clever way of making sure
# there's something in the list so calling code doesn't have to check list
# size before building a WHERE clause out of it
my @criteria = ("1=1");
if ($criteria->{name}) {
my $name = $dbh->quote($criteria->{name});
trick_taint($name); # Detaint data as we have quoted it.
push(@criteria, "flagtypes.name = $name");
}
if ($criteria->{target_type}) {
# The target type is stored in the database as a one-character string
# ("a" for attachment and "b" for bug), but this function takes complete
# names ("attachment" and "bug") for clarity, so we must convert them.
my $target_type = $criteria->{target_type} eq 'bug'? 'b' : 'a';
push(@criteria, "flagtypes.target_type = '$target_type'");
}
if (exists($criteria->{is_active})) {
my $is_active = $criteria->{is_active} ? "1" : "0";
push(@criteria, "flagtypes.is_active = $is_active");
}
if ($criteria->{product_id} && $criteria->{'component_id'}) {
my $product_id = $criteria->{product_id};
my $component_id = $criteria->{component_id};
# Add inclusions to the query, which simply involves joining the table
# by flag type ID and target product/component.
push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
push(@criteria, "(i.product_id = $product_id OR i.product_id IS NULL)");
push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
# Add exclusions to the query, which is more complicated. First of all,
# we do a LEFT JOIN so we don't miss flag types with no exclusions.
# Then, as with inclusions, we join on flag type ID and target product/
# component. However, since we want flag types that *aren't* on the
# exclusions list, we add a WHERE criteria to use only records with
# NULL exclusion type, i.e. without any exclusions.
my $join_clause = "flagtypes.id = e.type_id " .
"AND (e.product_id = $product_id OR e.product_id IS NULL) " .
"AND (e.component_id = $component_id OR e.component_id IS NULL)";
push(@$tables, "LEFT JOIN flagexclusions AS e ON ($join_clause)");
push(@criteria, "e.type_id IS NULL");
}
if ($criteria->{group}) {
my $gid = $criteria->{group};
detaint_natural($gid);
push(@criteria, "(flagtypes.grant_group_id = $gid " .
" OR flagtypes.request_group_id = $gid)");
}
return @criteria;
}
1;
=end private
=head1 SEE ALSO
=over
=item B<Bugzilla::Flags>
=back
=head1 CONTRIBUTORS
=over
=item Myk Melez <myk@mozilla.org>
=item Kevin Benton <kevin.benton@amd.com>
=item Frédéric Buclin <LpSolit@gmail.com>
=back
=cut

View File

@@ -1,275 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Joel Peshkin <bugreport@peshkin.net>
# Erik Stambaugh <erik@dasbistro.com>
# Tiago R. Mello <timello@async.com.br>
# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
package Bugzilla::Group;
use base qw(Bugzilla::Object);
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Error;
###############################
##### Module Initialization ###
###############################
use constant DB_COLUMNS => qw(
groups.id
groups.name
groups.description
groups.isbuggroup
groups.userregexp
groups.isactive
);
use constant DB_TABLE => 'groups';
use constant LIST_ORDER => 'isbuggroup, name';
use constant VALIDATORS => {
name => \&_check_name,
description => \&_check_description,
userregexp => \&_check_user_regexp,
isbuggroup => \&_check_is_bug_group,
};
use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
###############################
#### Accessors ######
###############################
sub description { return $_[0]->{'description'}; }
sub is_bug_group { return $_[0]->{'isbuggroup'}; }
sub user_regexp { return $_[0]->{'userregexp'}; }
sub is_active { return $_[0]->{'isactive'}; }
###############################
#### Methods ####
###############################
sub is_active_bug_group {
my $self = shift;
return $self->is_active && $self->is_bug_group;
}
sub _rederive_regexp {
my ($self) = @_;
RederiveRegexp($self->user_regexp, $self->id);
}
sub members_non_inherited {
my ($self) = @_;
return $self->{members_non_inherited}
if exists $self->{members_non_inherited};
my $member_ids = Bugzilla->dbh->selectcol_arrayref(
'SELECT DISTINCT user_id FROM user_group_map
WHERE isbless = 0 AND group_id = ?',
undef, $self->id) || [];
require Bugzilla::User;
$self->{members_non_inherited} = Bugzilla::User->new_from_list($member_ids);
return $self->{members_non_inherited};
}
################################
##### Module Subroutines ###
################################
sub create {
my $class = shift;
my ($params) = @_;
my $dbh = Bugzilla->dbh;
print get_text('install_group_create', { name => $params->{name} }) . "\n"
if Bugzilla->usage_mode == USAGE_MODE_CMDLINE;
my $group = $class->SUPER::create(@_);
# Since we created a new group, give the "admin" group all privileges
# initially.
my $admin = new Bugzilla::Group({name => 'admin'});
# This function is also used to create the "admin" group itself,
# so there's a chance it won't exist yet.
if ($admin) {
my $sth = $dbh->prepare('INSERT INTO group_group_map
(member_id, grantor_id, grant_type)
VALUES (?, ?, ?)');
$sth->execute($admin->id, $group->id, GROUP_MEMBERSHIP);
$sth->execute($admin->id, $group->id, GROUP_BLESS);
$sth->execute($admin->id, $group->id, GROUP_VISIBLE);
}
$group->_rederive_regexp() if $group->user_regexp;
return $group;
}
sub ValidateGroupName {
my ($name, @users) = (@_);
my $dbh = Bugzilla->dbh;
my $query = "SELECT id FROM groups " .
"WHERE name = ?";
if (Bugzilla->params->{'usevisibilitygroups'}) {
my @visible = (-1);
foreach my $user (@users) {
$user && push @visible, @{$user->visible_groups_direct};
}
my $visible = join(', ', @visible);
$query .= " AND id IN($visible)";
}
my $sth = $dbh->prepare($query);
$sth->execute($name);
my ($ret) = $sth->fetchrow_array();
return $ret;
}
# This sub is not perldoc'ed because we expect it to go away and
# just become the _rederive_regexp private method.
sub RederiveRegexp {
my ($regexp, $gid) = @_;
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare("SELECT userid, login_name, group_id
FROM profiles
LEFT JOIN user_group_map
ON user_group_map.user_id = profiles.userid
AND group_id = ?
AND grant_type = ?
AND isbless = 0");
my $sthadd = $dbh->prepare("INSERT INTO user_group_map
(user_id, group_id, grant_type, isbless)
VALUES (?, ?, ?, 0)");
my $sthdel = $dbh->prepare("DELETE FROM user_group_map
WHERE user_id = ? AND group_id = ?
AND grant_type = ? and isbless = 0");
$sth->execute($gid, GRANT_REGEXP);
while (my ($uid, $login, $present) = $sth->fetchrow_array()) {
if (($regexp =~ /\S+/) && ($login =~ m/$regexp/i))
{
$sthadd->execute($uid, $gid, GRANT_REGEXP) unless $present;
} else {
$sthdel->execute($uid, $gid, GRANT_REGEXP) if $present;
}
}
}
###############################
### Validators ###
###############################
sub _check_name {
my ($invocant, $name) = @_;
$name = trim($name);
$name || ThrowUserError("empty_group_name");
my $exists = new Bugzilla::Group({name => $name });
ThrowUserError("group_exists", { name => $name }) if $exists;
return $name;
}
sub _check_description {
my ($invocant, $desc) = @_;
$desc = trim($desc);
$desc || ThrowUserError("empty_group_description");
return $desc;
}
sub _check_user_regexp {
my ($invocant, $regex) = @_;
$regex = trim($regex) || '';
ThrowUserError("invalid_regexp") unless (eval {qr/$regex/});
return $regex;
}
sub _check_is_bug_group {
return $_[1] ? 1 : 0;
}
1;
__END__
=head1 NAME
Bugzilla::Group - Bugzilla group class.
=head1 SYNOPSIS
use Bugzilla::Group;
my $group = new Bugzilla::Group(1);
my $group = new Bugzilla::Group({name => 'AcmeGroup'});
my $id = $group->id;
my $name = $group->name;
my $description = $group->description;
my $user_reg_exp = $group->user_reg_exp;
my $is_active = $group->is_active;
my $is_active_bug_group = $group->is_active_bug_group;
my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
my @groups = Bugzilla::Group->get_all;
=head1 DESCRIPTION
Group.pm represents a Bugzilla Group object. It is an implementation
of L<Bugzilla::Object>, and thus has all the methods that L<Bugzilla::Object>
provides, in addition to any methods documented below.
=head1 SUBROUTINES
=over
=item C<create>
Note that in addition to what L<Bugzilla::Object/create($params)>
normally does, this function also makes the new group be inherited
by the C<admin> group. That is, the C<admin> group will automatically
be a member of this group.
=item C<ValidateGroupName($name, @users)>
Description: ValidateGroupName checks to see if ANY of the users
in the provided list of user objects can see the
named group.
Params: $name - String with the group name.
@users - An array with Bugzilla::User objects.
Returns: It returns the group id if successful
and undef otherwise.
=back
=head1 METHODS
=over
=item C<members_non_inherited>
Returns an arrayref of L<Bugzilla::User> objects representing people who are
"directly" in this group, meaning that they're in it because they match
the group regular expression, or they have been actually added to the
group manually.
=back

View File

@@ -1,192 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Zach Lipton <zach@zachlipton.com>
#
package Bugzilla::Hook;
use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Error;
use strict;
sub process {
my ($name, $args) = @_;
# get a list of all extensions
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
# check each extension to see if it uses the hook
# if so, invoke the extension source file:
foreach my $extension (@extensions) {
# all of these variables come directly from code or directory names.
# If there's malicious data here, we have much bigger issues to
# worry about, so we can safely detaint them:
trick_taint($extension);
if (-e $extension.'/code/'.$name.'.pl') {
Bugzilla->hook_args($args);
do($extension.'/code/'.$name.'.pl');
ThrowCodeError('extension_invalid',
{ errstr => $@, name => $name, extension => $extension }) if $@;
# Flush stored data.
Bugzilla->hook_args({});
}
}
}
1;
__END__
=head1 NAME
Bugzilla::Hook - Extendible extension hooks for Bugzilla code
=head1 SYNOPSIS
use Bugzilla::Hook;
Bugzilla::Hook::process("hookname", { arg => $value, arg2 => $value2 });
=head1 DESCRIPTION
Bugzilla allows extension modules to drop in and add routines at
arbitrary points in Bugzilla code. These points are refered to as
hooks. When a piece of standard Bugzilla code wants to allow an extension
to perform additional functions, it uses Bugzilla::Hook's L</process>
subroutine to invoke any extension code if installed.
=head2 How Hooks Work
When a hook named C<HOOK_NAME> is run, Bugzilla will attempt to invoke any
source files named F<extensions/*/code/HOOK_NAME.pl>.
So, for example, if your extension is called "testopia", and you
want to have code run during the L</install-update_db> hook, you
would have a file called F<extensions/testopia/code/install-update_db.pl>
that contained perl code to run during that hook.
=head2 Arguments Passed to Hooks
Some L<hooks|/HOOKS> have params that are passed to them.
These params are accessible through L<Bugzilla/hook_args>.
That returns a hashref. Very frequently, if you want your
hook to do anything, you have to modify these variables.
=head1 SUBROUTINES
=over
=item C<process>
=over
=item B<Description>
Invoke any code hooks with a matching name from any installed extensions.
See C<customization.xml> in the Bugzilla Guide for more information on
Bugzilla's extension mechanism.
=item B<Params>
=over
=item C<$name> - The name of the hook to invoke.
=item C<$args> - A hashref. The named args to pass to the hook.
They will be accessible to the hook via L<Bugzilla/hook_args>.
=back
=item B<Returns> (nothing)
=back
=back
=head1 HOOKS
This describes what hooks exist in Bugzilla currently.
=head2 enter_bug-entrydefaultvars
This happens right before the template is loaded on enter_bug.cgi.
Params:
=over
=item C<vars> - A hashref. The variables that will be passed into the template.
=back
=head2 install-requirements
Because of the way Bugzilla installation works, there can't be a normal
hook during the time that F<checksetup.pl> checks what modules are
installed. (C<Bugzilla::Hook> needs to have those modules installed--it's
a chicken-and-egg problem.)
So instead of the way hooks normally work, this hook just looks for two
subroutines (or constants, since all constants are just subroutines) in
your file, called C<OPTIONAL_MODULES> and C<REQUIRED_MODULES>,
which should return arrayrefs in the same format as C<OPTIONAL_MODULES> and
C<REQUIRED_MODULES> in L<Bugzilla::Install::Requirements>.
These subroutines will be passed an arrayref that contains the current
Bugzilla requirements of the same type, in case you want to modify
Bugzilla's requirements somehow. (Probably the most common would be to
alter a version number or the "feature" element of C<OPTIONAL_MODULES>.)
F<checksetup.pl> will add these requirements to its own.
Please remember--if you put something in C<REQUIRED_MODULES>, then
F<checksetup.pl> B<cannot complete> unless the user has that module
installed! So use C<OPTIONAL_MODULES> whenever you can.
=head2 install-update_db
This happens at the very end of all the tables being updated
during an installation or upgrade. If you need to modify your custom
schema, do it here. No params are passed.
=head2 db_schema-abstract_schema
This allows you to add tables to Bugzilla. Note that we recommend that you
prefix the names of your tables with some word, so that they don't conflict
with any future Bugzilla tables.
If you wish to add new I<columns> to existing Bugzilla tables, do that
in L</install-update_db>.
Params:
=over
=item C<schema> - A hashref, in the format of
L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>. Add new hash keys to make new table
definitions. F<checksetup.pl> will automatically add these tables to the
database when run.
=back

View File

@@ -1,428 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Install;
# Functions in this this package can assume that the database
# has been set up, params are available, localconfig is
# available, and any module can be used.
#
# If you want to write an installation function that can't
# make those assumptions, then it should go into one of the
# packages under the Bugzilla::Install namespace.
use strict;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Group;
use Bugzilla::Product;
use Bugzilla::User;
use Bugzilla::User::Setting;
use Bugzilla::Util qw(get_text);
use Bugzilla::Version;
sub SETTINGS {
return {
# 2005-03-03 travis@sedsystems.ca -- Bug 41972
display_quips => { options => ["on", "off"], default => "on" },
# 2005-03-10 travis@sedsystems.ca -- Bug 199048
comment_sort_order => { options => ["oldest_to_newest", "newest_to_oldest",
"newest_to_oldest_desc_first"],
default => "oldest_to_newest" },
# 2005-05-12 bugzilla@glob.com.au -- Bug 63536
post_bug_submit_action => { options => ["next_bug", "same_bug", "nothing"],
default => "next_bug" },
# 2005-06-29 wurblzap@gmail.com -- Bug 257767
csv_colsepchar => { options => [',',';'], default => ',' },
# 2005-10-26 wurblzap@gmail.com -- Bug 291459
zoom_textareas => { options => ["on", "off"], default => "on" },
# 2005-10-21 LpSolit@gmail.com -- Bug 313020
per_bug_queries => { options => ['on', 'off'], default => 'off' },
# 2006-05-01 olav@bkor.dhs.org -- Bug 7710
state_addselfcc => { options => ['always', 'never', 'cc_unless_role'],
default => 'cc_unless_role' },
# 2006-08-04 wurblzap@gmail.com -- Bug 322693
skin => { subclass => 'Skin', default => 'standard' },
# 2006-12-10 LpSolit@gmail.com -- Bug 297186
lang => { options => [split(/[\s,]+/, Bugzilla->params->{'languages'})],
default => Bugzilla->params->{'defaultlanguage'} }
}
};
use constant SYSTEM_GROUPS => (
{
name => 'admin',
description => 'Administrators'
},
{
name => 'tweakparams',
description => 'Can change Parameters'
},
{
name => 'editusers',
description => 'Can edit or disable users'
},
{
name => 'creategroups',
description => 'Can create and destroy groups'
},
{
name => 'editclassifications',
description => 'Can create, destroy, and edit classifications'
},
{
name => 'editcomponents',
description => 'Can create, destroy, and edit components'
},
{
name => 'editkeywords',
description => 'Can create, destroy, and edit keywords'
},
{
name => 'editbugs',
description => 'Can edit all bug fields',
userregexp => '.*'
},
{
name => 'canconfirm',
description => 'Can confirm a bug or mark it a duplicate'
},
{
name => 'bz_canusewhines',
description => 'User can configure whine reports for self'
},
{
name => 'bz_sudoers',
description => 'Can perform actions as other users'
},
# There are also other groups created in update_system_groups.
);
use constant DEFAULT_CLASSIFICATION => {
name => 'Unclassified',
description => 'Not assigned to any classification'
};
use constant DEFAULT_PRODUCT => {
name => 'TestProduct',
description => 'This is a test product.'
. ' This ought to be blown away and replaced with real stuff in a'
. ' finished installation of bugzilla.'
};
use constant DEFAULT_COMPONENT => {
name => 'TestComponent',
description => 'This is a test component in the test product database.'
. ' This ought to be blown away and replaced with real stuff in'
. ' a finished installation of Bugzilla.'
};
sub update_settings {
my %settings = %{SETTINGS()};
foreach my $setting (keys %settings) {
add_setting($setting,
$settings{$setting}->{options},
$settings{$setting}->{default},
$settings{$setting}->{subclass});
}
}
sub update_system_groups {
my $dbh = Bugzilla->dbh;
# Create most of the system groups
foreach my $definition (SYSTEM_GROUPS) {
my $exists = new Bugzilla::Group({ name => $definition->{name} });
$definition->{isbuggroup} = 0;
Bugzilla::Group->create($definition) unless $exists;
}
# Certain groups need something done after they are created. We do
# that here.
# Make sure people who can whine at others can also whine.
if (!new Bugzilla::Group({name => 'bz_canusewhineatothers'})) {
my $whineatothers = Bugzilla::Group->create({
name => 'bz_canusewhineatothers',
description => 'Can configure whine reports for other users',
isbuggroup => 0 });
my $whine = new Bugzilla::Group({ name => 'bz_canusewhines' });
$dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
VALUES (?,?)', undef, $whine->id, $whineatothers->id);
}
# Make sure sudoers are automatically protected from being sudoed.
if (!new Bugzilla::Group({name => 'bz_sudo_protect'})) {
my $sudo_protect = Bugzilla::Group->create({
name => 'bz_sudo_protect',
description => 'Can not be impersonated by other users',
isbuggroup => 0 });
my $sudo = new Bugzilla::Group({ name => 'bz_sudoers' });
$dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
VALUES (?,?)', undef, $sudo_protect->id, $sudo->id);
}
# Re-evaluate all regexps, to keep them up-to-date.
my $sth = $dbh->prepare(
"SELECT profiles.userid, profiles.login_name, groups.id,
groups.userregexp, user_group_map.group_id
FROM (profiles CROSS JOIN groups)
LEFT JOIN user_group_map
ON user_group_map.user_id = profiles.userid
AND user_group_map.group_id = groups.id
AND user_group_map.grant_type = ?
WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL");
my $sth_add = $dbh->prepare(
"INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
VALUES (?, ?, 0, " . GRANT_REGEXP . ")");
my $sth_del = $dbh->prepare(
"DELETE FROM user_group_map
WHERE user_id = ? AND group_id = ? AND isbless = 0
AND grant_type = " . GRANT_REGEXP);
$sth->execute(GRANT_REGEXP);
while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) {
if ($login =~ m/$rexp/i) {
$sth_add->execute($uid, $gid) unless $present;
} else {
$sth_del->execute($uid, $gid) if $present;
}
}
}
# This function should be called only after creating the admin user.
sub create_default_product {
my $dbh = Bugzilla->dbh;
# Make the default Classification if it doesn't already exist.
if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
my $class = DEFAULT_CLASSIFICATION;
print get_text('install_default_classification',
{ name => $class->{name} }) . "\n";
$dbh->do('INSERT INTO classifications (name, description)
VALUES (?, ?)',
undef, $class->{name}, $class->{description});
}
# And same for the default product/component.
if (!$dbh->selectrow_array('SELECT 1 FROM products')) {
my $default_prod = DEFAULT_PRODUCT;
print get_text('install_default_product',
{ name => $default_prod->{name} }) . "\n";
$dbh->do(q{INSERT INTO products (name, description)
VALUES (?,?)},
undef, $default_prod->{name}, $default_prod->{description});
my $product = new Bugzilla::Product({name => $default_prod->{name}});
# The default version.
Bugzilla::Version::create(Bugzilla::Version::DEFAULT_VERSION, $product);
# And we automatically insert the default milestone.
$dbh->do(q{INSERT INTO milestones (product_id, value, sortkey)
SELECT id, defaultmilestone, 0
FROM products});
# Get the user who will be the owner of the Product.
# We pick the admin with the lowest id, or we insert
# an invalid "0" into the database, just so that we can
# create the component.
my $admin_group = new Bugzilla::Group({name => 'admin'});
my ($admin_id) = $dbh->selectrow_array(
'SELECT user_id FROM user_group_map WHERE group_id = ?
ORDER BY user_id ' . $dbh->sql_limit(1),
undef, $admin_group->id) || 0;
my $default_comp = DEFAULT_COMPONENT;
$dbh->do("INSERT INTO components (name, product_id, description,
initialowner)
VALUES (?, ?, ?, ?)", undef, $default_comp->{name},
$product->id, $default_comp->{description}, $admin_id);
}
}
sub create_admin {
my ($params) = @_;
my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
my $admin_group = new Bugzilla::Group({ name => 'admin' });
my $admin_inheritors =
Bugzilla::User->flatten_group_membership($admin_group->id);
my $admin_group_ids = join(',', @$admin_inheritors);
my ($admin_count) = $dbh->selectrow_array(
"SELECT COUNT(*) FROM user_group_map
WHERE group_id IN ($admin_group_ids)");
return if $admin_count;
my %answer = %{Bugzilla->installation_answers};
my $login = $answer{'ADMIN_EMAIL'};
my $password = $answer{'ADMIN_PASSWORD'};
my $full_name = $answer{'ADMIN_REALNAME'};
if (!$login || !$password || !$full_name) {
print "\n" . get_text('install_admin_setup') . "\n\n";
}
while (!$login) {
print get_text('install_admin_get_email') . ' ';
$login = <STDIN>;
chomp $login;
eval { Bugzilla::User->check_login_name_for_creation($login); };
if ($@) {
print $@ . "\n";
undef $login;
}
}
while (!defined $full_name) {
print get_text('install_admin_get_name') . ' ';
$full_name = <STDIN>;
chomp($full_name);
}
while (!$password) {
# trap a few interrupts so we can fix the echo if we get aborted.
local $SIG{HUP} = \&_create_admin_exit;
local $SIG{INT} = \&_create_admin_exit;
local $SIG{QUIT} = \&_create_admin_exit;
local $SIG{TERM} = \&_create_admin_exit;
system("stty","-echo") unless ON_WINDOWS; # disable input echoing
print get_text('install_admin_get_password') . ' ';
$password = <STDIN>;
chomp $password;
print "\n", get_text('install_admin_get_password2') . ' ';
my $pass2 = <STDIN>;
chomp $pass2;
eval { validate_password($password, $pass2); };
if ($@) {
print "\n$@\n";
undef $password;
}
system("stty","echo") unless ON_WINDOWS;
}
my $admin = Bugzilla::User->create({ login_name => $login,
realname => $full_name,
cryptpassword => $password });
make_admin($admin);
}
sub make_admin {
my ($user) = @_;
my $dbh = Bugzilla->dbh;
$user = ref($user) ? $user
: new Bugzilla::User(login_to_id($user, THROW_ERROR));
my $admin_group = new Bugzilla::Group({ name => 'admin' });
# Admins get explicit membership and bless capability for the admin group
$dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
my $group_insert = $dbh->prepare(
'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
VALUES (?, ?, ?, ?)');
# These are run in an eval so that we can ignore the error of somebody
# already being granted these things.
eval {
$group_insert->execute($user->id, $admin_group->id, 0, GRANT_DIRECT);
};
eval {
$group_insert->execute($user->id, $admin_group->id, 1, GRANT_DIRECT);
};
# Admins should also have editusers directly, even though they'll usually
# inherit it. People could have changed their inheritance structure.
my $editusers = new Bugzilla::Group({ name => 'editusers' });
eval {
$group_insert->execute($user->id, $editusers->id, 0, GRANT_DIRECT);
};
print "\n", get_text('install_admin_created', { user => $user }), "\n";
}
# This is just in case we get interrupted while getting the admin's password.
sub _create_admin_exit {
# re-enable input echoing
system("stty","echo") unless ON_WINDOWS;
exit 1;
}
1;
__END__
=head1 NAME
Bugzilla::Install - Functions and variables having to do with
installation.
=head1 SYNOPSIS
use Bugzilla::Install;
Bugzilla::Install::update_settings();
=head1 DESCRIPTION
This module is used primarily by L<checksetup.pl> during installation.
This module contains functions that deal with general installation
issues after the database is completely set up and configured.
=head1 CONSTANTS
=over
=item C<SETTINGS>
Contains information about Settings, used by L</update_settings()>.
=back
=head1 SUBROUTINES
=over
=item C<update_settings()>
Description: Adds and updates Settings for users.
Params: none
Returns: nothing.
=item C<create_default_product()>
Description: Creates the default product and classification if
they don't exist.
Params: none
Returns: nothing
=back

File diff suppressed because it is too large Load Diff

View File

@@ -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.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
# Bill Barry <after.fallout@gmail.com>
package Bugzilla::Install::Filesystem;
# NOTE: This package may "use" any modules that it likes,
# and localconfig is available. However, all functions in this
# package should assume that:
#
# * Templates are not available.
# * Files do not have the correct permissions.
# * The database does not exist.
use strict;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Install::Localconfig;
use Bugzilla::Util;
use File::Find;
use File::Path;
use File::Basename;
use IO::File;
use POSIX ();
use base qw(Exporter);
our @EXPORT = qw(
update_filesystem
create_htaccess
fix_all_file_permissions
);
# This looks like a constant because it effectively is, but
# it has to call other subroutines and read the current filesystem,
# so it's defined as a sub. This is not exported, so it doesn't have
# a perldoc. However, look at the various hashes defined inside this
# function to understand what it returns. (There are comments throughout.)
#
# The rationale for the file permissions is that the web server generally
# runs as apache, so the cgi scripts should not be writable for apache,
# otherwise someone may find it possible to change the cgis when exploiting
# some security flaw somewhere (not necessarily in Bugzilla!)
sub FILESYSTEM {
my $datadir = bz_locations()->{'datadir'};
my $attachdir = bz_locations()->{'attachdir'};
my $extensionsdir = bz_locations()->{'extensionsdir'};
my $webdotdir = bz_locations()->{'webdotdir'};
my $templatedir = bz_locations()->{'templatedir'};
my $libdir = bz_locations()->{'libpath'};
my $skinsdir = bz_locations()->{'skinsdir'};
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
# The set of permissions that we use:
# FILES
# Executable by the web server
my $ws_executable = $ws_group ? 0750 : 0755;
# Executable by the owner only.
my $owner_executable = 0700;
# Readable by the web server.
my $ws_readable = $ws_group ? 0640 : 0644;
# Readable by the owner only.
my $owner_readable = 0600;
# Writeable by the web server.
my $ws_writeable = $ws_group ? 0660 : 0666;
# DIRECTORIES
# Readable by the web server.
my $ws_dir_readable = $ws_group ? 0750 : 0755;
# Readable only by the owner.
my $owner_dir_readable = 0700;
# Writeable by the web server.
my $ws_dir_writeable = $ws_group ? 0770 : 01777;
# The webserver can overwrite files owned by other users,
# in this directory.
my $ws_dir_full_control = $ws_group ? 0770 : 0777;
# Note: When being processed by checksetup, these have their permissions
# set in this order: %all_dirs, %recurse_dirs, %all_files.
#
# Each is processed in alphabetical order of keys, so shorter keys
# will have their permissions set before longer keys (thus setting
# the permissions on parent directories before setting permissions
# on their children).
# --- FILE PERMISSIONS (Non-created files) --- #
my %files = (
'*' => { perms => $ws_readable },
'*.cgi' => { perms => $ws_executable },
'whineatnews.pl' => { perms => $ws_executable },
'collectstats.pl' => { perms => $ws_executable },
'checksetup.pl' => { perms => $owner_executable },
'importxml.pl' => { perms => $ws_executable },
'runtests.pl' => { perms => $owner_executable },
'testserver.pl' => { perms => $ws_executable },
'whine.pl' => { perms => $ws_executable },
'customfield.pl' => { perms => $owner_executable },
'email_in.pl' => { perms => $ws_executable },
'docs/makedocs.pl' => { perms => $owner_executable },
'docs/rel_notes.txt' => { perms => $ws_readable },
'docs/README.docs' => { perms => $owner_readable },
"$datadir/bugzilla-update.xml" => { perms => $ws_writeable },
"$datadir/params" => { perms => $ws_writeable },
"$datadir/mailer.testfile" => { perms => $ws_writeable },
);
# Directories that we want to set the perms on, but not
# recurse through. These are directories we didn't create
# in checkesetup.pl.
my %non_recurse_dirs = (
'.' => $ws_dir_readable,
docs => $ws_dir_readable,
);
# This sets the permissions for each item inside each of these
# directories, including the directory itself.
# 'CVS' directories are special, though, and are never readable by
# the webserver.
my %recurse_dirs = (
# Writeable directories
"$datadir/template" => { files => $ws_readable,
dirs => $ws_dir_full_control },
$attachdir => { files => $ws_writeable,
dirs => $ws_dir_writeable },
$webdotdir => { files => $ws_writeable,
dirs => $ws_dir_writeable },
graphs => { files => $ws_writeable,
dirs => $ws_dir_writeable },
# Readable directories
"$datadir/mining" => { files => $ws_readable,
dirs => $ws_dir_readable },
"$datadir/duplicates" => { files => $ws_readable,
dirs => $ws_dir_readable },
"$libdir/Bugzilla" => { files => $ws_readable,
dirs => $ws_dir_readable },
$templatedir => { files => $ws_readable,
dirs => $ws_dir_readable },
images => { files => $ws_readable,
dirs => $ws_dir_readable },
css => { files => $ws_readable,
dirs => $ws_dir_readable },
js => { files => $ws_readable,
dirs => $ws_dir_readable },
skins => { files => $ws_readable,
dirs => $ws_dir_readable },
t => { files => $owner_readable,
dirs => $owner_dir_readable },
'docs/html' => { files => $ws_readable,
dirs => $ws_dir_readable },
'docs/pdf' => { files => $ws_readable,
dirs => $ws_dir_readable },
'docs/txt' => { files => $ws_readable,
dirs => $ws_dir_readable },
'docs/images' => { files => $ws_readable,
dirs => $ws_dir_readable },
'docs/lib' => { files => $owner_readable,
dirs => $owner_dir_readable },
'docs/xml' => { files => $owner_readable,
dirs => $owner_dir_readable },
);
# --- FILES TO CREATE --- #
# The name of each directory that we should actually *create*,
# pointing at its default permissions.
my %create_dirs = (
$datadir => $ws_dir_full_control,
"$datadir/mimedump-tmp" => $ws_dir_writeable,
"$datadir/mining" => $ws_dir_readable,
"$datadir/duplicates" => $ws_dir_readable,
$attachdir => $ws_dir_writeable,
$extensionsdir => $ws_dir_readable,
graphs => $ws_dir_writeable,
$webdotdir => $ws_dir_writeable,
'skins/custom' => $ws_dir_readable,
'skins/contrib' => $ws_dir_readable,
);
# The name of each file, pointing at its default permissions and
# default contents.
my %create_files = (
"$datadir/mail" => { perms => $ws_readable },
);
# Each standard stylesheet has an associated custom stylesheet that
# we create. Also, we create placeholders for standard stylesheets
# for contrib skins which don't provide them themselves.
foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) {
next unless -d $skin_dir;
next if basename($skin_dir) =~ /^cvs$/i;
foreach (<$skinsdir/standard/*.css>) {
my $standard_css_file = basename($_);
my $custom_css_file = "$skin_dir/$standard_css_file";
$create_files{$custom_css_file} = { perms => $ws_readable, contents => <<EOT
/*
* Custom rules for $standard_css_file.
* The rules you put here override rules in that stylesheet.
*/
EOT
}
}
}
# Because checksetup controls the creation of index.html separately
# from all other files, it gets its very own hash.
my %index_html = (
'index.html' => { perms => $ws_readable, contents => <<EOT
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Refresh" content="0; URL=index.cgi">
</head>
<body>
<h1>I think you are looking for <a href="index.cgi">index.cgi</a></h1>
</body>
</html>
EOT
}
);
# Because checksetup controls the .htaccess creation separately
# by a localconfig variable, these go in a separate variable from
# %create_files.
my $ht_default_deny = <<EOT;
# nothing in this directory is retrievable unless overridden by an .htaccess
# in a subdirectory
deny from all
EOT
my %htaccess = (
"$attachdir/.htaccess" => { perms => $ws_readable,
contents => $ht_default_deny },
"$libdir/Bugzilla/.htaccess" => { perms => $ws_readable,
contents => $ht_default_deny },
"$templatedir/.htaccess" => { perms => $ws_readable,
contents => $ht_default_deny },
'.htaccess' => { perms => $ws_readable, contents => <<EOT
# Don't allow people to retrieve non-cgi executable files or our private data
<FilesMatch ^(.*\\.pm|.*\\.pl|.*localconfig.*)\$>
deny from all
</FilesMatch>
EOT
},
"$webdotdir/.htaccess" => { perms => $ws_readable, contents => <<EOT
# Restrict access to .dot files to the public webdot server at research.att.com
# if research.att.com ever changes their IP, or if you use a different
# webdot server, you'll need to edit this
<FilesMatch \\.dot\$>
Allow from 192.20.225.0/24
Deny from all
</FilesMatch>
# Allow access to .png files created by a local copy of 'dot'
<FilesMatch \\.png\$>
Allow from all
</FilesMatch>
# And no directory listings, either.
Deny from all
EOT
},
# Even though $datadir may not (and should not) be in the webtree,
# we can't know for sure, so create the .htaccess anyway. It's harmless
# if it's not accessible...
"$datadir/.htaccess" => { perms => $ws_readable, contents => <<EOT
# Nothing in this directory is retrievable unless overridden by an .htaccess
# in a subdirectory; the only exception is duplicates.rdf, which is used by
# duplicates.xul and must be loadable over the web
deny from all
<Files duplicates.rdf>
allow from all
</Files>
EOT
},
);
my %all_files = (%create_files, %htaccess, %index_html, %files);
my %all_dirs = (%create_dirs, %non_recurse_dirs);
return {
create_dirs => \%create_dirs,
recurse_dirs => \%recurse_dirs,
all_dirs => \%all_dirs,
create_files => \%create_files,
htaccess => \%htaccess,
index_html => \%index_html,
all_files => \%all_files,
};
}
sub update_filesystem {
my ($params) = @_;
my $fs = FILESYSTEM();
my %dirs = %{$fs->{create_dirs}};
my %files = %{$fs->{create_files}};
my $datadir = bz_locations->{'datadir'};
# If the graphs/ directory doesn't exist, we're upgrading from
# a version old enough that we need to update the $datadir/mining
# format.
if (-d "$datadir/mining" && !-d 'graphs') {
_update_old_charts($datadir);
}
# By sorting the dirs, we assure that shorter-named directories
# (meaning parent directories) are always created before their
# child directories.
foreach my $dir (sort keys %dirs) {
unless (-d $dir) {
print "Creating $dir directory...\n";
mkdir $dir || die $!;
# For some reason, passing in the permissions to "mkdir"
# doesn't work right, but doing a "chmod" does.
chmod $dirs{$dir}, $dir || die $!;
}
}
_create_files(%files);
if ($params->{index_html}) {
_create_files(%{$fs->{index_html}});
}
elsif (-e 'index.html') {
my $templatedir = bz_locations()->{'templatedir'};
print <<EOT;
*** It appears that you still have an old index.html hanging around.
Either the contents of this file should be moved into a template and
placed in the '$templatedir/en/custom' directory, or you should delete
the file.
EOT
}
# Delete old files that no longer need to exist
# 2001-04-29 jake@bugzilla.org - Remove oldemailtech
# http://bugzilla.mozilla.org/show_bugs.cgi?id=71552
if (-d 'shadow') {
print "Removing shadow directory...\n";
rmtree("shadow");
}
if (-e "$datadir/versioncache") {
print "Removing versioncache...\n";
unlink "$datadir/versioncache";
}
}
sub create_htaccess {
_create_files(%{FILESYSTEM()->{htaccess}});
# Repair old .htaccess files
my $htaccess = new IO::File('.htaccess', 'r') || die ".htaccess: $!";
my $old_data;
{ local $/; $old_data = <$htaccess>; }
$htaccess->close;
my $repaired = 0;
if ($old_data =~ s/\|localconfig\|/\|.*localconfig.*\|/) {
$repaired = 1;
}
if ($old_data !~ /\(\.\*\\\.pm\|/) {
$old_data =~ s/\(/(.*\\.pm\|/;
$repaired = 1;
}
if ($repaired) {
print "Repairing .htaccess...\n";
$htaccess = new IO::File('.htaccess', 'w') || die $!;
print $htaccess $old_data;
$htaccess->close;
}
my $webdot_dir = bz_locations()->{'webdotdir'};
# The public webdot IP address changed.
my $webdot = new IO::File("$webdot_dir/.htaccess", 'r')
|| die "$webdot_dir/.htaccess: $!";
my $webdot_data;
{ local $/; $webdot_data = <$webdot>; }
$webdot->close;
if ($webdot_data =~ /192\.20\.225\.10/) {
print "Repairing $webdot_dir/.htaccess...\n";
$webdot_data =~ s/192\.20\.225\.10/192.20.225.0\/24/g;
$webdot = new IO::File("$webdot_dir/.htaccess", 'w') || die $!;
print $webdot $webdot_data;
$webdot->close;
}
}
# A helper for the above functions.
sub _create_files {
my (%files) = @_;
# It's not necessary to sort these, but it does make the
# output of checksetup.pl look a bit nicer.
foreach my $file (sort keys %files) {
unless (-e $file) {
print "Creating $file...\n";
my $info = $files{$file};
my $fh = new IO::File($file, O_WRONLY | O_CREAT, $info->{perms})
|| die $!;
print $fh $info->{contents} if $info->{contents};
$fh->close;
}
}
}
# If you ran a REALLY old version of Bugzilla, your chart files are in the
# wrong format. This code is a little messy, because it's very old, and
# when moving it into this module, I couldn't test it so I left it almost
# completely alone.
sub _update_old_charts {
my ($datadir) = @_;
print "Updating old chart storage format...\n";
foreach my $in_file (glob("$datadir/mining/*")) {
# Don't try and upgrade image or db files!
next if (($in_file =~ /\.gif$/i) ||
($in_file =~ /\.png$/i) ||
($in_file =~ /\.db$/i) ||
($in_file =~ /\.orig$/i));
rename("$in_file", "$in_file.orig") or next;
open(IN, "$in_file.orig") or next;
open(OUT, '>', $in_file) or next;
# Fields in the header
my @declared_fields;
# Fields we changed to half way through by mistake
# This list comes from an old version of collectstats.pl
# This part is only for people who ran later versions of 2.11 (devel)
my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED
RESOLVED VERIFIED CLOSED);
# Fields we actually want (matches the current collectstats.pl)
my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND
DUPLICATE WORKSFORME MOVED);
while (<IN>) {
if (/^# fields?: (.*)\s$/) {
@declared_fields = map uc, (split /\||\r/, $1);
print OUT "# fields: ", join('|', @out_fields), "\n";
}
elsif (/^(\d+\|.*)/) {
my @data = split(/\||\r/, $1);
my %data;
if (@data == @declared_fields) {
# old format
for my $i (0 .. $#declared_fields) {
$data{$declared_fields[$i]} = $data[$i];
}
}
elsif (@data == @intermediate_fields) {
# Must have changed over at this point
for my $i (0 .. $#intermediate_fields) {
$data{$intermediate_fields[$i]} = $data[$i];
}
}
elsif (@data == @out_fields) {
# This line's fine - it has the right number of entries
for my $i (0 .. $#out_fields) {
$data{$out_fields[$i]} = $data[$i];
}
}
else {
print "Oh dear, input line $. of $in_file had " .
scalar(@data) . " fields\nThis was unexpected.",
" You may want to check your data files.\n";
}
print OUT join('|',
map { defined ($data{$_}) ? ($data{$_}) : "" } @out_fields),
"\n";
}
else {
print OUT;
}
}
close(IN);
close(OUT);
}
}
sub fix_all_file_permissions {
my ($output) = @_;
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
my $group_id = _check_web_server_group($ws_group, $output);
return if ON_WINDOWS;
my $fs = FILESYSTEM();
my %files = %{$fs->{all_files}};
my %dirs = %{$fs->{all_dirs}};
my %recurse_dirs = %{$fs->{recurse_dirs}};
print get_text('install_file_perms_fix') . "\n" if $output;
my $owner_id = POSIX::getuid();
$group_id = POSIX::getgid() unless defined $group_id;
foreach my $dir (sort keys %dirs) {
next unless -d $dir;
_fix_perms($dir, $owner_id, $group_id, $dirs{$dir});
}
foreach my $dir (sort keys %recurse_dirs) {
next unless -d $dir;
# Set permissions on the directory itself.
my $perms = $recurse_dirs{$dir};
_fix_perms($dir, $owner_id, $group_id, $perms->{dirs});
# Now recurse through the directory and set the correct permissions
# on subdirectories and files.
find({ no_chdir => 1, wanted => sub {
my $name = $File::Find::name;
if (-d $name) {
_fix_perms($name, $owner_id, $group_id, $perms->{dirs});
}
else {
_fix_perms($name, $owner_id, $group_id, $perms->{files});
}
}}, $dir);
}
foreach my $file (sort keys %files) {
# %files supports globs
foreach my $filename (glob $file) {
# Don't touch directories.
next if -d $filename || !-e $filename;
_fix_perms($filename, $owner_id, $group_id,
$files{$file}->{perms});
}
}
_fix_cvs_dirs($owner_id, '.');
}
# A helper for fix_all_file_permissions
sub _fix_cvs_dirs {
my ($owner_id, $dir) = @_;
my $owner_gid = POSIX::getgid();
find({ no_chdir => 1, wanted => sub {
my $name = $File::Find::name;
if ($File::Find::dir =~ /\/CVS/ || $_ eq '.cvsignore'
|| (-d $name && $_ eq 'CVS')) {
_fix_perms($name, $owner_id, $owner_gid, 0700);
}
}}, $dir);
}
sub _fix_perms {
my ($name, $owner, $group, $perms) = @_;
#printf ("Changing $name to %o\n", $perms);
chown $owner, $group, $name
|| warn "Failed to change ownership of $name: $!";
chmod $perms, $name
|| warn "Failed to change permissions of $name: $!";
}
sub _check_web_server_group {
my ($group, $output) = @_;
my $filename = bz_locations()->{'localconfig'};
my $group_id;
# If we are on Windows, webservergroup does nothing
if (ON_WINDOWS && $group && $output) {
print "\n\n" . get_text('install_webservergroup_windows') . "\n\n";
}
# If we're not on Windows, make sure that webservergroup isn't
# empty.
elsif (!ON_WINDOWS && !$group && $output) {
print "\n\n" . get_text('install_webservergroup_empty') . "\n\n";
}
# If we're not on Windows, make sure we are actually a member of
# the webservergroup.
elsif (!ON_WINDOWS && $group) {
$group_id = getgrnam($group);
ThrowCodeError('invalid_webservergroup', { group => $group })
unless defined $group_id;
# If on unix, see if we need to print a warning about a webservergroup
# that we can't chgrp to
if ($output && $< != 0 && !grep($_ eq $group_id, split(" ", $)))) {
print "\n\n" . get_text('install_webservergroup_not_in') . "\n\n";
}
}
return $group_id;
}
1;
__END__
=head1 NAME
Bugzilla::Install::Filesystem - Fix up the filesystem during
installation.
=head1 DESCRIPTION
This module is used primarily by L<checksetup.pl> to modify the
filesystem during installation, including creating the data/ directory.
=head1 SUBROUTINES
=over
=item C<update_filesystem({ index_html => 0 })>
Description: Creates all the directories and files that Bugzilla
needs to function but doesn't ship with. Also does
any updates to these files as necessary during an
upgrade.
Params: C<index_html> - Whether or not we should create
the F<index.html> file.
Returns: nothing
=item C<create_htaccess()>
Description: Creates all of the .htaccess files for Apache,
in the various Bugzilla directories. Also updates
the .htaccess files if they need updating.
Params: none
Returns: nothing
=item C<fix_all_file_permissions($output)>
Description: Sets all the file permissions on all of Bugzilla's files
to what they should be. Note that permissions are different
depending on whether or not C<$webservergroup> is set
in F<localconfig>.
Params: C<$output> - C<true> if you want this function to print
out information about what it's doing.
Returns: nothing
=back

View File

@@ -1,471 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Initial Developer of the Original Code is Everything Solved.
# Portions created by Everything Solved are Copyright (C) 2006
# Everything Solved. All Rights Reserved.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Install::Localconfig;
# NOTE: This package may "use" any modules that it likes. However,
# all functions in this package should assume that:
#
# * The data/ directory does not exist.
# * Templates are not available.
# * Files do not have the correct permissions
# * The database is not up to date
use strict;
use Bugzilla::Constants;
use Data::Dumper;
use IO::File;
use Safe;
use base qw(Exporter);
our @EXPORT_OK = qw(
read_localconfig
update_localconfig
);
use constant LOCALCONFIG_VARS => (
{
name => 'create_htaccess',
default => 1,
desc => <<EOT
# If you are using Apache as your web server, Bugzilla can create .htaccess
# files for you that will instruct Apache not to serve files that shouldn't
# be accessed from the web (like your local configuration data and non-cgi
# executable files). For this to work, the directory your Bugzilla
# installation is in must be within the jurisdiction of a <Directory> block
# in the httpd.conf file that has 'AllowOverride Limit' in it. If it has
# 'AllowOverride All' or other options with Limit, that's fine.
# (Older Apache installations may use an access.conf file to store these
# <Directory> blocks.)
# If this is set to 1, Bugzilla will create these files if they don't exist.
# If this is set to 0, Bugzilla will not create these files.
EOT
},
{
name => 'webservergroup',
default => ON_WINDOWS ? '' : 'apache',
desc => q{# This is the group your web server runs as.
# If you have a Windows box, ignore this setting.
# If you do not have access to the group your web server runs under,
# set this to "". If you do set this to "", then your Bugzilla installation
# will be _VERY_ insecure, because some files will be world readable/writable,
# and so anyone who can get local access to your machine can do whatever they
# want. You should only have this set to "" if this is a testing installation
# and you cannot set this up any other way. YOU HAVE BEEN WARNED!
# If you set this to anything other than "", you will need to run checksetup.pl
# as} . ROOT_USER . qq{, or as a user who is a member of the specified group.\n}
},
{
name => 'db_driver',
default => 'mysql',
desc => <<EOT
# What SQL database to use. Default is mysql. List of supported databases
# can be obtained by listing Bugzilla/DB directory - every module corresponds
# to one supported database and the name corresponds to a driver name.
EOT
},
{
name => 'db_host',
default => 'localhost',
desc =>
"# The DNS name of the host that the database server runs on.\n"
},
{
name => 'db_name',
default => 'bugs',
desc => "# The name of the database\n"
},
{
name => 'db_user',
default => 'bugs',
desc => "# Who we connect to the database as.\n"
},
{
name => 'db_pass',
default => '',
desc => <<EOT
# Enter your database password here. It's normally advisable to specify
# a password for your bugzilla database user.
# If you use apostrophe (') or a backslash (\\) in your password, you'll
# need to escape it by preceding it with a '\\' character. (\\') or (\\)
# (Far simpler just not to use those characters.)
EOT
},
{
name => 'db_port',
default => 0,
desc => <<EOT
# Sometimes the database server is running on a non-standard port. If that's
# the case for your database server, set this to the port number that your
# database server is running on. Setting this to 0 means "use the default
# port for my database server."
EOT
},
{
name => 'db_sock',
default => '',
desc => <<EOT
# MySQL Only: Enter a path to the unix socket for MySQL. If this is
# blank, then MySQL's compiled-in default will be used. You probably
# want that.
EOT
},
{
name => 'db_check',
default => 1,
desc => <<EOT
# Should checksetup.pl try to verify that your database setup is correct?
# (with some combinations of database servers/Perl modules/moonphase this
# doesn't work)
EOT
},
{
name => 'index_html',
default => 0,
desc => <<EOT
# With the introduction of a configurable index page using the
# template toolkit, Bugzilla's main index page is now index.cgi.
# Most web servers will allow you to use index.cgi as a directory
# index, and many come preconfigured that way, but if yours doesn't
# then you'll need an index.html file that provides redirection
# to index.cgi. Setting \$index_html to 1 below will allow
# checksetup.pl to create one for you if it doesn't exist.
# NOTE: checksetup.pl will not replace an existing file, so if you
# wish to have checksetup.pl create one for you, you must
# make sure that index.html doesn't already exist
EOT
},
{
name => 'cvsbin',
default => \&_get_default_cvsbin,
desc => <<EOT
# For some optional functions of Bugzilla (such as the pretty-print patch
# viewer), we need the cvs binary to access files and revisions.
# Because it's possible that this program is not in your path, you can specify
# its location here. Please specify the full path to the executable.
EOT
},
{
name => 'interdiffbin',
default => \&_get_default_interdiffbin,
desc => <<EOT
# For some optional functions of Bugzilla (such as the pretty-print patch
# viewer), we need the interdiff binary to make diffs between two patches.
# Because it's possible that this program is not in your path, you can specify
# its location here. Please specify the full path to the executable.
EOT
},
{
name => 'diffpath',
default => \&_get_default_diffpath,
desc => <<EOT
# The interdiff feature needs diff, so we have to have that path.
# Please specify the directory name only; do not use trailing slash.
EOT
},
);
use constant OLD_LOCALCONFIG_VARS => qw(
mysqlpath
contenttypes
pages
severities platforms opsys priorities
);
sub read_localconfig {
my ($include_deprecated) = @_;
my $filename = bz_locations()->{'localconfig'};
my %localconfig;
if (-e $filename) {
my $s = new Safe;
# Some people like to store their database password in another file.
$s->permit('dofile');
$s->rdo($filename);
if ($@ || $!) {
my $err_msg = $@ ? $@ : $!;
die <<EOT;
An error has occurred while reading your 'localconfig' file. The text of
the error message is:
$err_msg
Please fix the error in your 'localconfig' file. Alternately, rename your
'localconfig' file, rerun checksetup.pl, and re-enter your answers.
\$ mv -f localconfig localconfig.old
\$ ./checksetup.pl
EOT
}
my @vars = map($_->{name}, LOCALCONFIG_VARS);
push(@vars, OLD_LOCALCONFIG_VARS) if $include_deprecated;
foreach my $var (@vars) {
my $glob = $s->varglob($var);
# We can't get the type of a variable out of a Safe automatically.
# We can only get the glob itself. So we figure out its type this
# way, by trying first a scalar, then an array, then a hash.
#
# The interesting thing is that this converts all deprecated
# array or hash vars into hashrefs or arrayrefs, but that's
# fine since as I write this all modern localconfig vars are
# actually scalars.
if (defined $$glob) {
$localconfig{$var} = $$glob;
}
elsif (defined @$glob) {
$localconfig{$var} = \@$glob;
}
elsif (defined %$glob) {
$localconfig{$var} = \%$glob;
}
}
}
return \%localconfig;
}
#
# This is quite tricky. But fun!
#
# First we read the file 'localconfig'. Then we check if the variables we
# need are defined. If not, we will append the new settings to
# localconfig, instruct the user to check them, and stop.
#
# Why do it this way?
#
# Assume we will enhance Bugzilla and eventually more local configuration
# stuff arises on the horizon.
#
# But the file 'localconfig' is not in the Bugzilla CVS or tarfile. You
# know, we never want to overwrite your own version of 'localconfig', so
# we can't put it into the CVS/tarfile, can we?
#
# Now, when we need a new variable, we simply add the necessary stuff to
# LOCALCONFIG_VARS. When the user gets the new version of Bugzilla from CVS and
# runs checksetup, it finds out "Oh, there is something new". Then it adds
# some default value to the user's local setup and informs the user to
# check that to see if it is what the user wants.
#
# Cute, ey?
#
sub update_localconfig {
my ($params) = @_;
my $output = $params->{output} || 0;
my $answer = Bugzilla->installation_answers;
my $localconfig = read_localconfig('include deprecated');
my @new_vars;
foreach my $var (LOCALCONFIG_VARS) {
my $name = $var->{name};
if (!defined $localconfig->{$name}) {
push(@new_vars, $name);
$var->{default} = &{$var->{default}} if ref($var->{default}) eq 'CODE';
$localconfig->{$name} = $answer->{$name} || $var->{default};
}
}
my @old_vars;
foreach my $name (OLD_LOCALCONFIG_VARS) {
push(@old_vars, $name) if defined $localconfig->{$name};
}
if (!$localconfig->{'interdiffbin'} && $output) {
print <<EOT
OPTIONAL NOTE: If you want to be able to use the 'difference between two
patches' feature of Bugzilla (which requires the PatchReader Perl module
as well), you should install patchutils from:
http://cyberelk.net/tim/patchutils/
EOT
}
my $filename = bz_locations->{'localconfig'};
if (scalar @old_vars) {
my $oldstuff = join(', ', @old_vars);
print <<EOT
The following variables are no longer used in $filename, and
should be removed: $oldstuff
EOT
}
if (scalar @new_vars) {
my $filename = bz_locations->{'localconfig'};
my $fh = new IO::File($filename, '>>') || die "$filename: $!";
$fh->seek(0, SEEK_END);
foreach my $var (LOCALCONFIG_VARS) {
if (grep($_ eq $var->{name}, @new_vars)) {
print $fh "\n", $var->{desc},
Data::Dumper->Dump([$localconfig->{$var->{name}}],
["*$var->{name}"]);
}
}
my $newstuff = join(', ', @new_vars);
print <<EOT;
This version of Bugzilla contains some variables that you may want to
change and adapt to your local settings. Please edit the file
$filename and rerun checksetup.pl.
The following variables are new to $filename since you last ran
checksetup.pl: $newstuff
EOT
exit;
}
# Reset the cache for Bugzilla->localconfig so that it will be re-read
delete Bugzilla->request_cache->{localconfig};
return { old_vars => \@old_vars, new_vars => \@new_vars };
}
sub _get_default_cvsbin {
return '' if ON_WINDOWS;
my $cvs_executable = `which cvs`;
if ($cvs_executable =~ /no cvs/ || $cvs_executable eq '') {
# If which didn't find it, just set to blank
$cvs_executable = "";
} else {
chomp $cvs_executable;
}
return $cvs_executable;
}
sub _get_default_interdiffbin {
return '' if ON_WINDOWS;
my $interdiff = `which interdiff`;
if ($interdiff =~ /no interdiff/ || $interdiff eq '') {
# If which didn't find it, just set to blank
$interdiff = '';
} else {
chomp $interdiff;
}
return $interdiff;
}
sub _get_default_diffpath {
return '' if ON_WINDOWS;
my $diff_binaries;
$diff_binaries = `which diff`;
if ($diff_binaries =~ /no diff/ || $diff_binaries eq '') {
# If which didn't find it, set to blank
$diff_binaries = "";
} else {
$diff_binaries =~ s:/diff\n$::;
}
return $diff_binaries;
}
1;
__END__
=head1 NAME
Bugzilla::Install::Localconfig - Functions and variables dealing
with the manipulation and creation of the F<localconfig> file.
=head1 SYNOPSIS
use Bugzilla::Install::Requirements qw(update_localconfig);
update_localconfig({ output => 1 });
=head1 DESCRIPTION
This module is used primarily by L<checksetup.pl> to create and
modify the localconfig file. Most scripts should use L<Bugzilla/localconfig>
to access localconfig variables.
=head1 CONSTANTS
=over
=item C<LOCALCONFIG_VARS>
An array of hashrefs. These hashrefs contain three keys:
name - The name of the variable.
default - The default value for the variable. Should always be
something that can fit in a scalar.
desc - Additional text to put in localconfig before the variable
definition. Must end in a newline. Each line should start
with "#" unless you have some REALLY good reason not
to do that.
=item C<OLD_LOCALCONFIG_VARS>
An array of names of variables. If C<update_localconfig> finds these
variables defined in localconfig, it will print out a warning.
=back
=head1 SUBROUTINES
=over
=item C<read_localconfig($include_deprecated)>
Description: Reads the localconfig file and returns all valid
values in a hashref.
Params: C<$include_deprecated> - C<true> if you want the returned
hashref to also include variables listed in
C<OLD_LOCALCONFIG_VARS>, if they exist. Generally
this is only for use by C<update_localconfig>.
Returns: A hashref of the localconfig variables. If an array
is defined, it will be an arrayref in the returned hash. If a
hash is defined, it will be a hashref in the returned hash.
Only includes variables specified in C<LOCALCONFIG_VARS>
(and C<OLD_LOCALCONFIG_VARS> if C<$include_deprecated> is
specified).
=item C<update_localconfig({ output =E<gt> 1 })>
Description: Adds any new variables to localconfig that aren't
currently defined there. Also optionally prints out
a message about vars that *should* be there and aren't.
Exits the program if it adds any new vars.
Params: C<output> - C<true> if the function should display informational
output and warnings. It will always display errors or
any message which would cause program execution to halt.
Returns: A hashref, with C<old_vals> being an array of names of variables
that were removed, and C<new_vals> being an array of names
of variables that were added to localconfig.
=back

View File

@@ -1,726 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
# Marc Schumann <wurblzap@gmail.com>
package Bugzilla::Install::Requirements;
# NOTE: This package MUST NOT "use" any Bugzilla modules other than
# Bugzilla::Constants, anywhere. We may "use" standard perl modules.
#
# Subroutines may "require" and "import" from modules, but they
# MUST NOT "use."
use strict;
use List::Util qw(max);
use POSIX ();
use Safe;
use base qw(Exporter);
our @EXPORT = qw(
REQUIRED_MODULES
OPTIONAL_MODULES
check_requirements
check_graphviz
display_version_and_os
have_vers
vers_cmp
install_command
);
use Bugzilla::Constants;
# The below two constants are subroutines so that they can implement
# a hook. Other than that they are actually constants.
# "package" is the perl package we're checking for. "module" is the name
# of the actual module we load with "require" to see if the package is
# installed or not. "version" is the version we need, or 0 if we'll accept
# any version.
#
# "blacklist" is an arrayref of regular expressions that describe versions that
# are 'blacklisted'--that is, even if the version is high enough, Bugzilla
# will refuse to say that it's OK to run with that version.
sub REQUIRED_MODULES {
my $perl_ver = sprintf('%vd', $^V);
my @modules = (
{
package => 'CGI.pm',
module => 'CGI',
# Perl 5.10 requires CGI 3.33 due to a taint issue when
# uploading attachments, see bug 416382.
version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '2.93'
},
{
package => 'TimeDate',
module => 'Date::Format',
version => '2.21'
},
{
package => 'DBI',
module => 'DBI',
version => '1.41'
},
{
package => 'PathTools',
module => 'File::Spec',
version => '0.84'
},
{
package => 'Template-Toolkit',
module => 'Template',
version => '2.12'
},
{
package => 'Email-Send',
module => 'Email::Send',
version => ON_WINDOWS ? '2.16' : '2.00'
},
{
# This will pull in Email::MIME for us, also.
package => 'Email-MIME-Modifier',
module => 'Email::MIME::Modifier',
version => 0
},
);
my $all_modules = _get_extension_requirements(
'REQUIRED_MODULES', \@modules);
return $all_modules;
};
sub OPTIONAL_MODULES {
my @modules = (
{
package => 'GD',
module => 'GD',
version => '1.20',
feature => 'Graphical Reports, New Charts, Old Charts'
},
{
package => 'Template-GD',
# This module tells us whether or not Template-GD is installed
# on Template-Toolkits after 2.14, and still works with 2.14 and lower.
module => 'Template::Plugin::GD::Image',
version => 0,
feature => 'Graphical Reports'
},
{
package => 'Chart',
module => 'Chart::Base',
version => '1.0',
feature => 'New Charts, Old Charts'
},
{
package => 'GDGraph',
module => 'GD::Graph',
version => 0,
feature => 'Graphical Reports'
},
{
package => 'GDTextUtil',
module => 'GD::Text',
version => 0,
feature => 'Graphical Reports'
},
{
package => 'XML-Twig',
module => 'XML::Twig',
version => 0,
feature => 'Move Bugs Between Installations'
},
{
package => 'MIME-tools',
# MIME::Parser is packaged as MIME::Tools on ActiveState Perl
module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
version => '5.406',
feature => 'Move Bugs Between Installations'
},
{
package => 'libwww-perl',
module => 'LWP::UserAgent',
version => 0,
feature => 'Automatic Update Notifications'
},
{
package => 'PatchReader',
module => 'PatchReader',
version => '0.9.4',
feature => 'Patch Viewer'
},
{
package => 'PerlMagick',
module => 'Image::Magick',
version => 0,
feature => 'Optionally Convert BMP Attachments to PNGs'
},
{
package => 'perl-ldap',
module => 'Net::LDAP',
version => 0,
feature => 'LDAP Authentication'
},
{
package => 'SOAP-Lite',
module => 'SOAP::Lite',
version => 0,
feature => 'XML-RPC Interface'
},
{
# We need the 'utf8_mode' method of HTML::Parser, for HTML::Scrubber.
package => 'HTML-Parser',
module => 'HTML::Parser',
version => '3.40',
feature => 'More HTML in Product/Group Descriptions'
},
{
package => 'HTML-Scrubber',
module => 'HTML::Scrubber',
version => 0,
feature => 'More HTML in Product/Group Descriptions'
},
# Inbound Email
{
package => 'Email-MIME-Attachment-Stripper',
module => 'Email::MIME::Attachment::Stripper',
version => 0,
feature => 'Inbound Email'
},
{
package => 'Email-Reply',
module => 'Email::Reply',
version => 0,
feature => 'Inbound Email'
},
# mod_perl
{
package => 'mod_perl',
module => 'mod_perl2',
version => '1.999022',
feature => 'mod_perl'
},
);
# Even very new releases of perl (5.8.5) don't come with this version,
# so I didn't want to make it a general requirement just for
# running under mod_cgi.
# If Perl 5.10 is installed, then CGI 3.33 is already required. So this
# check is only relevant with Perl 5.8.x.
my $perl_ver = sprintf('%vd', $^V);
if (vers_cmp($perl_ver, '5.10') < 0) {
push(@modules, { package => 'CGI.pm',
module => 'CGI',
version => '3.11',
feature => 'mod_perl' });
}
my $all_modules = _get_extension_requirements(
'OPTIONAL_MODULES', \@modules);
return $all_modules;
};
# This implements the install-requirements hook described in Bugzilla::Hook.
sub _get_extension_requirements {
my ($function, $base_modules) = @_;
my @all_modules;
# get a list of all extensions
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
foreach my $extension (@extensions) {
my $file = "$extension/code/install-requirements.pl";
if (-e $file) {
my $safe = new Safe;
# This is a very liberal Safe.
$safe->permit(qw(:browse require entereval caller));
$safe->rdo($file);
if ($@) {
warn $@;
next;
}
my $modules = eval { &{$safe->varglob($function)}($base_modules) };
next unless $modules;
push(@all_modules, @$modules);
}
}
unshift(@all_modules, @$base_modules);
return \@all_modules;
};
sub check_requirements {
my ($output) = @_;
print "\nChecking perl modules...\n" if $output;
my $root = ROOT_USER;
my %missing = _check_missing(REQUIRED_MODULES, $output);
print "\nChecking available perl DBD modules...\n" if $output;
my $have_one_dbd = 0;
my $db_modules = DB_MODULE;
foreach my $db (keys %$db_modules) {
my $dbd = $db_modules->{$db}->{dbd};
$have_one_dbd = 1 if have_vers($dbd, $output);
}
print "\nThe following Perl modules are optional:\n" if $output;
my %missing_optional = _check_missing(OPTIONAL_MODULES, $output);
# If we're running on Windows, reset the input line terminator so that
# console input works properly - loading CGI tends to mess it up
$/ = "\015\012" if ON_WINDOWS;
my $pass = !scalar(keys %missing) && $have_one_dbd;
return {
pass => $pass,
one_dbd => $have_one_dbd,
missing => \%missing,
optional => \%missing_optional,
any_missing => !$pass || scalar(keys %missing_optional),
};
}
# A helper for check_requirements
sub _check_missing {
my ($modules, $output) = @_;
my %missing;
foreach my $module (@$modules) {
unless (have_vers($module, $output)) {
$missing{$module->{package}} = $module;
}
}
return %missing;
}
# Returns the build ID of ActivePerl. If several versions of
# ActivePerl are installed, it won't be able to know which one
# you are currently running. But that's our best guess.
sub _get_activestate_build_id {
eval 'use Win32::TieRegistry';
return 0 if $@;
my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
or return 0;
return $key->GetValue("CurrentVersion");
}
sub print_module_instructions {
my ($check_results, $output) = @_;
# We only print these notes if we have to.
if ((!$output && %{$check_results->{missing}})
|| ($output && $check_results->{any_missing}))
{
print "\n* NOTE: You must run any commands listed below as "
. ROOT_USER . ".\n\n";
if (ON_WINDOWS) {
my $perl_ver = sprintf('%vd', $^V);
# URL when running Perl 5.8.x.
my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
my $repo_up_cmd =
'* *';
# Packages for Perl 5.10 are not compatible with Perl 5.8.
if (vers_cmp($perl_ver, '5.10') > -1) {
$url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
}
# ActivePerl older than revision 819 require an additional command.
if (_get_activestate_build_id() < 819) {
$repo_up_cmd = <<EOT;
* *
* Then you have to do (also as an Administrator): *
* *
* ppm repo up theory58S *
* *
* Do that last command over and over until you see "theory58S" at the *
* top of the displayed list. *
EOT
}
print <<EOT;
***********************************************************************
* Note For Windows Users *
***********************************************************************
* In order to install the modules listed below, you first have to run *
* the following command as an Administrator: *
* *
* ppm repo add theory58S $url_to_theory58S
$repo_up_cmd
***********************************************************************
EOT
}
}
# Required Modules
if (my %missing = %{$check_results->{missing}}) {
print <<EOT;
***********************************************************************
* REQUIRED MODULES *
***********************************************************************
* Bugzilla requires you to install some Perl modules which are either *
* missing from your system, or the version on your system is too old. *
* *
* The latest versions of each module can be installed by running the *
* commands below. *
***********************************************************************
EOT
print "COMMANDS:\n\n";
foreach my $package (keys %missing) {
my $command = install_command($missing{$package});
print " $command\n";
}
print "\n";
}
if (!$check_results->{one_dbd}) {
print <<EOT;
***********************************************************************
* DATABASE ACCESS *
***********************************************************************
* In order to access your database, Bugzilla requires that the *
* correct "DBD" module be installed for the database that you are *
* running. *
* *
* Pick and run the correct command below for the database that you *
* plan to use with Bugzilla. *
***********************************************************************
COMMANDS:
EOT
my %db_modules = %{DB_MODULE()};
foreach my $db (keys %db_modules) {
my $command = install_command($db_modules{$db}->{dbd});
printf "%10s: \%s\n", $db_modules{$db}->{name}, $command;
print ' ' x 12 . "Minimum version required: "
. $db_modules{$db}->{dbd}->{version} . "\n";
}
print "\n";
}
return unless $output;
if (my %missing = %{$check_results->{optional}}) {
print <<EOT;
**********************************************************************
* OPTIONAL MODULES *
**********************************************************************
* Certain Perl modules are not required by Bugzilla, but by *
* installing the latest version you gain access to additional *
* features. *
* *
* The optional modules you do not have installed are listed below, *
* with the name of the feature they enable. If you want to install *
* one of these modules, just run the appropriate command in the *
* "COMMANDS TO INSTALL" section. *
**********************************************************************
EOT
# We want to sort them so that they are ordered by feature.
my @missing_names = sort {$missing{$a}->{feature}
cmp $missing{$b}->{feature}} (keys %missing);
# Now we have to determine how large the table cols will be.
my $longest_name = max(map(length($_), @missing_names));
# The first column header is at least 11 characters long.
$longest_name = 11 if $longest_name < 11;
# The table is 71 characters long. There are seven mandatory
# characters (* and space) in the string. So, we have a total
# of 64 characters to work with.
my $remaining_space = 64 - $longest_name;
print '*' x 71 . "\n";
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
'MODULE NAME', 'ENABLES FEATURE(S)';
print '*' x 71 . "\n";
foreach my $name (@missing_names) {
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
$name, $missing{$name}->{feature};
}
print '*' x 71 . "\n";
print "COMMANDS TO INSTALL:\n\n";
foreach my $module (@missing_names) {
my $command = install_command($missing{$module});
printf "%15s: $command\n", $module;
}
}
}
sub check_graphviz {
my ($output) = @_;
return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);
printf("Checking for %15s %-9s ", "GraphViz", "(any)") if $output;
my $return = 0;
if(-x Bugzilla->params->{'webdotbase'}) {
print "ok: found\n" if $output;
$return = 1;
} else {
print "not a valid executable: " . Bugzilla->params->{'webdotbase'} . "\n";
}
my $webdotdir = bz_locations()->{'webdotdir'};
# Check .htaccess allows access to generated images
if (-e "$webdotdir/.htaccess") {
my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
|| die "$webdotdir/.htaccess: " . $!;
if (!grep(/png/, $htaccess->getlines)) {
print "Dependency graph images are not accessible.\n";
print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
}
$htaccess->close;
}
return $return;
}
sub display_version_and_os {
# Display version information
printf "\n* This is Bugzilla " . BUGZILLA_VERSION . " on perl %vd\n",
$^V;
my @os_details = POSIX::uname;
# 0 is the name of the OS, 2 is the major version,
my $os_name = $os_details[0] . ' ' . $os_details[2];
if (ON_WINDOWS) {
require Win32;
$os_name = Win32::GetOSName();
}
# 3 is the minor version.
print "* Running on $os_name $os_details[3]\n"
}
# This was originally clipped from the libnet Makefile.PL, adapted here to
# use the below vers_cmp routine for accurate version checking.
sub have_vers {
my ($params, $output) = @_;
my $module = $params->{module};
my $package = $params->{package};
if (!$package) {
$package = $module;
$package =~ s/::/-/g;
}
my $wanted = $params->{version};
my ($msg, $vnum, $vstr);
no strict 'refs';
printf("Checking for %15s %-9s ", $package, !$wanted?'(any)':"(v$wanted)")
if $output;
eval "require $module;";
# VERSION is provided by UNIVERSAL::
$vnum = eval { $module->VERSION } || -1;
# CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
# That breaks the standard version tests, so we need to manually correct
# the version
if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
$vnum = $1 . "." . $2;
}
if ($vnum eq "-1") { # string compare just in case it's non-numeric
$vstr = "not found";
}
elsif (vers_cmp($vnum,"0") > -1) {
$vstr = "found v$vnum";
}
else {
$vstr = "found unknown version";
}
my $vok = (vers_cmp($vnum,$wanted) > -1);
my $blacklisted;
if ($vok && $params->{blacklist}) {
$blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
$vok = 0 if $blacklisted;
}
my $ok = $vok ? "ok:" : "";
my $black_string = $blacklisted ? "(blacklisted)" : "";
print "$ok $vstr $black_string\n" if $output;
return $vok ? 1 : 0;
}
# This is taken straight from Sort::Versions 1.5, which is not included
# with perl by default.
sub vers_cmp {
my ($a, $b) = @_;
# Remove leading zeroes - Bug 344661
$a =~ s/^0*(\d.+)/$1/;
$b =~ s/^0*(\d.+)/$1/;
my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g);
my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g);
my ($A, $B);
while (@A and @B) {
$A = shift @A;
$B = shift @B;
if ($A eq '-' and $B eq '-') {
next;
} elsif ( $A eq '-' ) {
return -1;
} elsif ( $B eq '-') {
return 1;
} elsif ($A eq '.' and $B eq '.') {
next;
} elsif ( $A eq '.' ) {
return -1;
} elsif ( $B eq '.' ) {
return 1;
} elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
if ($A =~ /^0/ || $B =~ /^0/) {
return $A cmp $B if $A cmp $B;
} else {
return $A <=> $B if $A <=> $B;
}
} else {
$A = uc $A;
$B = uc $B;
return $A cmp $B if $A cmp $B;
}
}
@A <=> @B;
}
sub install_command {
my $module = shift;
my ($command, $package);
if (ON_WINDOWS) {
$command = 'ppm install %s';
$package = $module->{package};
}
else {
$command = "$^X -MCPAN -e 'install \"\%s\"'";
# Non-Windows installations need to use module names, because
# CPAN doesn't understand package names.
$package = $module->{module};
}
return sprintf $command, $package;
}
1;
__END__
=head1 NAME
Bugzilla::Install::Requirements - Functions and variables dealing
with Bugzilla's perl-module requirements.
=head1 DESCRIPTION
This module is used primarily by C<checksetup.pl> to determine whether
or not all of Bugzilla's prerequisites are installed. (That is, all the
perl modules it requires.)
=head1 CONSTANTS
=over 4
=item C<REQUIRED_MODULES>
An arrayref of hashrefs that describes the perl modules required by
Bugzilla. The hashes have two keys, C<name> and C<version>, which
represent the name of the module and the version that we require.
=back
=head1 SUBROUTINES
=over 4
=item C<check_requirements($output)>
Description: This checks what optional or required perl modules
are installed, like C<checksetup.pl> does.
Params: C<$output> - C<true> if you want the function to print
out information about what it's doing,
and the versions of everything installed.
If you don't pass the minimum requirements,
the will always print out something,
regardless of this parameter.
Returns: A hashref containing three values:
C<pass> - Whether or not we have all the mandatory
requirements.
C<missing> - A hash showing which mandatory requirements
are missing. The key is the module name,
and the value is the version we require.
C<optional> - Which optional modules are installed and
up-to-date enough for Bugzilla.
=item C<check_graphviz($output)>
Description: Checks if the graphviz binary specified in the
C<webdotbase> parameter is a valid binary, or a valid URL.
Params: C<$output> - C<$true> if you want the function to
print out information about what it's doing.
Returns: C<1> if the check was successful, C<0> otherwise.
=item C<vers_cmp($a, $b)>
Description: This is a comparison function, like you would use in
C<sort>, except that it compares two version numbers.
It's actually identical to versioncmp from
L<Sort::Versions>.
Params: c<$a> and C<$b> are versions you want to compare.
Returns: -1 if $a is less than $b, 0 if they are equal, and
1 if $a is greater than $b.
=item C<have_vers($module, $output)>
Description: Tells you whether or not you have the appropriate
version of the module requested. It also prints
out a message to the user explaining the check
and the result.
Params: C<$module> - A hashref, in the format of an item from
L</REQUIRED_MODULES>.
C<$output> - Set to true if you want this function to
print information to STDOUT about what it's
doing.
Returns: C<1> if you have the module installed and you have the
appropriate version. C<0> otherwise.
=item C<install_command($module)>
Description: Prints out the appropriate command to install the
module specified, depending on whether you're
on Windows or Linux.
Params: C<$module> - A hashref, in the format of an item from
L</REQUIRED_MODULES>.
Returns: nothing
=back

View File

@@ -1,189 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
package Bugzilla::Keyword;
use base qw(Bugzilla::Object);
use Bugzilla::Error;
use Bugzilla::Util;
###############################
#### Initialization ####
###############################
use constant DB_COLUMNS => qw(
keyworddefs.id
keyworddefs.name
keyworddefs.description
);
use constant DB_TABLE => 'keyworddefs';
use constant REQUIRED_CREATE_FIELDS => qw(name description);
use constant VALIDATORS => {
name => \&_check_name,
description => \&_check_description,
};
use constant UPDATE_COLUMNS => qw(
name
description
);
###############################
#### Accessors ######
###############################
sub description { return $_[0]->{'description'}; }
sub bug_count {
my ($self) = @_;
return $self->{'bug_count'} if defined $self->{'bug_count'};
($self->{'bug_count'}) =
Bugzilla->dbh->selectrow_array(
'SELECT COUNT(*) FROM keywords WHERE keywordid = ?',
undef, $self->id);
return $self->{'bug_count'};
}
###############################
#### Mutators #####
###############################
sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); }
###############################
#### Subroutines ######
###############################
sub keyword_count {
my ($count) =
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM keyworddefs');
return $count;
}
sub get_all_with_bug_count {
my $class = shift;
my $dbh = Bugzilla->dbh;
my $keywords =
$dbh->selectall_arrayref('SELECT ' . join(', ', DB_COLUMNS) . ',
COUNT(keywords.bug_id) AS bug_count
FROM keyworddefs
LEFT JOIN keywords
ON keyworddefs.id = keywords.keywordid ' .
$dbh->sql_group_by('keyworddefs.id',
'keyworddefs.name,
keyworddefs.description') . '
ORDER BY keyworddefs.name', {'Slice' => {}});
if (!$keywords) {
return [];
}
foreach my $keyword (@$keywords) {
bless($keyword, $class);
}
return $keywords;
}
###############################
### Validators ###
###############################
sub _check_name {
my ($self, $name) = @_;
$name = trim($name);
$name eq "" && ThrowUserError("keyword_blank_name");
if ($name =~ /[\s,]/) {
ThrowUserError("keyword_invalid_name");
}
# We only want to validate the non-existence of the name if
# we're creating a new Keyword or actually renaming the keyword.
if (!ref($self) || $self->name ne $name) {
my $keyword = new Bugzilla::Keyword({ name => $name });
ThrowUserError("keyword_already_exists", { name => $name }) if $keyword;
}
return $name;
}
sub _check_description {
my ($self, $desc) = @_;
$desc = trim($desc);
$desc eq '' && ThrowUserError("keyword_blank_description");
return $desc;
}
1;
__END__
=head1 NAME
Bugzilla::Keyword - A Keyword that can be added to a bug.
=head1 SYNOPSIS
use Bugzilla::Keyword;
my $count = Bugzilla::Keyword::keyword_count;
my $description = $keyword->description;
my $keywords = Bugzilla::Keyword->get_all_with_bug_count();
=head1 DESCRIPTION
Bugzilla::Keyword represents a keyword that can be added to a bug.
This implements all standard C<Bugzilla::Object> methods. See
L<Bugzilla::Object> for more details.
=head1 SUBROUTINES
This is only a list of subroutines specific to C<Bugzilla::Keyword>.
See L<Bugzilla::Object> for more subroutines that this object
implements.
=over
=item C<keyword_count()>
Description: A utility function to get the total number
of keywords defined. Mostly used to see
if there are any keywords defined at all.
Params: none
Returns: An integer, the count of keywords.
=item C<get_all_with_bug_count()>
Description: Returns all defined keywords. This is an efficient way
to get the associated bug counts, as only one SQL query
is executed with this method, instead of one per keyword
when calling get_all and then bug_count.
Params: none
Returns: A reference to an array of Keyword objects, or an empty
arrayref if there are no keywords.
=back
=cut

View File

@@ -1,139 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>,
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
# Dan Mosedale <dmose@mozilla.org>
# Alan Raetz <al_raetz@yahoo.com>
# Jacob Steenhagen <jake@actex.net>
# Matthew Tuck <matty@chariot.net.au>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# J. Paul Reed <preed@sigkill.com>
# Gervase Markham <gerv@gerv.net>
# Byron Jones <bugzilla@glob.com.au>
# Frédéric Buclin <LpSolit@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Mailer;
use strict;
use base qw(Exporter);
@Bugzilla::Mailer::EXPORT = qw(MessageToMTA);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use Date::Format qw(time2str);
use Encode qw(encode);
use Encode::MIME::Header;
use Email::Address;
use Email::MIME;
# Loading this gives us encoding_set.
use Email::MIME::Modifier;
use Email::Send;
sub MessageToMTA {
my ($msg) = (@_);
my $method = Bugzilla->params->{'mail_delivery_method'};
return if $method eq 'None';
my $email = ref($msg) ? $msg : Email::MIME->new($msg);
foreach my $part ($email->parts) {
$part->charset_set('UTF-8') if Bugzilla->params->{'utf8'};
$part->encoding_set('quoted-printable') if !is_7bit_clean($part->body);
}
# MIME-Version must be set otherwise some mailsystems ignore the charset
$email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
# Encode the headers correctly in quoted-printable
foreach my $header qw(From To Cc Reply-To Sender Errors-To Subject) {
if (my $value = $email->header($header)) {
$value = Encode::decode("UTF-8", $value) if Bugzilla->params->{'utf8'};
# avoid excessive line wrapping done by Encode.
local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
my $encoded = encode('MIME-Q', $value);
$email->header_set($header, $encoded);
}
}
my $from = $email->header('From');
my ($hostname, @args);
if ($method eq "Sendmail") {
if (ON_WINDOWS) {
$Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
}
push @args, "-i";
# We want to make sure that we pass *only* an email address.
if ($from) {
my ($email_obj) = Email::Address->parse($from);
if ($email_obj) {
my $from_email = $email_obj->address;
push(@args, "-f$from_email") if $from_email;
}
}
push(@args, "-ODeliveryMode=deferred")
if !Bugzilla->params->{"sendmailnow"};
}
else {
# Sendmail will automatically append our hostname to the From
# address, but other mailers won't.
my $urlbase = Bugzilla->params->{'urlbase'};
$urlbase =~ m|//([^:/]+)[:/]?|;
$hostname = $1;
$from .= "\@$hostname" if $from !~ /@/;
$email->header_set('From', $from);
# Sendmail adds a Date: header also, but others may not.
if (!defined $email->header('Date')) {
$email->header_set('Date', time2str("%a, %e %b %Y %T %z", time()));
}
}
if ($method eq "SMTP") {
push @args, Host => Bugzilla->params->{"smtpserver"},
Hello => $hostname,
Debug => Bugzilla->params->{'smtp_debug'};
}
if ($method eq "Test") {
my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
open TESTFILE, '>>', $filename;
# From - <date> is required to be a valid mbox file.
print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
close TESTFILE;
}
else {
# This is useful for both Sendmail and Qmail, so we put it out here.
local $ENV{PATH} = SENDMAIL_PATH;
my $mailer = Email::Send->new({ mailer => $method,
mailer_args => \@args });
my $retval = $mailer->send($email);
ThrowCodeError('mail_send_error', { msg => $retval, mail => $email })
if !$retval;
}
}
1;

View File

@@ -1,194 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
package Bugzilla::Milestone;
use base qw(Bugzilla::Object);
use Bugzilla::Util;
use Bugzilla::Error;
################################
##### Initialization #####
################################
use constant DEFAULT_SORTKEY => 0;
use constant DB_TABLE => 'milestones';
use constant DB_COLUMNS => qw(
id
value
product_id
sortkey
);
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'sortkey, value';
sub new {
my $class = shift;
my $param = shift;
my $dbh = Bugzilla->dbh;
my $product;
if (ref $param) {
$product = $param->{product};
my $name = $param->{name};
if (!defined $product) {
ThrowCodeError('bad_arg',
{argument => 'product',
function => "${class}::new"});
}
if (!defined $name) {
ThrowCodeError('bad_arg',
{argument => 'name',
function => "${class}::new"});
}
my $condition = 'product_id = ? AND value = ?';
my @values = ($product->id, $name);
$param = { condition => $condition, values => \@values };
}
unshift @_, $param;
return $class->SUPER::new(@_);
}
sub bug_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'bug_count'}) {
$self->{'bug_count'} = $dbh->selectrow_array(q{
SELECT COUNT(*) FROM bugs
WHERE product_id = ? AND target_milestone = ?},
undef, $self->product_id, $self->name) || 0;
}
return $self->{'bug_count'};
}
################################
##### Accessors ######
################################
sub name { return $_[0]->{'value'}; }
sub product_id { return $_[0]->{'product_id'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
################################
##### Subroutines #####
################################
sub check_milestone {
my ($product, $milestone_name) = @_;
unless ($milestone_name) {
ThrowUserError('milestone_not_specified');
}
my $milestone = new Bugzilla::Milestone({ product => $product,
name => $milestone_name });
unless ($milestone) {
ThrowUserError('milestone_not_valid',
{'product' => $product->name,
'milestone' => $milestone_name});
}
return $milestone;
}
sub check_sort_key {
my ($milestone_name, $sortkey) = @_;
# Keep a copy in case detaint_signed() clears the sortkey
my $stored_sortkey = $sortkey;
if (!detaint_signed($sortkey) || $sortkey < -32768
|| $sortkey > 32767) {
ThrowUserError('milestone_sortkey_invalid',
{'name' => $milestone_name,
'sortkey' => $stored_sortkey});
}
return $sortkey;
}
1;
__END__
=head1 NAME
Bugzilla::Milestone - Bugzilla product milestone class.
=head1 SYNOPSIS
use Bugzilla::Milestone;
my $milestone = new Bugzilla::Milestone(
{ product => $product, name => 'milestone_value' });
my $product_id = $milestone->product_id;
my $value = $milestone->value;
my $milestone = $hash_ref->{'milestone_value'};
=head1 DESCRIPTION
Milestone.pm represents a Product Milestone object.
=head1 METHODS
=over
=item C<new($product_id, $value)>
Description: The constructor is used to load an existing milestone
by passing a product id and a milestone value.
Params: $product_id - Integer with a Bugzilla product id.
$value - String with a milestone value.
Returns: A Bugzilla::Milestone object.
=item C<bug_count()>
Description: Returns the total of bugs that belong to the milestone.
Params: none.
Returns: Integer with the number of bugs.
=back
=head1 SUBROUTINES
=over
=item C<check_milestone($product, $milestone_name)>
Description: Checks if a milestone name was passed in
and if it is a valid milestone.
Params: $product - Bugzilla::Product object.
$milestone_name - String with a milestone name.
Returns: Bugzilla::Milestone object.
=back
=cut

View File

@@ -1,595 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved.
# Portions created by Everything Solved are Copyright (C) 2006
# Everything Solved. All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
use strict;
package Bugzilla::Object;
use Bugzilla::Util;
use Bugzilla::Error;
use constant NAME_FIELD => 'name';
use constant ID_FIELD => 'id';
use constant LIST_ORDER => NAME_FIELD;
###############################
#### Initialization ####
###############################
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my $object = $class->_init(@_);
bless($object, $class) if $object;
return $object;
}
# Note: Because this uses sql_istrcmp, if you make a new object use
# Bugzilla::Object, make sure that you modify bz_setup_database
# in Bugzilla::DB::Pg appropriately, to add the right LOWER
# index. You can see examples already there.
sub _init {
my $class = shift;
my ($param) = @_;
my $dbh = Bugzilla->dbh;
my $columns = join(',', $class->DB_COLUMNS);
my $table = $class->DB_TABLE;
my $name_field = $class->NAME_FIELD;
my $id_field = $class->ID_FIELD;
my $id = $param unless (ref $param eq 'HASH');
my $object;
if (defined $id) {
# We special-case if somebody specifies an ID, so that we can
# validate it as numeric.
detaint_natural($id)
|| ThrowCodeError('param_must_be_numeric',
{function => $class . '::_init'});
$object = $dbh->selectrow_hashref(qq{
SELECT $columns FROM $table
WHERE $id_field = ?}, undef, $id);
} else {
unless (defined $param->{name} || (defined $param->{'condition'}
&& defined $param->{'values'}))
{
ThrowCodeError('bad_arg', { argument => 'param',
function => $class . '::new' });
}
my ($condition, @values);
if (defined $param->{name}) {
$condition = $dbh->sql_istrcmp($name_field, '?');
push(@values, $param->{name});
}
elsif (defined $param->{'condition'} && defined $param->{'values'}) {
caller->isa('Bugzilla::Object')
|| ThrowCodeError('protection_violation',
{ caller => caller,
function => $class . '::new',
argument => 'condition/values' });
$condition = $param->{'condition'};
push(@values, @{$param->{'values'}});
}
map { trick_taint($_) } @values;
$object = $dbh->selectrow_hashref(
"SELECT $columns FROM $table WHERE $condition", undef, @values);
}
return $object;
}
sub new_from_list {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
my ($id_list) = @_;
my $dbh = Bugzilla->dbh;
my $columns = join(',', $class->DB_COLUMNS);
my $table = $class->DB_TABLE;
my $order = $class->LIST_ORDER;
my $id_field = $class->ID_FIELD;
my $objects;
if (@$id_list) {
my @detainted_ids;
foreach my $id (@$id_list) {
detaint_natural($id) ||
ThrowCodeError('param_must_be_numeric',
{function => $class . '::new_from_list'});
push(@detainted_ids, $id);
}
$objects = $dbh->selectall_arrayref(
"SELECT $columns FROM $table WHERE $id_field IN ("
. join(',', @detainted_ids) . ") ORDER BY $order", {Slice=>{}});
} else {
return [];
}
foreach my $object (@$objects) {
bless($object, $class);
}
return $objects;
}
###############################
#### Accessors ######
###############################
sub id { return $_[0]->{'id'}; }
sub name { return $_[0]->{'name'}; }
###############################
#### Methods ####
###############################
sub set {
my ($self, $field, $value) = @_;
# This method is protected. It's used to help implement set_ functions.
caller->isa('Bugzilla::Object')
|| ThrowCodeError('protection_violation',
{ caller => caller,
superclass => __PACKAGE__,
function => 'Bugzilla::Object->set' });
my $validators = $self->VALIDATORS;
if (exists $validators->{$field}) {
my $validator = $validators->{$field};
$value = $self->$validator($value, $field);
}
$self->{$field} = $value;
}
sub update {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $table = $self->DB_TABLE;
my $id_field = $self->ID_FIELD;
my $old_self = $self->new($self->id);
my (@update_columns, @values, %changes);
foreach my $column ($self->UPDATE_COLUMNS) {
if ($old_self->{$column} ne $self->{$column}) {
my $value = $self->{$column};
trick_taint($value) if defined $value;
push(@values, $value);
push(@update_columns, $column);
# We don't use $value because we don't want to detaint this for
# the caller.
$changes{$column} = [$old_self->{$column}, $self->{$column}];
}
}
my $columns = join(', ', map {"$_ = ?"} @update_columns);
$dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
@values, $self->id) if @values;
return \%changes;
}
###############################
#### Subroutines ######
###############################
sub create {
my ($class, $params) = @_;
my $dbh = Bugzilla->dbh;
$class->check_required_create_fields($params);
my $field_values = $class->run_create_validators($params);
return $class->insert_create_data($field_values);
}
sub check_required_create_fields {
my ($class, $params) = @_;
foreach my $field ($class->REQUIRED_CREATE_FIELDS) {
ThrowCodeError('param_required',
{ function => "${class}->create", param => $field })
if !exists $params->{$field};
}
}
sub run_create_validators {
my ($class, $params) = @_;
my $validators = $class->VALIDATORS;
my %field_values;
# We do the sort just to make sure that validation always
# happens in a consistent order.
foreach my $field (sort keys %$params) {
my $value;
if (exists $validators->{$field}) {
my $validator = $validators->{$field};
$value = $class->$validator($params->{$field}, $field);
}
else {
$value = $params->{$field};
}
# We want people to be able to explicitly set fields to NULL,
# and that means they can be set to undef.
trick_taint($value) if defined $value && !ref($value);
$field_values{$field} = $value;
}
return \%field_values;
}
sub insert_create_data {
my ($class, $field_values) = @_;
my $dbh = Bugzilla->dbh;
my (@field_names, @values);
while (my ($field, $value) = each %$field_values) {
push(@field_names, $field);
push(@values, $value);
}
my $qmarks = '?,' x @field_names;
chop($qmarks);
my $table = $class->DB_TABLE;
$dbh->do("INSERT INTO $table (" . join(', ', @field_names)
. ") VALUES ($qmarks)", undef, @values);
my $id = $dbh->bz_last_key($table, $class->ID_FIELD);
return $class->new($id);
}
sub get_all {
my $class = shift;
my $dbh = Bugzilla->dbh;
my $table = $class->DB_TABLE;
my $order = $class->LIST_ORDER;
my $id_field = $class->ID_FIELD;
my $ids = $dbh->selectcol_arrayref(qq{
SELECT $id_field FROM $table ORDER BY $order});
my $objects = $class->new_from_list($ids);
return @$objects;
}
1;
__END__
=head1 NAME
Bugzilla::Object - A base class for objects in Bugzilla.
=head1 SYNOPSIS
my $object = new Bugzilla::Object(1);
my $object = new Bugzilla::Object({name => 'TestProduct'});
my $id = $object->id;
my $name = $object->name;
=head1 DESCRIPTION
Bugzilla::Object is a base class for Bugzilla objects. You never actually
create a Bugzilla::Object directly, you only make subclasses of it.
Basically, Bugzilla::Object exists to allow developers to create objects
more easily. All you have to do is define C<DB_TABLE>, C<DB_COLUMNS>,
and sometimes C<LIST_ORDER> and you have a whole new object.
You should also define accessors for any columns other than C<name>
or C<id>.
=head1 CONSTANTS
Frequently, these will be the only things you have to define in your
subclass in order to have a fully-functioning object. C<DB_TABLE>
and C<DB_COLUMNS> are required.
=over
=item C<DB_TABLE>
The name of the table that these objects are stored in. For example,
for C<Bugzilla::Keyword> this would be C<keyworddefs>.
=item C<DB_COLUMNS>
The names of the columns that you want to read out of the database
and into this object. This should be an array.
=item C<NAME_FIELD>
The name of the column that should be considered to be the unique
"name" of this object. The 'name' is a B<string> that uniquely identifies
this Object in the database. Defaults to 'name'. When you specify
C<{name => $name}> to C<new()>, this is the column that will be
matched against in the DB.
=item C<ID_FIELD>
The name of the column that represents the unique B<integer> ID
of this object in the database. Defaults to 'id'.
=item C<LIST_ORDER>
The order that C<new_from_list> and C<get_all> should return objects
in. This should be the name of a database column. Defaults to
L</NAME_FIELD>.
=item C<REQUIRED_CREATE_FIELDS>
The list of fields that B<must> be specified when the user calls
C<create()>. This should be an array.
=item C<VALIDATORS>
A hashref that points to a function that will validate each param to
L</create>.
Validators are called both by L</create> and L</set>. When
they are called by L</create>, the first argument will be the name
of the class (what we normally call C<$class>).
When they are called by L</set>, the first argument will be
a reference to the current object (what we normally call C<$self>).
The second argument will be the value passed to L</create> or
L</set>for that field.
The third argument will be the name of the field being validated.
This may be required by validators which validate several distinct fields.
These functions should call L<Bugzilla::Error/ThrowUserError> if they fail.
The validator must return the validated value.
=item C<UPDATE_COLUMNS>
A list of columns to update when L</update> is called.
If a field can't be changed, it shouldn't be listed here. (For example,
the L</ID_FIELD> usually can't be updated.)
=back
=head1 METHODS
=head2 Constructors
=over
=item C<new($param)>
=over
=item B<Description>
The constructor is used to load an existing object from the database,
by id or by name.
=item B<Params>
If you pass an integer, the integer is the id of the object,
from the database, that we want to read in. (id is defined
as the value in the L</ID_FIELD> column).
If you pass in a hash, you can pass a C<name> key. The
value of the C<name> key is the case-insensitive name of the object
(from L</NAME_FIELD>) in the DB.
B<Additional Parameters Available for Subclasses>
If you are a subclass of C<Bugzilla::Object>, you can pass
C<condition> and C<values> as hash keys, instead of the above.
C<condition> is a set of SQL conditions for the WHERE clause, which contain
placeholders.
C<values> is a reference to an array. The array contains the values
for each placeholder in C<condition>, in order.
This is to allow subclasses to have complex parameters, and then to
translate those parameters into C<condition> and C<values> when they
call C<$self->SUPER::new> (which is this function, usually).
If you try to call C<new> outside of a subclass with the C<condition>
and C<values> parameters, Bugzilla will throw an error. These parameters
are intended B<only> for use by subclasses.
=item B<Returns>
A fully-initialized object.
=back
=item C<new_from_list(\@id_list)>
Description: Creates an array of objects, given an array of ids.
Params: \@id_list - A reference to an array of numbers, database ids.
If any of these are not numeric, the function
will throw an error. If any of these are not
valid ids in the database, they will simply
be skipped.
Returns: A reference to an array of objects.
=back
=head2 Database Manipulation
=over
=item C<create>
Description: Creates a new item in the database.
Throws a User Error if any of the passed-in params
are invalid.
Params: C<$params> - hashref - A value to put in each database
field for this object. Certain values must be set (the
ones specified in L</REQUIRED_CREATE_FIELDS>), and
the function will throw a Code Error if you don't set
them.
Returns: The Object just created in the database.
Notes: In order for this function to work in your subclass,
your subclass's L</ID_FIELD> must be of C<SERIAL>
type in the database. Your subclass also must
define L</REQUIRED_CREATE_FIELDS> and L</VALIDATORS>.
Subclass Implementors: This function basically just
calls L</check_required_create_fields>, then
L</run_create_validators>, and then finally
L</insert_create_data>. So if you have a complex system that
you need to implement, you can do it by calling these
three functions instead of C<SUPER::create>.
=item C<check_required_create_fields>
=over
=item B<Description>
Part of L</create>. Throws an error if any of the L</REQUIRED_CREATE_FIELDS>
have not been specified in C<$params>
=item B<Params>
=over
=item C<$params> - The same as C<$params> from L</create>.
=back
=item B<Returns> (nothing)
=back
=item C<run_create_validators>
Description: Runs the validation of input parameters for L</create>.
This subroutine exists so that it can be overridden
by subclasses who need to do special validations
of their input parameters. This method is B<only> called
by L</create>.
Params: The same as L</create>.
Returns: A hash, in a similar format as C<$params>, except that
these are the values to be inserted into the database,
not the values that were input to L</create>.
=item C<insert_create_data>
Part of L</create>.
Takes the return value from L</run_create_validators> and inserts the
data into the database. Returns a newly created object.
=item C<update>
=over
=item B<Description>
Saves the values currently in this object to the database.
Only the fields specified in L</UPDATE_COLUMNS> will be
updated, and they will only be updated if their values have changed.
=item B<Params> (none)
=item B<Returns>
A hashref showing what changed during the update. The keys are the column
names from L</UPDATE_COLUMNS>. If a field was not changed, it will not be
in the hash at all. If the field was changed, the key will point to an arrayref.
The first item of the arrayref will be the old value, and the second item
will be the new value.
If there were no changes, we return a reference to an empty hash.
=back
=back
=head2 Subclass Helpers
These functions are intended only for use by subclasses. If
you call them from anywhere else, they will throw a C<CodeError>.
=over
=item C<set>
=over
=item B<Description>
Sets a certain hash member of this class to a certain value.
Used for updating fields. Calls the validator for this field,
if it exists. Subclasses should use this function
to implement the various C<set_> mutators for their different
fields.
See L</VALIDATORS> for more information.
=item B<Params>
=over
=item C<$field> - The name of the hash member to update. This should
be the same as the name of the field in L</VALIDATORS>, if it exists there.
=item C<$value> - The value that you're setting the field to.
=back
=item B<Returns> (nothing)
=back
=back
=head1 CLASS FUNCTIONS
=over
=item C<get_all>
Description: Returns all objects in this table from the database.
Params: none.
Returns: A list of objects, or an empty list if there are none.
Notes: Note that you must call this as C<$class->get_all>. For
example, C<Bugzilla::Keyword->get_all>.
C<Bugzilla::Keyword::get_all> will not work.
=back
=cut

View File

@@ -1,363 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
use strict;
package Bugzilla::Product;
use Bugzilla::Version;
use Bugzilla::Milestone;
use Bugzilla::Util;
use Bugzilla::Group;
use Bugzilla::Error;
use Bugzilla::Install::Requirements;
use base qw(Bugzilla::Object);
use constant DEFAULT_CLASSIFICATION_ID => 1;
###############################
#### Initialization ####
###############################
use constant DB_TABLE => 'products';
use constant DB_COLUMNS => qw(
products.id
products.name
products.classification_id
products.description
products.milestoneurl
products.disallownew
products.votesperuser
products.maxvotesperbug
products.votestoconfirm
products.defaultmilestone
);
###############################
#### Methods ####
###############################
sub components {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{components}) {
my $ids = $dbh->selectcol_arrayref(q{
SELECT id FROM components
WHERE product_id = ?
ORDER BY name}, undef, $self->id);
require Bugzilla::Component;
$self->{components} = Bugzilla::Component->new_from_list($ids);
}
return $self->{components};
}
sub group_controls {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{group_controls}) {
my $query = qq{SELECT
groups.id,
group_control_map.entry,
group_control_map.membercontrol,
group_control_map.othercontrol,
group_control_map.canedit,
group_control_map.editcomponents,
group_control_map.editbugs,
group_control_map.canconfirm
FROM groups
LEFT JOIN group_control_map
ON groups.id = group_control_map.group_id
WHERE group_control_map.product_id = ?
AND groups.isbuggroup != 0
ORDER BY groups.name};
$self->{group_controls} =
$dbh->selectall_hashref($query, 'id', undef, $self->id);
foreach my $group (keys(%{$self->{group_controls}})) {
$self->{group_controls}->{$group}->{'group'} =
new Bugzilla::Group($group);
}
}
return $self->{group_controls};
}
sub versions {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{versions}) {
my $ids = $dbh->selectcol_arrayref(q{
SELECT id FROM versions
WHERE product_id = ?}, undef, $self->id);
$self->{versions} = Bugzilla::Version->new_from_list($ids);
}
return $self->{versions};
}
sub milestones {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{milestones}) {
my $ids = $dbh->selectcol_arrayref(q{
SELECT id FROM milestones
WHERE product_id = ?}, undef, $self->id);
$self->{milestones} = Bugzilla::Milestone->new_from_list($ids);
}
return $self->{milestones};
}
sub bug_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'bug_count'}) {
$self->{'bug_count'} = $dbh->selectrow_array(qq{
SELECT COUNT(bug_id) FROM bugs
WHERE product_id = ?}, undef, $self->id);
}
return $self->{'bug_count'};
}
sub bug_ids {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'bug_ids'}) {
$self->{'bug_ids'} =
$dbh->selectcol_arrayref(q{SELECT bug_id FROM bugs
WHERE product_id = ?},
undef, $self->id);
}
return $self->{'bug_ids'};
}
sub user_has_access {
my ($self, $user) = @_;
return Bugzilla->dbh->selectrow_array(
'SELECT CASE WHEN group_id IS NULL THEN 1 ELSE 0 END
FROM products LEFT JOIN group_control_map
ON group_control_map.product_id = products.id
AND group_control_map.entry != 0
AND group_id NOT IN (' . $user->groups_as_string . ')
WHERE products.id = ? ' . Bugzilla->dbh->sql_limit(1),
undef, $self->id);
}
sub flag_types {
my $self = shift;
if (!defined $self->{'flag_types'}) {
$self->{'flag_types'} = {};
foreach my $type ('bug', 'attachment') {
my %flagtypes;
foreach my $component (@{$self->components}) {
foreach my $flagtype (@{$component->flag_types->{$type}}) {
$flagtypes{$flagtype->{'id'}} ||= $flagtype;
}
}
$self->{'flag_types'}->{$type} = [sort { $a->{'sortkey'} <=> $b->{'sortkey'}
|| $a->{'name'} cmp $b->{'name'} } values %flagtypes];
}
}
return $self->{'flag_types'};
}
###############################
#### Accessors ######
###############################
sub description { return $_[0]->{'description'}; }
sub milestone_url { return $_[0]->{'milestoneurl'}; }
sub disallow_new { return $_[0]->{'disallownew'}; }
sub votes_per_user { return $_[0]->{'votesperuser'}; }
sub max_votes_per_bug { return $_[0]->{'maxvotesperbug'}; }
sub votes_to_confirm { return $_[0]->{'votestoconfirm'}; }
sub default_milestone { return $_[0]->{'defaultmilestone'}; }
sub classification_id { return $_[0]->{'classification_id'}; }
###############################
#### Subroutines ######
###############################
sub check_product {
my ($product_name) = @_;
unless ($product_name) {
ThrowUserError('product_not_specified');
}
my $product = new Bugzilla::Product({name => $product_name});
unless ($product) {
ThrowUserError('product_doesnt_exist',
{'product' => $product_name});
}
return $product;
}
1;
__END__
=head1 NAME
Bugzilla::Product - Bugzilla product class.
=head1 SYNOPSIS
use Bugzilla::Product;
my $product = new Bugzilla::Product(1);
my $product = new Bugzilla::Product({ name => 'AcmeProduct' });
my @components = $product->components();
my $groups_controls = $product->group_controls();
my @milestones = $product->milestones();
my @versions = $product->versions();
my $bugcount = $product->bug_count();
my $bug_ids = $product->bug_ids();
my $has_access = $product->user_has_access($user);
my $flag_types = $product->flag_types();
my $id = $product->id;
my $name = $product->name;
my $description = $product->description;
my $milestoneurl = $product->milestone_url;
my disallownew = $product->disallow_new;
my votesperuser = $product->votes_per_user;
my maxvotesperbug = $product->max_votes_per_bug;
my votestoconfirm = $product->votes_to_confirm;
my $defaultmilestone = $product->default_milestone;
my $classificationid = $product->classification_id;
=head1 DESCRIPTION
Product.pm represents a product object. It is an implementation
of L<Bugzilla::Object>, and thus provides all methods that
L<Bugzilla::Object> provides.
The methods that are specific to C<Bugzilla::Product> are listed
below.
=head1 METHODS
=over
=item C<components()>
Description: Returns an array of component objects belonging to
the product.
Params: none.
Returns: An array of Bugzilla::Component object.
=item C<group_controls()>
Description: Returns a hash (group id as key) with all product
group controls.
Params: none.
Returns: A hash with group id as key and hash containing
a Bugzilla::Group object and the properties of group
relative to the product.
=item C<versions()>
Description: Returns all valid versions for that product.
Params: none.
Returns: An array of Bugzilla::Version objects.
=item C<milestones()>
Description: Returns all valid milestones for that product.
Params: none.
Returns: An array of Bugzilla::Milestone objects.
=item C<bug_count()>
Description: Returns the total of bugs that belong to the product.
Params: none.
Returns: Integer with the number of bugs.
=item C<bug_ids()>
Description: Returns the IDs of bugs that belong to the product.
Params: none.
Returns: An array of integer.
=item C<user_has_access()>
Description: Tells you whether or not the user is allowed to enter
bugs into this product, based on the C<entry> group
control. To see whether or not a user can actually
enter a bug into a product, use C<$user-&gt;can_enter_product>.
Params: C<$user> - A Bugzilla::User object.
Returns C<1> If this user's groups allow him C<entry> access to
this Product, C<0> otherwise.
=item C<flag_types()>
Description: Returns flag types available for at least one of
its components.
Params: none.
Returns: Two references to an array of flagtype objects.
=back
=head1 SUBROUTINES
=over
=item C<check_product($product_name)>
Description: Checks if the product name was passed in and if is a valid
product.
Params: $product_name - String with a product name.
Returns: Bugzilla::Product object.
=back
=head1 SEE ALSO
L<Bugzilla::Object>
=cut

File diff suppressed because it is too large Load Diff

View File

@@ -1,510 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): C. Begle
# Jesse Ruderman
# Andreas Franke <afranke@mathweb.org>
# Stephen Lee <slee@uk.bnsmc.com>
# Marc Schumann <wurblzap@gmail.com>
package Bugzilla::Search::Quicksearch;
# Make it harder for us to do dangerous things in Perl.
use strict;
use Bugzilla::Error;
use Bugzilla::Constants;
use Bugzilla::Keyword;
use Bugzilla::Bug;
use Bugzilla::Field;
use Bugzilla::Util;
use base qw(Exporter);
@Bugzilla::Search::Quicksearch::EXPORT = qw(quicksearch);
# Word renamings
use constant MAPPINGS => {
# Status, Resolution, Platform, OS, Priority, Severity
"status" => "bug_status",
"resolution" => "resolution", # no change
"platform" => "rep_platform",
"os" => "op_sys",
"opsys" => "op_sys",
"priority" => "priority", # no change
"pri" => "priority",
"severity" => "bug_severity",
"sev" => "bug_severity",
# People: AssignedTo, Reporter, QA Contact, CC, Added comment (?)
"owner" => "assigned_to", # deprecated since bug 76507
"assignee" => "assigned_to",
"assignedto" => "assigned_to",
"reporter" => "reporter", # no change
"rep" => "reporter",
"qa" => "qa_contact",
"qacontact" => "qa_contact",
"cc" => "cc", # no change
# Product, Version, Component, Target Milestone
"product" => "product", # no change
"prod" => "product",
"version" => "version", # no change
"ver" => "version",
"component" => "component", # no change
"comp" => "component",
"milestone" => "target_milestone",
"target" => "target_milestone",
"targetmilestone" => "target_milestone",
# Summary, Description, URL, Status whiteboard, Keywords
"summary" => "short_desc",
"shortdesc" => "short_desc",
"desc" => "longdesc",
"description" => "longdesc",
#"comment" => "longdesc", # ???
# reserve "comment" for "added comment" email search?
"longdesc" => "longdesc",
"url" => "bug_file_loc",
"whiteboard" => "status_whiteboard",
"statuswhiteboard" => "status_whiteboard",
"sw" => "status_whiteboard",
"keywords" => "keywords", # no change
"kw" => "keywords",
# Attachments
"attachment" => "attachments.description",
"attachmentdesc" => "attachments.description",
"attachdesc" => "attachments.description",
"attachmentdata" => "attach_data.thedata",
"attachdata" => "attach_data.thedata",
"attachmentmimetype" => "attachments.mimetype",
"attachmimetype" => "attachments.mimetype"
};
# We might want to put this into localconfig or somewhere
use constant PLATFORMS => ('pc', 'sun', 'macintosh', 'mac');
use constant PRODUCT_EXCEPTIONS => (
'row', # [Browser]
# ^^^
'new', # [MailNews]
# ^^^
);
use constant COMPONENT_EXCEPTIONS => (
'hang' # [Bugzilla: Component/Keyword Changes]
# ^^^^
);
# Quicksearch-wide globals for boolean charts.
our ($chart, $and, $or);
sub quicksearch {
my ($searchstring) = (@_);
my $cgi = Bugzilla->cgi;
my $urlbase = correct_urlbase();
$chart = 0;
$and = 0;
$or = 0;
# Remove leading and trailing commas and whitespace.
$searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
ThrowUserError('buglist_parameters_required') unless ($searchstring);
if ($searchstring =~ m/^[0-9,\s]*$/) {
# Bug number(s) only.
# Allow separation by comma or whitespace.
$searchstring =~ s/[,\s]+/,/g;
if (index($searchstring, ',') < $[) {
# Single bug number; shortcut to show_bug.cgi.
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$searchstring");
exit;
}
else {
# List of bug numbers.
$cgi->param('bug_id', $searchstring);
$cgi->param('order', 'bugs.bug_id');
$cgi->param('bugidtype', 'include');
}
}
else {
# It's not just a bug number or a list of bug numbers.
# Maybe it's an alias?
if ($searchstring =~ /^([^,\s]+)$/) {
if (Bugzilla->dbh->selectrow_array(q{SELECT COUNT(*)
FROM bugs
WHERE alias = ?},
undef,
$1)) {
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$1");
exit;
}
}
# It's no alias either, so it's a more complex query.
my $legal_statuses = get_legal_field_values('bug_status');
my $legal_resolutions = get_legal_field_values('resolution');
# Globally translate " AND ", " OR ", " NOT " to space, pipe, dash.
$searchstring =~ s/\s+AND\s+/ /g;
$searchstring =~ s/\s+OR\s+/|/g;
$searchstring =~ s/\s+NOT\s+/ -/g;
my @words = splitString($searchstring);
my $searchComments =
$#words < Bugzilla->params->{'quicksearch_comment_cutoff'};
my @openStates = BUG_STATE_OPEN;
my @closedStates;
my @unknownFields;
my (%states, %resolutions);
foreach (@$legal_statuses) {
push(@closedStates, $_) unless is_open_state($_);
}
foreach (@openStates) { $states{$_} = 1 }
if ($words[0] eq 'ALL') {
foreach (@$legal_statuses) { $states{$_} = 1 }
shift @words;
}
elsif ($words[0] eq 'OPEN') {
shift @words;
}
elsif ($words[0] =~ /^\+[A-Z]+(,[A-Z]+)*$/) {
# e.g. +DUP,FIX
if (matchPrefixes(\%states,
\%resolutions,
[split(/,/, substr($words[0], 1))],
\@closedStates,
$legal_resolutions)) {
shift @words;
# Allowing additional resolutions means we need to keep
# the "no resolution" resolution.
$resolutions{'---'} = 1;
}
else {
# Carry on if no match found.
}
}
elsif ($words[0] =~ /^[A-Z]+(,[A-Z]+)*$/) {
# e.g. NEW,ASSI,REOP,FIX
undef %states;
if (matchPrefixes(\%states,
\%resolutions,
[split(/,/, $words[0])],
$legal_statuses,
$legal_resolutions)) {
shift @words;
}
else {
# Carry on if no match found
foreach (@openStates) { $states{$_} = 1 }
}
}
else {
# Default: search for unresolved bugs only.
# Put custom code here if you would like to change this behaviour.
}
# If we have wanted resolutions, allow closed states
if (keys(%resolutions)) {
foreach (@closedStates) { $states{$_} = 1 }
}
$cgi->param('bug_status', keys(%states));
$cgi->param('resolution', keys(%resolutions));
# Loop over all main-level QuickSearch words.
foreach my $qsword (@words) {
my $negate = substr($qsword, 0, 1) eq '-';
if ($negate) {
$qsword = substr($qsword, 1);
}
my $firstChar = substr($qsword, 0, 1);
my $baseWord = substr($qsword, 1);
my @subWords = split(/[\|,]/, $baseWord);
if ($firstChar eq '+') {
foreach (@subWords) {
addChart('short_desc', 'substring', $qsword, $negate);
}
}
elsif ($firstChar eq '#') {
addChart('short_desc', 'anywords', $baseWord, $negate);
if ($searchComments) {
addChart('longdesc', 'anywords', $baseWord, $negate);
}
}
elsif ($firstChar eq ':') {
foreach (@subWords) {
addChart('product', 'substring', $_, $negate);
addChart('component', 'substring', $_, $negate);
}
}
elsif ($firstChar eq '@') {
foreach (@subWords) {
addChart('assigned_to', 'substring', $_, $negate);
}
}
elsif ($firstChar eq '[') {
addChart('short_desc', 'substring', $baseWord, $negate);
addChart('status_whiteboard', 'substring', $baseWord, $negate);
}
elsif ($firstChar eq '!') {
addChart('keywords', 'anywords', $baseWord, $negate);
}
else { # No special first char
# Split by '|' to get all operands for a boolean OR.
foreach my $or_operand (split(/\|/, $qsword)) {
if ($or_operand =~ /^votes:([0-9]+)$/) {
# votes:xx ("at least xx votes")
addChart('votes', 'greaterthan', $1 - 1, $negate);
}
elsif ($or_operand =~ /^([^:]+):([^:]+)$/) {
# generic field1,field2,field3:value1,value2 notation
my @fields = split(/,/, $1);
my @values = split(/,/, $2);
foreach my $field (@fields) {
# Skip and record any unknown fields
if (!defined(MAPPINGS->{$field})) {
push(@unknownFields, $field);
next;
}
$field = MAPPINGS->{$field};
foreach (@values) {
addChart($field, 'substring', $_, $negate);
}
}
}
else {
# Having ruled out the special cases, we may now split
# by comma, which is another legal boolean OR indicator.
foreach my $word (split(/,/, $or_operand)) {
# Platform
if (grep({lc($word) eq $_} PLATFORMS)) {
addChart('rep_platform', 'substring',
$word, $negate);
}
# Priority
elsif ($word =~ m/^[pP]([1-5](-[1-5])?)$/) {
addChart('priority', 'regexp',
"[$1]", $negate);
}
# Severity
elsif (grep({lc($word) eq substr($_, 0, 3)}
@{get_legal_field_values('bug_severity')})) {
addChart('bug_severity', 'substring',
$word, $negate);
}
# Votes (votes>xx)
elsif ($word =~ m/^votes>([0-9]+)$/) {
addChart('votes', 'greaterthan',
$1, $negate);
}
# Votes (votes>=xx, votes=>xx)
elsif ($word =~ m/^votes(>=|=>)([0-9]+)$/) {
addChart('votes', 'greaterthan',
$2-1, $negate);
}
else { # Default QuickSearch word
if (!grep({lc($word) eq $_}
PRODUCT_EXCEPTIONS) &&
length($word)>2
) {
addChart('product', 'substring',
$word, $negate);
}
if (!grep({lc($word) eq $_}
COMPONENT_EXCEPTIONS) &&
length($word)>2
) {
addChart('component', 'substring',
$word, $negate);
}
if (grep({lc($word) eq $_}
map($_->name, Bugzilla::Keyword->get_all))) {
addChart('keywords', 'substring',
$word, $negate);
if (length($word)>2) {
addChart('short_desc', 'substring',
$word, $negate);
addChart('status_whiteboard',
'substring',
$word, $negate);
}
}
else {
addChart('short_desc', 'substring',
$word, $negate);
addChart('status_whiteboard', 'substring',
$word, $negate);
}
if ($searchComments) {
addChart('longdesc', 'substring',
$word, $negate);
}
}
# URL field (for IP addrs, host.names,
# scheme://urls)
if ($word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
|| $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
|| $word =~ /:[\\\/][\\\/]/
|| $word =~ /localhost/
|| $word =~ /mailto[:]?/
# || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
) {
addChart('bug_file_loc', 'substring',
$word, $negate);
}
} # foreach my $word (split(/,/, $qsword))
} # votes and generic field detection
} # foreach (split(/\|/, $_))
} # "switch" $firstChar
$chart++;
$and = 0;
$or = 0;
} # foreach (@words)
# Inform user about any unknown fields
if (scalar(@unknownFields)) {
ThrowUserError("quicksearch_unknown_field",
{ fields => \@unknownFields });
}
# Make sure we have some query terms left
scalar($cgi->param())>0 || ThrowUserError("buglist_parameters_required");
}
# List of quicksearch-specific CGI parameters to get rid of.
my @params_to_strip = ('quicksearch', 'load', 'run');
my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
if ($cgi->param('load')) {
# Param 'load' asks us to display the query in the advanced search form.
print $cgi->redirect(-uri => "${urlbase}query.cgi?format=advanced&amp;"
. $modified_query_string);
}
# Otherwise, pass the modified query string to the caller.
# We modified $cgi->params, so the caller can choose to look at that, too,
# and disregard the return value.
$cgi->delete(@params_to_strip);
return $modified_query_string;
}
###########################################################################
# Helpers
###########################################################################
# Split string on whitespace, retaining quoted strings as one
sub splitString {
my $string = shift;
my @quoteparts;
my @parts;
my $i = 0;
# Now split on quote sign; be tolerant about unclosed quotes
@quoteparts = split(/"/, $string);
foreach my $part (@quoteparts) {
# After every odd quote, quote special chars
$part = url_quote($part) if $i++ % 2;
}
# Join again
$string = join('"', @quoteparts);
# Now split on unescaped whitespace
@parts = split(/\s+/, $string);
foreach (@parts) {
# Remove quotes
s/"//g;
}
return @parts;
}
# Expand found prefixes to states or resolutions
sub matchPrefixes {
my $hr_states = shift;
my $hr_resolutions = shift;
my $ar_prefixes = shift;
my $ar_check_states = shift;
my $ar_check_resolutions = shift;
my $foundMatch = 0;
foreach my $prefix (@$ar_prefixes) {
foreach (@$ar_check_states) {
if (/^$prefix/) {
$$hr_states{$_} = 1;
$foundMatch = 1;
}
}
foreach (@$ar_check_resolutions) {
if (/^$prefix/) {
$$hr_resolutions{$_} = 1;
$foundMatch = 1;
}
}
}
return $foundMatch;
}
# Negate comparison type
sub negateComparisonType {
my $comparisonType = shift;
if ($comparisonType eq 'substring') {
return 'notsubstring';
}
elsif ($comparisonType eq 'anywords') {
return 'nowords';
}
elsif ($comparisonType eq 'regexp') {
return 'notregexp';
}
else {
# Don't know how to negate that
ThrowCodeError('unknown_comparison_type');
}
}
# Add a boolean chart
sub addChart {
my ($field, $comparisonType, $value, $negate) = @_;
$negate && ($comparisonType = negateComparisonType($comparisonType));
makeChart("$chart-$and-$or", $field, $comparisonType, $value);
if ($negate) {
$and++;
$or = 0;
}
else {
$or++;
}
}
# Create the CGI parameters for a boolean chart
sub makeChart {
my ($expr, $field, $type, $value) = @_;
my $cgi = Bugzilla->cgi;
$cgi->param("field$expr", $field);
$cgi->param("type$expr", $type);
$cgi->param("value$expr", url_decode($value));
}
1;

View File

@@ -1,265 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Everything Solved.
# Portions created by Everything Solved are Copyright (C) 2006
# Everything Solved. All Rights Reserved.
#
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
package Bugzilla::Search::Saved;
use base qw(Bugzilla::Object);
use Bugzilla::CGI;
use Bugzilla::Constants;
use Bugzilla::Group;
use Bugzilla::Error;
use Bugzilla::Search qw(IsValidQueryType);
use Bugzilla::User;
use Bugzilla::Util;
#############
# Constants #
#############
use constant DB_TABLE => 'namedqueries';
use constant DB_COLUMNS => qw(
id
userid
name
query
query_type
);
use constant REQUIRED_CREATE_FIELDS => qw(name query);
use constant VALIDATORS => {
name => \&_check_name,
query => \&_check_query,
query_type => \&_check_query_type,
link_in_footer => \&_check_link_in_footer,
};
use constant UPDATE_COLUMNS => qw(query query_type);
##############
# Validators #
##############
sub _check_link_in_footer { return $_[1] ? 1 : 0; }
sub _check_name {
my ($invocant, $name) = @_;
$name = trim($name);
$name || ThrowUserError("query_name_missing");
$name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
if (length($name) > MAX_LEN_QUERY_NAME) {
ThrowUserError("query_name_too_long");
}
return $name;
}
sub _check_query {
my ($invocant, $query) = @_;
$query || ThrowUserError("buglist_parameters_required");
my $cgi = new Bugzilla::CGI($query);
$cgi->clean_search_url;
return $cgi->query_string;
}
sub _check_query_type {
my ($invocant, $type) = @_;
# Right now the only query type is LIST_OF_BUGS.
return $type ? LIST_OF_BUGS : QUERY_LIST;
}
#########################
# Database Manipulation #
#########################
sub create {
my $class = shift;
Bugzilla->login(LOGIN_REQUIRED);
my $dbh = Bugzilla->dbh;
$class->check_required_create_fields(@_);
my $params = $class->run_create_validators(@_);
# Right now you can only create a Saved Search for the current user.
$params->{userid} = Bugzilla->user->id;
$dbh->bz_lock_tables('namedqueries WRITE',
'namedqueries_link_in_footer WRITE');
my $lif = delete $params->{link_in_footer};
my $obj = $class->insert_create_data($params);
if ($lif) {
$dbh->do('INSERT INTO namedqueries_link_in_footer
(user_id, namedquery_id) VALUES (?,?)',
undef, $params->{userid}, $obj->id);
}
$dbh->bz_unlock_tables();
return $obj;
}
#####################
# Complex Accessors #
#####################
sub edit_link {
my ($self) = @_;
return $self->{edit_link} if defined $self->{edit_link};
my $cgi = new Bugzilla::CGI($self->url);
if (!$cgi->param('query_type')
|| !IsValidQueryType($cgi->param('query_type')))
{
$cgi->param('query_type', 'advanced');
}
$self->{edit_link} = $cgi->canonicalise_query;
return $self->{edit_link};
}
sub used_in_whine {
my ($self) = @_;
return $self->{used_in_whine} if exists $self->{used_in_whine};
($self->{used_in_whine}) = Bugzilla->dbh->selectrow_array(
'SELECT 1 FROM whine_events INNER JOIN whine_queries
ON whine_events.id = whine_queries.eventid
WHERE whine_events.owner_userid = ? AND query_name = ?', undef,
$self->{userid}, $self->name) || 0;
return $self->{used_in_whine};
}
sub link_in_footer {
my ($self, $user) = @_;
# We only cache link_in_footer for the current Bugzilla->user.
return $self->{link_in_footer} if exists $self->{link_in_footer} && !$user;
my $user_id = $user ? $user->id : Bugzilla->user->id;
my $link_in_footer = Bugzilla->dbh->selectrow_array(
'SELECT 1 FROM namedqueries_link_in_footer
WHERE namedquery_id = ? AND user_id = ?',
undef, $self->id, $user_id) || 0;
$self->{link_in_footer} = $link_in_footer if !$user;
return $link_in_footer;
}
sub shared_with_group {
my ($self) = @_;
return $self->{shared_with_group} if exists $self->{shared_with_group};
# Bugzilla only currently supports sharing with one group, even
# though the database backend allows for an infinite number.
my ($group_id) = Bugzilla->dbh->selectrow_array(
'SELECT group_id FROM namedquery_group_map WHERE namedquery_id = ?',
undef, $self->id);
$self->{shared_with_group} = $group_id ? new Bugzilla::Group($group_id)
: undef;
return $self->{shared_with_group};
}
####################
# Simple Accessors #
####################
sub bug_ids_only { return ($_[0]->{'query_type'} == LIST_OF_BUGS) ? 1 : 0; }
sub url { return $_[0]->{'query'}; }
sub user {
my ($self) = @_;
return $self->{user} if defined $self->{user};
$self->{user} = new Bugzilla::User($self->{userid});
return $self->{user};
}
############
# Mutators #
############
sub set_url { $_[0]->set('query', $_[1]); }
sub set_query_type { $_[0]->set('query_type', $_[1]); }
1;
__END__
=head1 NAME
Bugzilla::Search::Saved - A saved search
=head1 SYNOPSIS
use Bugzilla::Search::Saved;
my $query = new Bugzilla::Search::Saved($query_id);
my $edit_link = $query->edit_link;
my $search_url = $query->url;
my $owner = $query->user;
=head1 DESCRIPTION
This module exists to represent a L<Bugzilla::Search> that has been
saved to the database.
This is an implementation of L<Bugzilla::Object>, and so has all the
same methods available as L<Bugzilla::Object>, in addition to what is
documented below.
=head1 METHODS
=head2 Constructors and Database Manipulation
=over
=item C<new>
Does not accept a bare C<name> argument. Instead, accepts only an id.
See also: L<Bugzilla::Object/new>.
=back
=head2 Accessors
These return data about the object, without modifying the object.
=over
=item C<edit_link>
A url with which you can edit the search.
=item C<url>
The CGI parameters for the search, as a string.
=item C<link_in_footer>
Whether or not this search should be displayed in the footer for the
I<current user> (not the owner of the search, but the person actually
using Bugzilla right now).
=item C<bug_ids_only>
True if the search contains only a list of Bug IDs.
=item C<shared_with_group>
The L<Bugzilla::Group> that this search is shared with. C<undef> if
this search isn't shared.
=back

View File

@@ -1,258 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
# Lance Larsh <lance.larsh@oracle.com>
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::Error;
use Bugzilla::Util;
use Bugzilla::User;
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;
# Create a ref to an empty hash and bless it
my $self = {};
bless($self, $class);
my $arg_count = scalar(@_);
# new() can return undef if you pass in a series_id and the user doesn't
# have sufficient permissions. If you create a new series in this way,
# you need to check for an undef return, and act appropriately.
my $retval = $self;
# There are three ways of creating Series objects. Two (CGI and Parameters)
# are for use when creating a new series. One (Database) is for retrieving
# information on existing series.
if ($arg_count == 1) {
if (ref($_[0])) {
# We've been given a CGI object to create a new Series from.
# This series may already exist - external code needs to check
# before it calls writeToDatabase().
$self->initFromCGI($_[0]);
}
else {
# We've been given a series_id, which should represent an existing
# Series.
$retval = $self->initFromDatabase($_[0]);
}
}
elsif ($arg_count >= 6 && $arg_count <= 8) {
# We've been given a load of parameters to create a new Series from.
# Currently, undef is always passed as the first parameter; this allows
# you to call writeToDatabase() unconditionally.
$self->initFromParameters(@_);
}
else {
die("Bad parameters passed in - invalid number of args: $arg_count");
}
return $retval;
}
sub initFromDatabase {
my $self = shift;
my $series_id = shift;
detaint_natural($series_id)
|| ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
my $dbh = Bugzilla->dbh;
my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
"cc2.name, series.name, series.creator, series.frequency, " .
"series.query, series.is_public " .
"FROM series " .
"LEFT JOIN series_categories AS cc1 " .
" ON series.category = cc1.id " .
"LEFT JOIN series_categories AS cc2 " .
" ON series.subcategory = cc2.id " .
"LEFT JOIN category_group_map AS cgm " .
" ON series.category = cgm.category_id " .
"LEFT JOIN user_group_map AS ugm " .
" ON cgm.group_id = ugm.group_id " .
" AND ugm.user_id = " . Bugzilla->user->id .
" AND isbless = 0 " .
"WHERE series.series_id = $series_id AND " .
"(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
"(ugm.group_id IS NOT NULL)) " .
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
'series.name, series.creator, series.frequency, ' .
'series.query, series.is_public'));
if (@series) {
$self->initFromParameters(@series);
return $self;
}
else {
return undef;
}
}
sub initFromParameters {
# Pass undef as the first parameter if you are creating a new series.
my $self = shift;
($self->{'series_id'}, $self->{'category'}, $self->{'subcategory'},
$self->{'name'}, $self->{'creator'}, $self->{'frequency'},
$self->{'query'}, $self->{'public'}) = @_;
}
sub initFromCGI {
my $self = shift;
my $cgi = shift;
$self->{'series_id'} = $cgi->param('series_id') || undef;
if (defined($self->{'series_id'})) {
detaint_natural($self->{'series_id'})
|| ThrowCodeError("invalid_series_id",
{ 'series_id' => $self->{'series_id'} });
}
$self->{'category'} = $cgi->param('category')
|| $cgi->param('newcategory')
|| ThrowUserError("missing_category");
$self->{'subcategory'} = $cgi->param('subcategory')
|| $cgi->param('newsubcategory')
|| ThrowUserError("missing_subcategory");
$self->{'name'} = $cgi->param('name')
|| ThrowUserError("missing_name");
$self->{'creator'} = Bugzilla->user->id;
$self->{'frequency'} = $cgi->param('frequency');
detaint_natural($self->{'frequency'})
|| ThrowUserError("missing_frequency");
$self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
"category", "subcategory", "name",
"frequency", "public", "query_format");
trick_taint($self->{'query'});
$self->{'public'} = $cgi->param('public') ? 1 : 0;
# Change 'admin' here and in series.html.tmpl, or remove the check
# completely, if you want to change who can make series public.
$self->{'public'} = 0 unless Bugzilla->user->in_group('admin');
}
sub writeToDatabase {
my $self = shift;
my $dbh = Bugzilla->dbh;
$dbh->bz_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 = ?, is_public = ? " .
"WHERE series_id = ?", undef,
$category_id, $subcategory_id, $self->{'name'},
$self->{'frequency'}, $self->{'public'},
$self->{'series_id'});
}
else {
# Insert the new series into the series table
$dbh->do("INSERT INTO series (creator, category, subcategory, " .
"name, frequency, query, is_public) VALUES " .
"(?, ?, ?, ?, ?, ?, ?)", undef,
$self->{'creator'}, $category_id, $subcategory_id, $self->{'name'},
$self->{'frequency'}, $self->{'query'}, $self->{'public'});
# Retrieve series_id
$self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
"FROM series");
$self->{'series_id'}
|| ThrowCodeError("missing_series_id", { 'series' => $self });
}
$dbh->bz_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;

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
#
package Bugzilla::Template::Plugin::Bugzilla;
use strict;
use base qw(Template::Plugin);
use Bugzilla;
sub new {
my ($class, $context) = @_;
return bless {}, $class;
}
sub AUTOLOAD {
my $class = shift;
our $AUTOLOAD;
$AUTOLOAD =~ s/^.*:://;
return if $AUTOLOAD eq 'DESTROY';
return Bugzilla->$AUTOLOAD(@_);
}
1;
__END__
=head1 NAME
Bugzilla::Template::Plugin::Bugzilla
=head1 DESCRIPTION
Template Toolkit plugin to allow access to the persistent C<Bugzilla>
object.
=head1 SEE ALSO
L<Bugzilla>, L<Template::Plugin>

View File

@@ -1,141 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Zach Lipton <zach@zachlipton.com>
#
package Bugzilla::Template::Plugin::Hook;
use strict;
use Bugzilla::Constants;
use Bugzilla::Template;
use Bugzilla::Util;
use Bugzilla::Error;
use File::Spec;
use base qw(Template::Plugin);
sub load {
my ($class, $context) = @_;
return $class;
}
sub new {
my ($class, $context) = @_;
return bless { _CONTEXT => $context }, $class;
}
sub process {
my ($self, $hook_name) = @_;
my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
my $template = $self->{_CONTEXT}->stash->{component}->{name};
my @hooks = ();
# sanity check:
if (!$template =~ /[\w\.\/\-_\\]+/) {
ThrowCodeError('template_invalid', { name => $template});
}
# also get extension hook files that live in extensions/:
# parse out the parts of the template name
my ($vol, $subpath, $filename) = File::Spec->splitpath($template);
$subpath = $subpath || '';
$filename =~ m/(.*)\.(.*)\.tmpl/;
my $templatename = $1;
my $type = $2;
# munge the filename to create the extension hook filename:
my $extensiontemplate = $subpath.'/'.$templatename.'-'.$hook_name.'.'.$type.'.tmpl';
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
my @usedlanguages = getLanguages();
foreach my $extension (@extensions) {
foreach my $language (@usedlanguages) {
my $file = $extension.'/template/'.$language.'/'.$extensiontemplate;
if (-e $file) {
# tt is stubborn and won't take a template file not in its
# include path, so we open a filehandle and give it to process()
# so the hook gets invoked:
open (my $fh, $file);
push(@hooks, $fh);
}
}
}
# we keep this too since you can still put hook templates in
# template/en/custom/hook
foreach my $path (@$paths) {
my @files = glob("$path/hook/$template/$hook_name/*.tmpl");
# Have to remove the templates path (INCLUDE_PATH) from the
# file path since the template processor auto-adds it back.
@files = map($_ =~ /^$path\/(.*)$/ ? $1 : {}, @files);
# Add found files to the list of hooks, but removing duplicates,
# which can happen when there are identical hooks or duplicate
# directories in the INCLUDE_PATH (the latter probably being a TT bug).
foreach my $file (@files) {
push(@hooks, $file) unless grep($file eq $_, @hooks);
}
}
my $output;
foreach my $hook (@hooks) {
$output .= $self->{_CONTEXT}->process($hook);
}
return $output;
}
# get a list of languages we accept so we can find the hook
# that corresponds to our desired languages:
sub getLanguages() {
my $languages = trim(Bugzilla->params->{'languages'});
if (not ($languages =~ /,/)) { # only one language
return $languages;
}
my @languages = Bugzilla::Template::sortAcceptLanguage($languages);
my @accept_language = Bugzilla::Template::sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "" );
my @usedlanguages;
foreach my $lang (@accept_language) {
if(my @found = grep /^\Q$lang\E(-.+)?$/i, @languages) {
push (@usedlanguages, @found);
}
}
return @usedlanguages;
}
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:E<sol>E<sol>www.bugzilla.orgE<sol>docsE<sol>tipE<sol>htmlE<sol>customization.html>
L<http:E<sol>E<sol>bugzilla.mozilla.orgE<sol>show_bug.cgi?id=229658>
L<http:E<sol>E<sol>bugzilla.mozilla.orgE<sol>show_bug.cgi?id=298341>

View File

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

View File

@@ -1,567 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
################################################################################
# Module Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use strict;
# Bundle the functions in this file together into the "Bugzilla::Token" package.
package Bugzilla::Token;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Mailer;
use Bugzilla::Util;
use Bugzilla::User;
use Date::Format;
use Date::Parse;
use File::Basename;
use base qw(Exporter);
@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token);
################################################################################
# Public Functions
################################################################################
# Creates and sends a token to create a new user account.
# It assumes that the login has the correct format and is not already in use.
sub issue_new_user_account_token {
my $login_name = shift;
my $dbh = Bugzilla->dbh;
my $template = Bugzilla->template;
my $vars = {};
# Is there already a pending request for this login name? If yes, do not throw
# an error because the user may have lost his email with the token inside.
# But to prevent using this way to mailbomb an email address, make sure
# the last request is at least 10 minutes old before sending a new email.
my $pending_requests =
$dbh->selectrow_array('SELECT COUNT(*)
FROM tokens
WHERE tokentype = ?
AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
AND issuedate > NOW() - ' . $dbh->sql_interval(10, 'MINUTE'),
undef, ('account', $login_name));
ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests;
my ($token, $token_ts) = _create_token(undef, 'account', $login_name);
$vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'};
$vars->{'token_ts'} = $token_ts;
$vars->{'token'} = $token;
my $message;
$template->process('account/email/request-new.txt.tmpl', $vars, \$message)
|| ThrowTemplateError($template->error());
# In 99% of cases, the user getting the confirmation email is the same one
# who made the request, and so it is reasonable to send the email in the same
# language used to view the "Create a New Account" page (we cannot use his
# user prefs as the user has no account yet!).
MessageToMTA($message);
}
sub IssueEmailChangeToken {
my ($user, $old_email, $new_email) = @_;
my $email_suffix = Bugzilla->params->{'emailsuffix'};
my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email);
my $newtoken = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email);
# Mail the user the token along with instructions for using it.
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
my $vars = {};
$vars->{'oldemailaddress'} = $old_email . $email_suffix;
$vars->{'newemailaddress'} = $new_email . $email_suffix;
$vars->{'max_token_age'} = MAX_TOKEN_AGE;
$vars->{'token_ts'} = $token_ts;
$vars->{'token'} = $token;
$vars->{'emailaddress'} = $old_email . $email_suffix;
my $message;
$template->process("account/email/change-old.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
MessageToMTA($message);
$vars->{'token'} = $newtoken;
$vars->{'emailaddress'} = $new_email . $email_suffix;
$message = "";
$template->process("account/email/change-new.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($message);
}
# Generates a random token, adds it to the tokens table, and sends it
# to the user with instructions for using it to change their password.
sub IssuePasswordToken {
my $user = shift;
my $dbh = Bugzilla->dbh;
my $too_soon =
$dbh->selectrow_array('SELECT 1 FROM tokens
WHERE userid = ?
AND tokentype = ?
AND issuedate > NOW() - ' .
$dbh->sql_interval(10, 'MINUTE'),
undef, ($user->id, 'password'));
ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon;
my ($token, $token_ts) = _create_token($user->id, 'password', $::ENV{'REMOTE_ADDR'});
# Mail the user the token along with instructions for using it.
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
my $vars = {};
$vars->{'token'} = $token;
$vars->{'emailaddress'} = $user->email;
$vars->{'max_token_age'} = MAX_TOKEN_AGE;
$vars->{'token_ts'} = $token_ts;
my $message = "";
$template->process("account/password/forgotten-password.txt.tmpl",
$vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($message);
}
sub issue_session_token {
# Generates a random token, adds it to the tokens table, and returns
# the token to the caller.
my $data = shift;
return _create_token(Bugzilla->user->id, 'session', $data);
}
sub CleanTokenTable {
my $dbh = Bugzilla->dbh;
$dbh->bz_lock_tables('tokens WRITE');
$dbh->do('DELETE FROM tokens
WHERE ' . $dbh->sql_to_days('NOW()') . ' - ' .
$dbh->sql_to_days('issuedate') . ' >= ?',
undef, MAX_TOKEN_AGE);
$dbh->bz_unlock_tables();
}
sub GenerateUniqueToken {
# Generates a unique random token. Uses generate_random_password
# for the tokens themselves and checks uniqueness by searching for
# the token in the "tokens" table. Gives up if it can't come up
# with a token after about one hundred tries.
my ($table, $column) = @_;
my $token;
my $duplicate = 1;
my $tries = 0;
$table ||= "tokens";
$column ||= "token";
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare("SELECT userid FROM $table WHERE $column = ?");
while ($duplicate) {
++$tries;
if ($tries > 100) {
ThrowCodeError("token_generation_error");
}
$token = generate_random_password();
$sth->execute($token);
$duplicate = $sth->fetchrow_array;
}
return $token;
}
# Cancels a previously issued token and notifies the user.
# This should only happen when the user accidentally makes a token request
# or when a malicious hacker makes a token request on behalf of a user.
sub Cancel {
my ($token, $cancelaction, $vars) = @_;
my $dbh = Bugzilla->dbh;
$vars ||= {};
# Get information about the token being canceled.
trick_taint($token);
my ($issuedate, $tokentype, $eventdata, $userid) =
$dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
tokentype, eventdata, userid
FROM tokens
WHERE token = ?',
undef, $token);
# If we are canceling the creation of a new user account, then there
# is no entry in the 'profiles' table.
my $user = new Bugzilla::User($userid);
$vars->{'emailaddress'} = $userid ? $user->email : $eventdata;
$vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
$vars->{'token'} = $token;
$vars->{'tokentype'} = $tokentype;
$vars->{'issuedate'} = $issuedate;
$vars->{'eventdata'} = $eventdata;
$vars->{'cancelaction'} = $cancelaction;
# Notify the user via email about the cancellation.
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
my $message;
$template->process("account/cancel-token.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($message);
# Delete the token from the database.
delete_token($token);
}
sub DeletePasswordTokens {
my ($userid, $reason) = @_;
my $dbh = Bugzilla->dbh;
detaint_natural($userid);
my $tokens = $dbh->selectcol_arrayref('SELECT token FROM tokens
WHERE userid = ? AND tokentype = ?',
undef, ($userid, 'password'));
foreach my $token (@$tokens) {
Bugzilla::Token::Cancel($token, $reason);
}
}
# Returns an email change token if the user has one.
sub HasEmailChangeToken {
my $userid = shift;
my $dbh = Bugzilla->dbh;
my $token = $dbh->selectrow_array('SELECT token FROM tokens
WHERE userid = ?
AND (tokentype = ? OR tokentype = ?) ' .
$dbh->sql_limit(1),
undef, ($userid, 'emailnew', 'emailold'));
return $token;
}
# Returns the userid, issuedate and eventdata for the specified token
sub GetTokenData {
my ($token) = @_;
my $dbh = Bugzilla->dbh;
return unless defined $token;
$token = clean_text($token);
trick_taint($token);
return $dbh->selectrow_array(
"SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
FROM tokens
WHERE token = ?", undef, $token);
}
# Deletes specified token
sub delete_token {
my ($token) = @_;
my $dbh = Bugzilla->dbh;
return unless defined $token;
trick_taint($token);
$dbh->bz_lock_tables('tokens WRITE');
$dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token);
$dbh->bz_unlock_tables();
}
# Given a token, makes sure it comes from the currently logged in user
# and match the expected event. Returns 1 on success, else displays a warning.
# Note: this routine must not be called while tables are locked as it will try
# to lock some tables itself, see CleanTokenTable().
sub check_token_data {
my ($token, $expected_action) = @_;
my $user = Bugzilla->user;
my $template = Bugzilla->template;
my $cgi = Bugzilla->cgi;
my ($creator_id, $date, $token_action) = GetTokenData($token);
unless ($creator_id
&& $creator_id == $user->id
&& $token_action eq $expected_action)
{
# Something is going wrong. Ask confirmation before processing.
# It is possible that someone tried to trick an administrator.
# In this case, we want to know his name!
require Bugzilla::User;
my $vars = {};
$vars->{'abuser'} = Bugzilla::User->new($creator_id)->identity;
$vars->{'token_action'} = $token_action;
$vars->{'expected_action'} = $expected_action;
$vars->{'script_name'} = basename($0);
# Now is a good time to remove old tokens from the DB.
CleanTokenTable();
# If no token was found, create a valid token for the given action.
unless ($creator_id) {
$token = issue_session_token($expected_action);
$cgi->param('token', $token);
}
print $cgi->header();
$template->process('admin/confirm-action.html.tmpl', $vars)
|| ThrowTemplateError($template->error());
exit;
}
return 1;
}
################################################################################
# Internal Functions
################################################################################
# Generates a unique token and inserts it into the database
# Returns the token and the token timestamp
sub _create_token {
my ($userid, $tokentype, $eventdata) = @_;
my $dbh = Bugzilla->dbh;
detaint_natural($userid) if defined $userid;
trick_taint($tokentype);
trick_taint($eventdata);
$dbh->bz_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;
__END__
=head1 NAME
Bugzilla::Token - Provides different routines to manage tokens.
=head1 SYNOPSIS
use Bugzilla::Token;
Bugzilla::Token::issue_new_user_account_token($login_name);
Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
Bugzilla::Token::IssuePasswordToken($user);
Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
Bugzilla::Token::Cancel($token, $cancelaction, $vars);
Bugzilla::Token::CleanTokenTable();
my $token = issue_session_token($event);
check_token_data($token, $event)
delete_token($token);
my $token = Bugzilla::Token::GenerateUniqueToken($table, $column);
my $token = Bugzilla::Token::HasEmailChangeToken($user_id);
my ($token, $date, $data) = Bugzilla::Token::GetTokenData($token);
=head1 SUBROUTINES
=over
=item C<issue_new_user_account_token($login_name)>
Description: Creates and sends a token per email to the email address
requesting a new user account. It doesn't check whether
the user account already exists. The user will have to
use this token to confirm the creation of his user account.
Params: $login_name - The new login name requested by the user.
Returns: Nothing. It throws an error if the same user made the same
request in the last few minutes.
=item C<sub IssueEmailChangeToken($user, $old_email, $new_email)>
Description: Sends two distinct tokens per email to the old and new email
addresses to confirm the email address change for the given
user. These tokens remain valid for the next MAX_TOKEN_AGE days.
Params: $user - User object of the user requesting a new
email address.
$old_email - The current (old) email address of the user.
$new_email - The new email address of the user.
Returns: Nothing.
=item C<IssuePasswordToken($user)>
Description: Sends a token per email to the given user. This token
can be used to change the password (e.g. in case the user
cannot remember his password and wishes to enter a new one).
Params: $user - User object of the user requesting a new password.
Returns: Nothing. It throws an error if the same user made the same
request in the last few minutes.
=item C<CleanTokenTable()>
Description: Removes all tokens older than MAX_TOKEN_AGE days from the DB.
This means that these tokens will now be considered as invalid.
Params: None.
Returns: Nothing.
=item C<GenerateUniqueToken($table, $column)>
Description: Generates and returns a unique token. This token is unique
in the $column of the $table. This token is NOT stored in the DB.
Params: $table (optional): The table to look at (default: tokens).
$column (optional): The column to look at for uniqueness (default: token).
Returns: A token which is unique in $column.
=item C<Cancel($token, $cancelaction, $vars)>
Description: Invalidates an existing token, generally when the token is used
for an action which is not the one expected. An email is sent
to the user who originally requested this token to inform him
that this token has been invalidated (e.g. because an hacker
tried to use this token for some malicious action).
Params: $token: The token to invalidate.
$cancelaction: The reason why this token is invalidated.
$vars: Some additional information about this action.
Returns: Nothing.
=item C<DeletePasswordTokens($user_id, $reason)>
Description: Cancels all password tokens for the given user. Emails are sent
to the user to inform him about this action.
Params: $user_id: The user ID of the user account whose password tokens
are canceled.
$reason: The reason why these tokens are canceled.
Returns: Nothing.
=item C<HasEmailChangeToken($user_id)>
Description: Returns any existing token currently used for an email change
for the given user.
Params: $user_id - A user ID.
Returns: A token if it exists, else undef.
=item C<GetTokenData($token)>
Description: Returns all stored data for the given token.
Params: $token - A valid token.
Returns: The user ID, the date and time when the token was created and
the (event)data stored with that token.
=back
=head2 Security related routines
The following routines have been written to be used together as described below,
although they can be used separately.
=over
=item C<issue_session_token($event)>
Description: Creates and returns a token used internally.
Params: $event - The event which needs to be stored in the DB for future
reference/checks.
Returns: A unique token.
=item C<check_token_data($token, $event)>
Description: Makes sure the $token has been created by the currently logged in
user and to be used for the given $event. If this token is used for
an unexpected action (i.e. $event doesn't match the information stored
with the token), a warning is displayed asking whether the user really
wants to continue. On success, it returns 1.
This is the routine to use for security checks, combined with
issue_session_token() and delete_token() as follows:
1. First, create a token for some coming action.
my $token = issue_session_token($action);
2. Some time later, it's time to make sure that the expected action
is going to be executed, and by the expected user.
check_token_data($token, $action);
3. The check has been done and we no longer need this token.
delete_token($token);
Params: $token - The token used for security checks.
$event - The expected event to be run.
Returns: 1 on success, else a warning is thrown.
=item C<delete_token($token)>
Description: Deletes the specified token. No notification is sent.
Params: $token - The token to delete.
Returns: Nothing.
=back
=cut

View File

@@ -1,221 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
package Bugzilla::Update;
use strict;
use Bugzilla::Constants;
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
use constant LOCAL_FILE => "/bugzilla-update.xml"; # Relative to datadir.
use constant TIME_INTERVAL => 86400; # Default is one day, in seconds.
use constant TIMEOUT => 5; # Number of seconds before timeout.
# Look for new releases and notify logged in administrators about them.
sub get_notifications {
return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled');
# If the XML::Twig module is missing, we won't be able to parse
# the XML file. So there is no need to go further.
eval("require XML::Twig");
return if $@;
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
# Update the local XML file if this one doesn't exist or if
# the last modification time (stat[9]) is older than TIME_INTERVAL.
if (!-e $local_file || (time() - (stat($local_file))[9] > TIME_INTERVAL)) {
# Are we sure we didn't try to refresh this file already
# but we failed because we cannot modify its timestamp?
my $can_alter = (-e $local_file) ? utime(undef, undef, $local_file) : 1;
if ($can_alter) {
unlink $local_file; # Make sure the old copy is away.
my $error = _synchronize_data();
# If an error is returned, leave now.
return $error if $error;
}
else {
return {'error' => 'no_update', 'xml_file' => $local_file};
}
}
# If we cannot access the local XML file, ignore it.
return {'error' => 'no_access', 'xml_file' => $local_file} unless (-r $local_file);
my $twig = XML::Twig->new();
$twig->safe_parsefile($local_file);
# If the XML file is invalid, return.
return {'error' => 'corrupted', 'xml_file' => $local_file} if $@;
my $root = $twig->root;
my @releases;
foreach my $branch ($root->children('branch')) {
my $release = {
'branch_ver' => $branch->{'att'}->{'id'},
'latest_ver' => $branch->{'att'}->{'vid'},
'status' => $branch->{'att'}->{'status'},
'url' => $branch->{'att'}->{'url'},
'date' => $branch->{'att'}->{'date'}
};
push(@releases, $release);
}
# On which branch is the current installation running?
my @current_version =
(BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
my @release;
if (Bugzilla->params->{'upgrade_notification'} eq 'development_snapshot') {
@release = grep {$_->{'status'} eq 'development'} @releases;
# If there is no development snapshot available, then we are in the
# process of releasing a release candidate. That's the release we want.
unless (scalar(@release)) {
@release = grep {$_->{'status'} eq 'release-candidate'} @releases;
}
}
elsif (Bugzilla->params->{'upgrade_notification'} eq 'latest_stable_release') {
@release = grep {$_->{'status'} eq 'stable'} @releases;
}
elsif (Bugzilla->params->{'upgrade_notification'} eq 'stable_branch_release') {
# We want the latest stable version for the current branch.
# If we are running a development snapshot, we won't match anything.
my $branch_version = $current_version[0] . '.' . $current_version[1];
# We do a string comparison instead of a numerical one, because
# e.g. 2.2 == 2.20, but 2.2 ne 2.20 (and 2.2 is indeed much older).
@release = grep {$_->{'branch_ver'} eq $branch_version} @releases;
# If the branch is now closed, we should strongly suggest
# to upgrade to the latest stable release available.
if (scalar(@release) && $release[0]->{'status'} eq 'closed') {
@release = grep {$_->{'status'} eq 'stable'} @releases;
return {'data' => $release[0], 'deprecated' => $branch_version};
}
}
else {
# Unknown parameter.
return {'error' => 'unknown_parameter'};
}
# Return if no new release is available.
return unless scalar(@release);
# Only notify the administrator if the latest version available
# is newer than the current one.
my @new_version =
($release[0]->{'latest_ver'} =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
# We convert release candidates 'rc' to integers (rc ? 0 : 1) in order
# to compare versions easily.
$current_version[2] = ($current_version[2] && $current_version[2] eq 'rc') ? 0 : 1;
$new_version[2] = ($new_version[2] && $new_version[2] eq 'rc') ? 0 : 1;
my $is_newer = _compare_versions(\@current_version, \@new_version);
return ($is_newer == 1) ? {'data' => $release[0]} : undef;
}
sub _synchronize_data {
eval("require LWP::UserAgent");
return {'error' => 'missing_package', 'package' => 'LWP::UserAgent'} if $@;
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
my $ua = LWP::UserAgent->new();
$ua->timeout(TIMEOUT);
$ua->protocols_allowed(['http', 'https']);
# If the URL of the proxy is given, use it, else get this information
# from the environment variable.
my $proxy_url = Bugzilla->params->{'proxy_url'};
if ($proxy_url) {
$ua->proxy(['http', 'https'], $proxy_url);
}
else {
$ua->env_proxy;
}
$ua->mirror(REMOTE_FILE, $local_file);
# $ua->mirror() forces the modification time of the local XML file
# to match the modification time of the remote one.
# So we have to update it manually to reflect that a newer version
# of the file has effectively been requested. This will avoid
# any new download for the next TIME_INTERVAL.
if (-e $local_file) {
# Try to alter its last modification time.
my $can_alter = utime(undef, undef, $local_file);
# This error should never happen.
$can_alter || return {'error' => 'no_update', 'xml_file' => $local_file};
}
else {
# We have been unable to download the file.
return {'error' => 'cannot_download', 'xml_file' => $local_file};
}
# Everything went well.
return 0;
}
sub _compare_versions {
my ($old_ver, $new_ver) = @_;
while (scalar(@$old_ver) && scalar(@$new_ver)) {
my $old = shift(@$old_ver) || 0;
my $new = shift(@$new_ver) || 0;
return $new <=> $old if ($new <=> $old);
}
return scalar(@$new_ver) <=> scalar(@$old_ver);
}
1;
__END__
=head1 NAME
Bugzilla::Update - Update routines for Bugzilla
=head1 SYNOPSIS
use Bugzilla::Update;
# Get information about new releases
my $new_release = Bugzilla::Update::get_notifications();
=head1 DESCRIPTION
This module contains all required routines to notify you
about new releases. It downloads an XML file from bugzilla.org
and parses it, in order to display information based on your
preferences. Absolutely no information about the Bugzilla version
you are running is sent to bugzilla.org.
=head1 FUNCTIONS
=over
=item C<get_notifications()>
Description: This function informs you about new releases, if any.
Params: None.
Returns: On success, a reference to a hash with data about
new releases, if any.
On failure, a reference to a hash with the reason
of the failure and the name of the unusable XML file.
=back
=cut

File diff suppressed because it is too large Load Diff

View File

@@ -1,426 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Marc Schumann <wurblzap@gmail.com>
# Frédéric Buclin <LpSolit@gmail.com>
package Bugzilla::User::Setting;
use strict;
use base qw(Exporter);
# Module stuff
@Bugzilla::User::Setting::EXPORT = qw(get_all_settings get_defaults
add_setting);
use Bugzilla::Error;
use Bugzilla::Util qw(trick_taint get_text);
###############################
### Module Initialization ###
###############################
sub new {
my $invocant = shift;
my $setting_name = shift;
my $user_id = shift;
my $class = ref($invocant) || $invocant;
my $subclass = '';
# Create a ref to an empty hash and bless it
my $self = {};
my $dbh = Bugzilla->dbh;
# Confirm that the $setting_name is properly formed;
# if not, throw a code error.
#
# NOTE: due to the way that setting names are used in templates,
# they must conform to to the limitations set for HTML NAMEs and IDs.
#
if ( !($setting_name =~ /^[a-zA-Z][-.:\w]*$/) ) {
ThrowCodeError("setting_name_invalid", { name => $setting_name });
}
# If there were only two parameters passed in, then we need
# to retrieve the information for this setting ourselves.
if (scalar @_ == 0) {
my ($default, $is_enabled, $value);
($default, $is_enabled, $value, $subclass) =
$dbh->selectrow_array(
q{SELECT default_value, is_enabled, setting_value, subclass
FROM setting
LEFT JOIN profile_setting
ON setting.name = profile_setting.setting_name
WHERE name = ?
AND profile_setting.user_id = ?},
undef,
$setting_name, $user_id);
# if not defined, then grab the default value
if (! defined $value) {
($default, $is_enabled, $subclass) =
$dbh->selectrow_array(
q{SELECT default_value, is_enabled, subclass
FROM setting
WHERE name = ?},
undef,
$setting_name);
}
$self->{'is_enabled'} = $is_enabled;
$self->{'default_value'} = $default;
# IF the setting is enabled, AND the user has chosen a setting
# THEN return that value
# ELSE return the site default, and note that it is the default.
if ( ($is_enabled) && (defined $value) ) {
$self->{'value'} = $value;
} else {
$self->{'value'} = $default;
$self->{'isdefault'} = 1;
}
}
else {
($subclass) = $dbh->selectrow_array(
q{SELECT subclass FROM setting WHERE name = ?},
undef,
$setting_name);
# 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;
}
if ($subclass) {
eval('require ' . $class . '::' . $subclass);
$@ && ThrowCodeError('setting_subclass_invalid',
{'subclass' => $subclass});
$class = $class . '::' . $subclass;
}
bless($self, $class);
$self->{'_setting_name'} = $setting_name;
$self->{'_user_id'} = $user_id;
return $self;
}
###############################
### Subroutine Definitions ###
###############################
sub add_setting {
my ($name, $values, $default_value, $subclass, $force_check) = @_;
my $dbh = Bugzilla->dbh;
my $exists = _setting_exists($name);
return if ($exists && !$force_check);
($name && $default_value)
|| ThrowCodeError("setting_info_invalid");
if ($exists) {
# If this setting exists, we delete it and regenerate it.
$dbh->do('DELETE FROM setting_value WHERE name = ?', undef, $name);
$dbh->do('DELETE FROM setting WHERE name = ?', undef, $name);
# Remove obsolete user preferences for this setting.
my $list = join(', ', map {$dbh->quote($_)} @$values);
$dbh->do("DELETE FROM profile_setting
WHERE setting_name = ? AND setting_value NOT IN ($list)",
undef, $name);
}
else {
print get_text('install_setting_new', { name => $name }) . "\n";
}
$dbh->do(q{INSERT INTO setting (name, default_value, is_enabled, subclass)
VALUES (?, ?, 1, ?)},
undef, ($name, $default_value, $subclass));
my $sth = $dbh->prepare(q{INSERT INTO setting_value (name, value, sortindex)
VALUES (?, ?, ?)});
my $sortindex = 5;
foreach my $key (@$values){
$sth->execute($name, $key, $sortindex);
$sortindex += 5;
}
}
sub get_all_settings {
my ($user_id) = @_;
my $settings = get_defaults($user_id); # first get the defaults
my $dbh = Bugzilla->dbh;
my $sth = $dbh->prepare(
q{SELECT name, default_value, is_enabled, setting_value
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> - arrayref - contains the new choices
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

View File

@@ -1,78 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
#
package Bugzilla::User::Setting::Skin;
use strict;
use base qw(Bugzilla::User::Setting);
use Bugzilla::Constants;
use File::Spec::Functions;
use File::Basename;
use constant BUILTIN_SKIN_NAMES => ['standard'];
sub legal_values {
my ($self) = @_;
return $self->{'legal_values'} if defined $self->{'legal_values'};
my $dirbase = bz_locations()->{'skinsdir'} . '/contrib';
# Avoid modification of the list BUILTIN_SKIN_NAMES points to by copying the
# list over instead of simply writing $legal_values = BUILTIN_SKIN_NAMES.
my @legal_values = @{(BUILTIN_SKIN_NAMES)};
foreach my $direntry (glob(catdir($dirbase, '*'))) {
if (-d $direntry) {
# Stylesheet set
push(@legal_values, basename($direntry));
}
elsif ($direntry =~ /\.css$/) {
# Single-file stylesheet
push(@legal_values, basename($direntry));
}
}
return $self->{'legal_values'} = \@legal_values;
}
1;
__END__
=head1 NAME
Bugzilla::User::Setting::Skin - Object for a user preference setting for skins
=head1 DESCRIPTION
Skin.pm extends Bugzilla::User::Setting and implements a class specialized for
skins settings.
=head1 METHODS
=over
=item C<legal_values()>
Description: Returns all legal skins
Params: none
Returns: A reference to an array containing the names of all legal skins
=back

View File

@@ -1,907 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Jacob Steenhagen <jake@bugzilla.org>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Christopher Aillon <christopher@aillon.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Frédéric Buclin <LpSolit@gmail.com>
# Marc Schumann <wurblzap@gmail.com>
package Bugzilla::Util;
use strict;
use base qw(Exporter);
@Bugzilla::Util::EXPORT = qw(is_tainted trick_taint detaint_natural
detaint_signed
html_quote url_quote value_quote xml_quote
css_class_quote html_light_quote url_decode
i_am_cgi get_netaddr correct_urlbase
lsearch
diff_arrays diff_strings
trim wrap_comment find_wrap_point
perform_substs
format_time format_time_decimal validate_date
file_mod_time is_7bit_clean
bz_crypt generate_random_password
validate_email_syntax clean_text
get_text);
use Bugzilla::Constants;
use Date::Parse;
use Date::Format;
use Text::Wrap;
# This is from the perlsec page, slightly modified to remove a warning
# From that page:
# This function makes use of the fact that the presence of
# tainted data anywhere within an expression renders the
# entire expression tainted.
# Don't ask me how it works...
sub is_tainted {
return not eval { my $foo = join('',@_), kill 0; 1; };
}
sub trick_taint {
require Carp;
Carp::confess("Undef to trick_taint") unless defined $_[0];
my $match = $_[0] =~ /^(.*)$/s;
$_[0] = $match ? $1 : undef;
return (defined($_[0]));
}
sub detaint_natural {
my $match = $_[0] =~ /^(\d+)$/;
$_[0] = $match ? $1 : undef;
return (defined($_[0]));
}
sub detaint_signed {
my $match = $_[0] =~ /^([-+]?\d+)$/;
$_[0] = $match ? $1 : undef;
# Remove any leading plus sign.
if (defined($_[0]) && $_[0] =~ /^\+(\d+)$/) {
$_[0] = $1;
}
return (defined($_[0]));
}
sub html_quote {
my ($var) = (@_);
$var =~ s/\&/\&amp;/g;
$var =~ s/</\&lt;/g;
$var =~ s/>/\&gt;/g;
$var =~ s/\"/\&quot;/g;
return $var;
}
sub html_light_quote {
my ($text) = @_;
# List of allowed HTML elements having no attributes.
my @allow = qw(b strong em i u p br abbr acronym ins del cite code var
dfn samp kbd big small sub sup tt dd dt dl ul li ol);
# Are HTML::Scrubber and HTML::Parser installed?
eval { require HTML::Scrubber;
require HTML::Parser;
};
# We need utf8_mode() from HTML::Parser 3.40 if running Perl >= 5.8.
if ($@ || ($] >= 5.008 && $HTML::Parser::VERSION < 3.40)) { # Package(s) not installed.
my $safe = join('|', @allow);
my $chr = chr(1);
# First, escape safe elements.
$text =~ s#<($safe)>#$chr$1$chr#go;
$text =~ s#</($safe)>#$chr/$1$chr#go;
# Now filter < and >.
$text =~ s#<#&lt;#g;
$text =~ s#>#&gt;#g;
# Restore safe elements.
$text =~ s#$chr/($safe)$chr#</$1>#go;
$text =~ s#$chr($safe)$chr#<$1>#go;
return $text;
}
else { # Packages installed.
# We can be less restrictive. We can accept elements with attributes.
push(@allow, qw(a blockquote q span));
# Allowed protocols.
my $safe_protocols = join('|', SAFE_PROTOCOLS);
my $protocol_regexp = qr{(^(?:$safe_protocols):|^[^:]+$)}i;
# Deny all elements and attributes unless explicitly authorized.
my @default = (0 => {
id => 1,
name => 1,
class => 1,
'*' => 0, # Reject all other attributes.
}
);
# Specific rules for allowed elements. If no specific rule is set
# for a given element, then the default is used.
my @rules = (a => {
href => $protocol_regexp,
title => 1,
id => 1,
name => 1,
class => 1,
'*' => 0, # Reject all other attributes.
},
blockquote => {
cite => $protocol_regexp,
id => 1,
name => 1,
class => 1,
'*' => 0, # Reject all other attributes.
},
'q' => {
cite => $protocol_regexp,
id => 1,
name => 1,
class => 1,
'*' => 0, # Reject all other attributes.
},
);
my $scrubber = HTML::Scrubber->new(default => \@default,
allow => \@allow,
rules => \@rules,
comment => 0,
process => 0);
# Avoid filling the web server error log with Perl 5.8.x.
# In HTML::Scrubber 0.08, the HTML::Parser object is stored in
# the "_p" key, but this may change in future versions.
if ($] >= 5.008 && ref($scrubber->{_p}) eq 'HTML::Parser') {
$scrubber->{_p}->utf8_mode(1);
}
return $scrubber->scrub($text);
}
}
# This originally came from CGI.pm, by Lincoln D. Stein
sub url_quote {
my ($toencode) = (@_);
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
return $toencode;
}
sub css_class_quote {
my ($toencode) = (@_);
$toencode =~ s/ /_/g;
$toencode =~ s/([^a-zA-Z0-9_\-.])/uc sprintf("&#x%x;",ord($1))/eg;
return $toencode;
}
sub value_quote {
my ($var) = (@_);
$var =~ s/\&/\&amp;/g;
$var =~ s/</\&lt;/g;
$var =~ s/>/\&gt;/g;
$var =~ s/\"/\&quot;/g;
# See bug http://bugzilla.mozilla.org/show_bug.cgi?id=4928 for
# explanation of why Bugzilla does this linebreak substitution.
# This caused form submission problems in mozilla (bug 22983, 32000).
$var =~ s/\r\n/\&#013;/g;
$var =~ s/\n\r/\&#013;/g;
$var =~ s/\r/\&#013;/g;
$var =~ s/\n/\&#013;/g;
return $var;
}
sub xml_quote {
my ($var) = (@_);
$var =~ s/\&/\&amp;/g;
$var =~ s/</\&lt;/g;
$var =~ s/>/\&gt;/g;
$var =~ s/\"/\&quot;/g;
$var =~ s/\'/\&apos;/g;
return $var;
}
sub url_decode {
my ($todecode) = (@_);
$todecode =~ tr/+/ /; # pluses become spaces
$todecode =~ s/%([0-9a-fA-F]{2})/pack("c",hex($1))/ge;
return $todecode;
}
sub i_am_cgi {
# I use SERVER_SOFTWARE because it's required to be
# defined for all requests in the CGI spec.
return exists $ENV{'SERVER_SOFTWARE'} ? 1 : 0;
}
sub correct_urlbase {
my $ssl = Bugzilla->params->{'ssl'};
return Bugzilla->params->{'urlbase'} if $ssl eq 'never';
my $sslbase = Bugzilla->params->{'sslbase'};
if ($sslbase) {
return $sslbase if $ssl eq 'always';
# Authenticated Sessions
return $sslbase if Bugzilla->user->id;
}
# Set to "authenticated sessions" but nobody's logged in, or
# sslbase isn't set.
return Bugzilla->params->{'urlbase'};
}
sub lsearch {
my ($list,$item) = (@_);
my $count = 0;
foreach my $i (@$list) {
if ($i eq $item) {
return $count;
}
$count++;
}
return -1;
}
sub diff_arrays {
my ($old_ref, $new_ref) = @_;
my @old = @$old_ref;
my @new = @$new_ref;
# For each pair of (old, new) entries:
# If they're equal, set them to empty. When done, @old contains entries
# that were removed; @new contains ones that got added.
foreach my $oldv (@old) {
foreach my $newv (@new) {
next if ($newv eq '');
if ($oldv eq $newv) {
$newv = $oldv = '';
}
}
}
my @removed = grep { $_ ne '' } @old;
my @added = grep { $_ ne '' } @new;
return (\@removed, \@added);
}
sub trim {
my ($str) = @_;
if ($str) {
$str =~ s/^\s+//g;
$str =~ s/\s+$//g;
}
return $str;
}
sub diff_strings {
my ($oldstr, $newstr) = @_;
# Split the old and new strings into arrays containing their values.
$oldstr =~ s/[\s,]+/ /g;
$newstr =~ s/[\s,]+/ /g;
my @old = split(" ", $oldstr);
my @new = split(" ", $newstr);
my ($rem, $add) = diff_arrays(\@old, \@new);
my $removed = join (", ", @$rem);
my $added = join (", ", @$add);
return ($removed, $added);
}
sub wrap_comment {
my ($comment) = @_;
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 perform_substs {
my ($str, $substs) = (@_);
$str =~ s/%([a-z]*)%/(defined $substs->{$1} ? $substs->{$1} : Bugzilla->params->{$1})/eg;
return $str;
}
sub format_time {
my ($date, $format) = @_;
# If $format is undefined, try to guess the correct date format.
my $show_timezone;
if (!defined($format)) {
if ($date =~ m/^(\d{4})[-\.](\d{2})[-\.](\d{2}) (\d{2}):(\d{2})(:(\d{2}))?$/) {
my $sec = $7;
if (defined $sec) {
$format = "%Y-%m-%d %T";
} else {
$format = "%Y-%m-%d %R";
}
} else {
# Default date format. See Date::Format for other formats available.
$format = "%Y-%m-%d %R";
}
# By default, we want the timezone to be displayed.
$show_timezone = 1;
}
else {
# Search for %Z or %z, meaning we want the timezone to be displayed.
# Till bug 182238 gets fixed, we assume Bugzilla->params->{'timezone'}
# is used.
$show_timezone = ($format =~ s/\s?%Z$//i);
}
# str2time($date) is undefined if $date has an invalid date format.
my $time = str2time($date);
if (defined $time) {
$date = time2str($format, $time);
$date .= " " . Bugzilla->params->{'timezone'} if $show_timezone;
}
else {
# Don't let invalid (time) strings to be passed to templates!
$date = '';
}
return trim($date);
}
sub format_time_decimal {
my ($time) = (@_);
my $newtime = sprintf("%.2f", $time);
if ($newtime =~ /0\Z/) {
$newtime = sprintf("%.1f", $time);
}
return $newtime;
}
sub file_mod_time {
my ($filename) = (@_);
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks)
= stat($filename);
return $mtime;
}
sub bz_crypt {
my ($password) = @_;
# The list of characters that can appear in a salt. Salts and hashes
# are both encoded as a sequence of characters from a set containing
# 64 characters, each one of which represents 6 bits of the salt/hash.
# The encoding is similar to BASE64, the difference being that the
# BASE64 plus sign (+) is replaced with a forward slash (/).
my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/');
# Generate the salt. We use an 8 character (48 bit) salt for maximum
# security on systems whose crypt uses MD5. Systems with older
# versions of crypt will just use the first two characters of the salt.
my $salt = '';
for ( my $i=0 ; $i < 8 ; ++$i ) {
$salt .= $saltchars[rand(64)];
}
# Crypt the password.
my $cryptedpassword = crypt($password, $salt);
# Return the crypted password.
return $cryptedpassword;
}
sub generate_random_password {
my $size = shift || 10; # default to 10 chars if nothing specified
return join("", map{ ('0'..'9','a'..'z','A'..'Z')[rand 62] } (1..$size));
}
sub validate_email_syntax {
my ($addr) = @_;
my $match = Bugzilla->params->{'emailregexp'};
my $ret = ($addr =~ /$match/ && $addr !~ /[\\\(\)<>&,;:"\[\] \t\r\n]/);
if ($ret) {
# We assume these checks to suffice to consider the address untainted.
trick_taint($_[0]);
}
return $ret ? 1 : 0;
}
sub validate_date {
my ($date) = @_;
my $date2;
# $ts is undefined if the parser fails.
my $ts = str2time($date);
if ($ts) {
$date2 = time2str("%Y-%m-%d", $ts);
$date =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
$date2 =~ s/(\d+)-0*(\d+?)-0*(\d+?)/$1-$2-$3/;
}
my $ret = ($ts && $date eq $date2);
return $ret ? 1 : 0;
}
sub is_7bit_clean {
return $_[0] !~ /[^\x20-\x7E\x0A\x0D]/;
}
sub clean_text {
my ($dtext) = shift;
$dtext =~ s/[\x00-\x1F\x7F]+/ /g; # change control characters into a space
return trim($dtext);
}
sub get_text {
my ($name, $vars) = @_;
my $template = Bugzilla->template_inner;
$vars ||= {};
$vars->{'message'} = $name;
my $message;
$template->process('global/message.txt.tmpl', $vars, \$message)
|| ThrowTemplateError($template->error());
# Remove the indenting that exists in messages.html.tmpl.
$message =~ s/^ //gm;
return $message;
}
sub get_netaddr {
my $ipaddr = shift;
# Check for a valid IPv4 addr which we know how to parse
if (!$ipaddr || $ipaddr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) {
return undef;
}
my $addr = unpack("N", pack("CCCC", split(/\./, $ipaddr)));
my $maskbits = Bugzilla->params->{'loginnetmask'};
# Make Bugzilla ignore the IP address if loginnetmask is set to 0
return "0.0.0.0" if ($maskbits == 0);
$addr >>= (32-$maskbits);
$addr <<= (32-$maskbits);
return join(".", unpack("CCCC", pack("N", $addr)));
}
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 decoding
$rv = url_decode($var);
# Functions that tell you about your environment
my $is_cgi = i_am_cgi();
my $net_addr = get_netaddr($ip_addr);
my $urlbase = correct_urlbase();
# Functions for searching
$loc = lsearch(\@arr, $val);
# Data manipulation
($removed, $added) = diff_arrays(\@old, \@new);
# Functions for manipulating strings
$val = trim(" abc ");
($removed, $added) = diff_strings($old, $new);
$wrapped = wrap_comment($comment);
$msg = perform_substs($str, $substs);
# Functions for formatting time
format_time($time);
# Functions for dealing with files
$time = file_mod_time($filename);
# Cryptographic Functions
$crypted_password = bz_crypt($password);
$new_password = generate_random_password($password_length);
# Validation Functions
validate_email_syntax($email);
validate_date($date);
=head1 DESCRIPTION
This package contains various utility functions which do not belong anywhere
else.
B<It is not intended as a general dumping group for something which
people feel might be useful somewhere, someday>. Do not add methods to this
package unless it is intended to be used for a significant number of files,
and it does not belong anywhere else.
=head1 FUNCTIONS
This package provides several types of routines:
=head2 Tainting
Several functions are available to deal with tainted variables. B<Use these
with care> to avoid security holes.
=over 4
=item C<is_tainted>
Determines whether a particular variable is tainted
=item C<trick_taint($val)>
Tricks perl into untainting a particular variable.
Use trick_taint() when you know that there is no way that the data
in a scalar can be tainted, but taint mode still bails on it.
B<WARNING!! Using this routine on data that really could be tainted defeats
the purpose of taint mode. It should only be used on variables that have been
sanity checked in some way and have been determined to be OK.>
=item C<detaint_natural($num)>
This routine detaints a natural number. It returns a true value if the
value passed in was a valid natural number, else it returns false. You
B<MUST> check the result of this routine to avoid security holes.
=item C<detaint_signed($num)>
This routine detaints a signed integer. It returns a true value if the
value passed in was a valid signed integer, else it returns false. You
B<MUST> check the result of this routine to avoid security holes.
=back
=head2 Quoting
Some values may need to be quoted from perl. However, this should in general
be done in the template where possible.
=over 4
=item C<html_quote($val)>
Returns a value quoted for use in HTML, with &, E<lt>, E<gt>, and E<34> being
replaced with their appropriate HTML entities.
=item C<html_light_quote($val)>
Returns a string where only explicitly allowed HTML elements and attributes
are kept. All HTML elements and attributes not being in the whitelist are either
escaped (if HTML::Scrubber is not installed) or removed.
=item C<url_quote($val)>
Quotes characters so that they may be included as part of a url.
=item C<css_class_quote($val)>
Quotes characters so that they may be used as CSS class names. Spaces
are replaced by underscores.
=item C<value_quote($val)>
As well as escaping html like C<html_quote>, this routine converts newlines
into &#013;, suitable for use in html attributes.
=item C<xml_quote($val)>
This is similar to C<html_quote>, except that ' is escaped to &apos;. This
is kept separate from html_quote partly for compatibility with previous code
(for &apos;) and partly for future handling of non-ASCII characters.
=item C<url_decode($val)>
Converts the %xx encoding from the given URL back to its original form.
=back
=head2 Environment and Location
Functions returning information about your environment or location.
=over 4
=item C<i_am_cgi()>
Tells you whether or not you are being run as a CGI script in a web
server. For example, it would return false if the caller is running
in a command-line script.
=item C<get_netaddr($ipaddr)>
Given an IP address, this returns the associated network address, using
C<Bugzilla->params->{'loginnetmask'}> as the netmask. This can be used
to obtain data in order to restrict weak authentication methods (such as
cookies) to only some addresses.
=item C<correct_urlbase()>
Returns either the C<sslbase> or C<urlbase> parameter, depending on the
current setting for the C<ssl> parameter.
=back
=head2 Searching
Functions for searching within a set of values.
=over 4
=item C<lsearch($list, $item)>
Returns the position of C<$item> in C<$list>. C<$list> must be a list
reference.
If the item is not in the list, returns -1.
=back
=head2 Data Manipulation
=over 4
=item C<diff_arrays(\@old, \@new)>
Description: Takes two arrayrefs, and will tell you what it takes to
get from @old to @new.
Params: @old = array that you are changing from
@new = array that you are changing to
Returns: A list of two arrayrefs. The first is a reference to an
array containing items that were removed from @old. The
second is a reference to an array containing items
that were added to @old. If both returned arrays are
empty, @old and @new contain the same values.
=back
=head2 String Manipulation
=over 4
=item C<trim($str)>
Removes any leading or trailing whitespace from a string. This routine does not
modify the existing string.
=item C<diff_strings($oldstr, $newstr)>
Takes two strings containing a list of comma- or space-separated items
and returns what items were removed from or added to the new one,
compared to the old one. Returns a list, where the first entry is a scalar
containing removed items, and the second entry is a scalar containing added
items.
=item C<wrap_comment($comment)>
Takes a bug comment, and wraps it to the appropriate length. The length is
currently specified in C<Bugzilla::Constants::COMMENT_COLS>. Lines beginning
with ">" are assumed to be quotes, and they will not be wrapped.
The intended use of this function is to wrap comments that are about to be
displayed or emailed. Generally, wrapped text should not be stored in the
database.
=item C<find_wrap_point($string, $maxpos)>
Search for a comma, a whitespace or a hyphen to split $string, within the first
$maxpos characters. If none of them is found, just split $string at $maxpos.
The search starts at $maxpos and goes back to the beginning of the string.
=item C<perform_substs($str, $substs)>
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.
=item C<is_7bit_clean($str)>
Returns true is the string contains only 7-bit characters (ASCII 32 through 126,
ASCII 10 (LineFeed) and ASCII 13 (Carrage Return).
=item C<clean_text($str)>
Returns the parameter "cleaned" by exchanging non-printable characters with spaces.
Specifically characters (ASCII 0 through 31) and (ASCII 127) will become ASCII 32 (Space).
=item C<get_text>
=over
=item B<Description>
This is a method of getting localized strings within Bugzilla code.
Use this when you don't want to display a whole template, you just
want a particular string.
It uses the F<global/message.txt.tmpl> template to return a string.
=item B<Params>
=over
=item C<$message> - The identifier for the message.
=item C<$vars> - A hashref. Any variables you want to pass to the template.
=back
=item B<Returns>
A string.
=back
=back
=head2 Formatting Time
=over 4
=item C<format_time($time)>
Takes a time, converts it to the desired format and appends the timezone
as defined in editparams.cgi, if desired. This routine will be expanded
in the future to adjust for user preferences regarding what timezone to
display times in.
This routine is mainly called from templates to filter dates, see
"FILTER time" in Templates.pm. In this case, $format is undefined and
the routine has to "guess" the date format that was passed to $dbh->sql_date_format().
=item C<format_time_decimal($time)>
Returns a number with 2 digit precision, unless the last digit is a 0. Then it
returns only 1 digit precision.
=back
=head2 Files
=over 4
=item C<file_mod_time($filename)>
Takes a filename and returns the modification time. It returns it in the format
of the "mtime" parameter of the perl "stat" function.
=back
=head2 Cryptography
=over 4
=item C<bz_crypt($password)>
Takes a string and returns a C<crypt>ed value for it, using a random salt.
Please always use this function instead of the built-in perl "crypt"
when initially encrypting a password.
=begin undocumented
Random salts are generated because the alternative is usually
to use the first two characters of the password itself, and since
the salt appears in plaintext at the beginning of the encrypted
password string this has the effect of revealing the first two
characters of the password to anyone who views the encrypted version.
=end undocumented
=item C<generate_random_password($password_length)>
Returns an alphanumeric string with the specified length
(10 characters by default). Use this function to generate passwords
and tokens.
=back
=head2 Validation
=over 4
=item C<validate_email_syntax($email)>
Do a syntax checking for a legal email address and returns 1 if
the check is successful, else returns 0.
Untaints C<$email> if successful.
=item C<validate_date($date)>
Make sure the date has the correct format and returns 1 if
the check is successful, else returns 0.
=back

View File

@@ -1,289 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Tiago R. Mello <timello@async.com.br>
# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
package Bugzilla::Version;
use base qw(Bugzilla::Object);
use Bugzilla::Install::Requirements qw(vers_cmp);
use Bugzilla::Util;
use Bugzilla::Error;
################################
##### Initialization #####
################################
use constant DEFAULT_VERSION => 'unspecified';
use constant DB_TABLE => 'versions';
use constant DB_COLUMNS => qw(
id
value
product_id
);
use constant NAME_FIELD => 'value';
# This is "id" because it has to be filled in and id is probably the fastest.
# We do a custom sort in new_from_list below.
use constant LIST_ORDER => 'id';
sub new {
my $class = shift;
my $param = shift;
my $dbh = Bugzilla->dbh;
my $product;
if (ref $param) {
$product = $param->{product};
my $name = $param->{name};
if (!defined $product) {
ThrowCodeError('bad_arg',
{argument => 'product',
function => "${class}::new"});
}
if (!defined $name) {
ThrowCodeError('bad_arg',
{argument => 'name',
function => "${class}::new"});
}
my $condition = 'product_id = ? AND value = ?';
my @values = ($product->id, $name);
$param = { condition => $condition, values => \@values };
}
unshift @_, $param;
return $class->SUPER::new(@_);
}
sub new_from_list {
my $self = shift;
my $list = $self->SUPER::new_from_list(@_);
return [sort { vers_cmp(lc($a->name), lc($b->name)) } @$list];
}
sub bug_count {
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'bug_count'}) {
$self->{'bug_count'} = $dbh->selectrow_array(qq{
SELECT COUNT(*) FROM bugs
WHERE product_id = ? AND version = ?}, undef,
($self->product_id, $self->name)) || 0;
}
return $self->{'bug_count'};
}
sub remove_from_db {
my $self = shift;
my $dbh = Bugzilla->dbh;
# The version cannot be removed if there are bugs
# associated with it.
if ($self->bug_count) {
ThrowUserError("version_has_bugs", { nb => $self->bug_count });
}
$dbh->do(q{DELETE FROM versions WHERE product_id = ? AND value = ?},
undef, ($self->product_id, $self->name));
}
sub update {
my $self = shift;
my ($name, $product) = @_;
my $dbh = Bugzilla->dbh;
$name || ThrowUserError('version_not_specified');
# Remove unprintable characters
$name = clean_text($name);
return 0 if ($name eq $self->name);
my $version = new Bugzilla::Version({ product => $product, name => $name });
if ($version) {
ThrowUserError('version_already_exists',
{'name' => $version->name,
'product' => $product->name});
}
trick_taint($name);
$dbh->do("UPDATE bugs SET version = ?
WHERE version = ? AND product_id = ?", undef,
($name, $self->name, $self->product_id));
$dbh->do("UPDATE versions SET value = ?
WHERE product_id = ? AND value = ?", undef,
($name, $self->product_id, $self->name));
$self->{'value'} = $name;
return 1;
}
###############################
##### Accessors ####
###############################
sub name { return $_[0]->{'value'}; }
sub product_id { return $_[0]->{'product_id'}; }
###############################
##### Subroutines ###
###############################
sub check_version {
my ($product, $version_name) = @_;
$version_name || ThrowUserError('version_not_specified');
my $version = new Bugzilla::Version(
{ product => $product, name => $version_name });
unless ($version) {
ThrowUserError('version_not_valid',
{'product' => $product->name,
'version' => $version_name});
}
return $version;
}
sub create {
my ($name, $product) = @_;
my $dbh = Bugzilla->dbh;
# Cleanups and validity checks
$name || ThrowUserError('version_blank_name');
# Remove unprintable characters
$name = clean_text($name);
my $version = new Bugzilla::Version({ product => $product, name => $name });
if ($version) {
ThrowUserError('version_already_exists',
{'name' => $version->name,
'product' => $product->name});
}
# Add the new version
trick_taint($name);
$dbh->do(q{INSERT INTO versions (value, product_id)
VALUES (?, ?)}, undef, ($name, $product->id));
return new Bugzilla::Version($dbh->bz_last_key('versions', 'id'));
}
1;
__END__
=head1 NAME
Bugzilla::Version - Bugzilla product version class.
=head1 SYNOPSIS
use Bugzilla::Version;
my $version = new Bugzilla::Version(1, 'version_value');
my $product_id = $version->product_id;
my $value = $version->value;
$version->remove_from_db;
my $updated = $version->update($version_name, $product);
my $version = $hash_ref->{'version_value'};
my $version = Bugzilla::Version::check_version($product_obj,
'acme_version');
my $version = Bugzilla::Version::create($version_name, $product);
=head1 DESCRIPTION
Version.pm represents a Product Version object.
=head1 METHODS
=over
=item C<new($product_id, $value)>
Description: The constructor is used to load an existing version
by passing a product id and a version value.
Params: $product_id - Integer with a product id.
$value - String with a version value.
Returns: A Bugzilla::Version object.
=item C<bug_count()>
Description: Returns the total of bugs that belong to the version.
Params: none.
Returns: Integer with the number of bugs.
=item C<remove_from_db()>
Description: Removes the version from the database.
Params: none.
Retruns: none.
=item C<update($name, $product)>
Description: Update the value of the version.
Params: $name - String with the new version value.
$product - Bugzilla::Product object the version belongs to.
Returns: An integer - 1 if the version has been updated, else 0.
=back
=head1 SUBROUTINES
=over
=item C<check_version($product, $version_name)>
Description: Checks if the version name exists for the product name.
Params: $product - A Bugzilla::Product object.
$version_name - String with a version name.
Returns: Bugzilla::Version object.
=item C<create($version_name, $product)>
Description: Create a new version for the given product.
Params: $version_name - String with a version value.
$product - A Bugzilla::Product object.
Returns: A Bugzilla::Version object.
=back
=cut

View File

@@ -1,134 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::WebService;
use strict;
use Bugzilla::WebService::Constants;
use Date::Parse;
sub fail_unimplemented {
my $this = shift;
die SOAP::Fault
->faultcode(ERROR_UNIMPLEMENTED)
->faultstring('Service Unimplemented');
}
sub datetime_format {
my ($self, $date_string) = @_;
my $time = str2time($date_string);
my ($sec, $min, $hour, $mday, $mon, $year) = localtime $time;
# This format string was stolen from SOAP::Utils->format_datetime,
# which doesn't work but which has almost the right format string.
my $iso_datetime = sprintf('%d%02d%02dT%02d:%02d:%02d',
$year + 1900, $mon + 1, $mday, $hour, $min, $sec);
return $iso_datetime;
}
sub handle_login {
my ($self, $module, $method) = @_;
my $exempt = LOGIN_EXEMPT->{$module};
return if $exempt && grep { $_ eq $method } @$exempt;
Bugzilla->login;
}
package Bugzilla::WebService::XMLRPC::Transport::HTTP::CGI;
use strict;
eval 'use base qw(XMLRPC::Transport::HTTP::CGI)';
sub make_response {
my $self = shift;
$self->SUPER::make_response(@_);
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
# its cookies in Bugzilla::CGI, so we need to copy them over.
foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
$self->response->headers->push_header('Set-Cookie', $_);
}
}
1;
__END__
=head1 NAME
Bugzilla::WebService - The Web Service interface to Bugzilla
=head1 DESCRIPTION
This is the standard API for external programs that want to interact
with Bugzilla. It provides various methods in various modules.
=head1 STABLE, EXPERIMENTAL, and UNSTABLE
Methods are marked B<STABLE> if you can expect their parameters and
return values not to change between versions of Bugzilla. You are
best off always using methods marked B<STABLE>. We may add parameters
and additional items to the return values, but your old code will
always continue to work with any new changes we make. If we ever break
a B<STABLE> interface, we'll post a big notice in the Release Notes,
and it will only happen during a major new release.
Methods (or parts of methods) are marked B<EXPERIMENTAL> if
we I<believe> they will be stable, but there's a slight chance that
small parts will change in the future.
Certain parts of a method's description may be marked as B<UNSTABLE>,
in which case those parts are not guaranteed to stay the same between
Bugzilla versions.
=head1 ERRORS
If a particular webservice call fails, it will throw a standard XML-RPC
error. There will be a numeric error code, and then the description
field will contain descriptive text of the error. Each error that Bugzilla
can throw has a specific code that will not change between versions of
Bugzilla.
The various errors that functions can throw are specified by the
documentation of those functions.
If your code needs to know what error Bugzilla threw, use the numeric
code. Don't try to parse the description, because that may change
from version to version of Bugzilla.
Note that if you display the error to the user in an HTML program, make
sure that you properly escape the error, as it will not be HTML-escaped.
=head2 Transient vs. Fatal Errors
If the error code is a number greater than 0, the error is considered
"transient," which means that it was an error made by the user, not
some problem with Bugzilla itself.
If the error code is a number less than 0, the error is "fatal," which
means that it's some error in Bugzilla itself that probably requires
administrative attention.
Negative numbers and positive numbers don't overlap. That is, if there's
an error 302, there won't be an error -302.
=head2 Unknown Errors
Sometimes a function will throw an error that doesn't have a specific
error code. In this case, the code will be C<-32000> if it's a "fatal"
error, and C<32000> if it's a "transient" error.

View File

@@ -1,472 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::WebService::Bug;
use strict;
use base qw(Bugzilla::WebService);
import SOAP::Data qw(type);
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Field;
use Bugzilla::WebService::Constants;
use Bugzilla::Util qw(detaint_natural);
use Bugzilla::Bug;
use Bugzilla::BugMail;
use Bugzilla::Constants;
#############
# Constants #
#############
# This maps the names of internal Bugzilla bug fields to things that would
# make sense to somebody who's not intimately familiar with the inner workings
# of Bugzilla. (These are the field names that the WebService uses.)
use constant FIELD_MAP => {
status => 'bug_status',
severity => 'bug_severity',
description => 'comment',
summary => 'short_desc',
platform => 'rep_platform',
};
use constant GLOBAL_SELECT_FIELDS => qw(
bug_severity
bug_status
op_sys
priority
rep_platform
resolution
);
use constant PRODUCT_SPECIFIC_FIELDS => qw(version target_milestone component);
###########
# Methods #
###########
sub get_bugs {
my ($self, $params) = @_;
my $ids = $params->{ids};
defined $ids || ThrowCodeError('param_required', { param => 'ids' });
my @return;
foreach my $bug_id (@$ids) {
ValidateBugID($bug_id);
my $bug = new Bugzilla::Bug($bug_id);
# Timetracking fields are deleted if the user doesn't belong to
# the corresponding group.
unless (Bugzilla->user->in_group(Bugzilla->params->{'timetrackinggroup'})) {
delete $bug->{'estimated_time'};
delete $bug->{'remaining_time'};
delete $bug->{'deadline'};
}
# This is done in this fashion in order to produce a stable API.
# The internals of Bugzilla::Bug are not stable enough to just
# return them directly.
my $creation_ts = $self->datetime_format($bug->creation_ts);
my $delta_ts = $self->datetime_format($bug->delta_ts);
my %item;
$item{'creation_time'} = type('dateTime')->value($creation_ts);
$item{'last_change_time'} = type('dateTime')->value($delta_ts);
$item{'internals'} = $bug;
$item{'id'} = type('int')->value($bug->bug_id);
$item{'summary'} = type('string')->value($bug->short_desc);
if (Bugzilla->params->{'usebugaliases'}) {
$item{'alias'} = type('string')->value($bug->alias);
}
else {
# For API reasons, we always want the value to appear, we just
# don't want it to have a value if aliases are turned off.
$item{'alias'} = undef;
}
push(@return, \%item);
}
return { bugs => \@return };
}
sub create {
my ($self, $params) = @_;
Bugzilla->login(LOGIN_REQUIRED);
my %field_values;
foreach my $field (keys %$params) {
my $field_name = FIELD_MAP->{$field} || $field;
$field_values{$field_name} = $params->{$field};
}
# WebService users can't set the creation date of a bug.
delete $field_values{'creation_ts'};
my $bug = Bugzilla::Bug->create(\%field_values);
Bugzilla::BugMail::Send($bug->bug_id, { changer => $bug->reporter->login });
return { id => type('int')->value($bug->bug_id) };
}
sub legal_values {
my ($self, $params) = @_;
my $field = FIELD_MAP->{$params->{field}} || $params->{field};
my @custom_select =
Bugzilla->get_fields({ type => FIELD_TYPE_SINGLE_SELECT });
# We only want field names.
@custom_select = map {$_->name} @custom_select;
my $values;
if (grep($_ eq $field, GLOBAL_SELECT_FIELDS, @custom_select)) {
$values = get_legal_field_values($field);
}
elsif (grep($_ eq $field, PRODUCT_SPECIFIC_FIELDS)) {
my $id = $params->{product_id};
defined $id || ThrowCodeError('param_required',
{ function => 'Bug.legal_values', param => 'product_id' });
grep($_->id eq $id, @{Bugzilla->user->get_accessible_products})
|| ThrowUserError('product_access_denied', { product => $id });
my $product = new Bugzilla::Product($id);
my @objects;
if ($field eq 'version') {
@objects = @{$product->versions};
}
elsif ($field eq 'target_milestone') {
@objects = @{$product->milestones};
}
elsif ($field eq 'component') {
@objects = @{$product->components};
}
$values = [map { $_->name } @objects];
}
else {
ThrowCodeError('invalid_field_name', { field => $params->{field} });
}
my @result;
foreach my $val (@$values) {
push(@result, type('string')->value($val));
}
return { values => \@result };
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Bug - The API for creating, changing, and getting the
details of bugs.
=head1 DESCRIPTION
This part of the Bugzilla API allows you to file a new bug in Bugzilla,
or get information about bugs that have already been filed.
=head1 METHODS
See L<Bugzilla::WebService> for a description of B<STABLE>, B<UNSTABLE>,
and B<EXPERIMENTAL>.
=head2 Utility Functions
=over
=item C<legal_values> B<EXPERIMENTAL>
=over
=item B<Description>
Tells you what values are allowed for a particular field.
=item B<Params>
=over
=item C<field> - The name of the field you want information about.
This should be the same as the name you would use in L</create>, below.
=item C<product_id> - If you're picking a product-specific field, you have
to specify the id of the product you want the values for.
=back
=item B<Returns>
C<values> - An array of strings: the legal values for this field.
The values will be sorted as they normally would be in Bugzilla.
=item B<Errors>
=over
=item 106 (Invalid Product)
You were required to specify a product, and either you didn't, or you
specified an invalid product (or a product that you can't access).
=item 108 (Invalid Field Name)
You specified a field that doesn't exist or isn't a drop-down field.
=back
=back
=back
=head2 Bug Creation and Modification
=over
=item C<get_bugs> B<EXPERIMENTAL>
=over
=item B<Description>
Gets information about particular bugs in the database.
=item B<Params>
=over
=item C<ids>
An array of numbers and strings.
If an element in the array is entirely numeric, it represents a bug_id
from the Bugzilla database to fetch. If it contains any non-numeric
characters, it is considered to be a bug alias instead, and the bug with
that alias will be loaded.
Note that it's possible for aliases to be disabled in Bugzilla, in which
case you will be told that you have specified an invalid bug_id if you
try to specify an alias. (It will be error 100.)
=back
=item B<Returns>
A hash containing a single element, C<bugs>. This is an array of hashes.
Each hash contains the following items:
=over
=item id
C<int> The numeric bug_id of this bug.
=item alias
C<string> The alias of this bug. If there is no alias or aliases are
disabled in this Bugzilla, this will be an empty string.
=item summary
C<string> The summary of this bug.
=item creation_time
C<dateTime> When the bug was created.
=item last_change_time
C<dateTime> When the bug was last changed.
=item internals B<UNSTABLE>
A hash. The internals of a L<Bugzilla::Bug> object. This is extremely
unstable, and you should only rely on this if you absolutely have to. The
structure of the hash may even change between point releases of Bugzilla.
=back
=item B<Errors>
=over
=item 100 (Invalid Bug Alias)
If you specified an alias and either: (a) the Bugzilla you're querying
doesn't support aliases or (b) there is no bug with that alias.
=item 101 (Invalid Bug ID)
The bug_id you specified doesn't exist in the database.
=item 102 (Access Denied)
You do not have access to the bug_id you specified.
=back
=back
=item C<create> B<EXPERIMENTAL>
=over
=item B<Description>
This allows you to create a new bug in Bugzilla. If you specify any
invalid fields, they will be ignored. If you specify any fields you
are not allowed to set, they will just be set to their defaults or ignored.
You cannot currently set all the items here that you can set on enter_bug.cgi.
The WebService interface may allow you to set things other than those listed
here, but realize that anything undocumented is B<UNSTABLE> and will very
likely change in the future.
=item B<Params>
Some params must be set, or an error will be thrown. These params are
marked B<Required>.
Some parameters can have defaults set in Bugzilla, by the administrator.
If these parameters have defaults set, you can omit them. These parameters
are marked B<Defaulted>.
Clients that want to be able to interact uniformly with multiple
Bugzillas should always set both the params marked B<Required> and those
marked B<Defaulted>, because some Bugzillas may not have defaults set
for B<Defaulted> parameters, and then this method will throw an error
if you don't specify them.
The descriptions of the parameters below are what they mean when Bugzilla is
being used to track software bugs. They may have other meanings in some
installations.
=over
=item C<product> (string) B<Required> - The name of the product the bug
is being filed against.
=item C<component> (string) B<Required> - The name of a component in the
product above.
=item C<summary> (string) B<Required> - A brief description of the bug being
filed.
=item C<version> (string) B<Required> - A version of the product above;
the version the bug was found in.
=item C<description> (string) B<Defaulted> - The initial description for
this bug. Some Bugzilla installations require this to not be blank.
=item C<op_sys> (string) B<Defaulted> - The operating system the bug was
discovered on.
=item C<platform> (string) B<Defaulted> - What type of hardware the bug was
experienced on.
=item C<priority> (string) B<Defaulted> - What order the bug will be fixed
in by the developer, compared to the developer's other bugs.
=item C<severity> (string) B<Defaulted> - How severe the bug is.
=item C<alias> (string) - A brief alias for the bug that can be used
instead of a bug number when accessing this bug. Must be unique in
all of this Bugzilla.
=item C<assigned_to> (username) - A user to assign this bug to, if you
don't want it to be assigned to the component owner.
=item C<cc> (array) - An array of usernames to CC on this bug.
=item C<qa_contact> (username) - If this installation has QA Contacts
enabled, you can set the QA Contact here if you don't want to use
the component's default QA Contact.
=item C<status> (string) - The status that this bug should start out as.
Note that only certain statuses can be set on bug creation.
=item C<target_milestone> (string) - A valid target milestone for this
product.
=back
In addition to the above parameters, if your installation has any custom
fields, you can set them just by passing in the name of the field and
its value as a string.
=item B<Returns>
A hash with one element, C<id>. This is the id of the newly-filed bug.
=item B<Errors>
=over
=item 103 (Invalid Alias)
The alias you specified is invalid for some reason. See the error message
for more details.
=item 104 (Invalid Field)
One of the drop-down fields has an invalid value, or a value entered in a
text field is too long. The error message will have more detail.
=item 105 (Invalid Component)
Either you didn't specify a component, or the component you specified was
invalid.
=item 106 (Invalid Product)
Either you didn't specify a product, this product doesn't exist, or
you don't have permission to enter bugs in this product.
=item 107 (Invalid Summary)
You didn't specify a summary for the bug.
=item 504 (Invalid User)
Either the QA Contact, Assignee, or CC lists have some invalid user
in them. The error message will have more details.
=back
=item B<History>
=over
=item Before B<3.0.4>, parameters marked as B<Defaulted> were actually
B<Required>, due to a bug in Bugzilla.
=back
=back
=back

View File

@@ -1,96 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Mads Bondo Dydensborg <mbd@dbc.dk>
package Bugzilla::WebService::Bugzilla;
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::Constants;
import SOAP::Data qw(type);
use Time::Zone;
sub version {
return { version => type('string')->value(BUGZILLA_VERSION) };
}
sub timezone {
my $offset = tz_offset();
$offset = (($offset / 60) / 60) * 100;
$offset = sprintf('%+05d', $offset);
return { timezone => type('string')->value($offset) };
}
1;
__END__
=head1 NAME
Bugzilla::WebService::Bugzilla - Global functions for the webservice interface.
=head1 DESCRIPTION
This provides functions that tell you about Bugzilla in general.
=head1 METHODS
See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>,
and B<EXPERIMENTAL> mean.
=over
=item C<version> B<EXPERIMENTAL>
=over
=item B<Description>
Returns the current version of Bugzilla.
=item B<Params> (none)
=item B<Returns>
A hash with a single item, C<version>, that is the version as a
string.
=item B<Errors> (none)
=back
=item C<timezone> B<EXPERIMENTAL>
=over
=item B<Description>
Returns the timezone of the server Bugzilla is running on. This is
important because all dates/times that the webservice interface
returns will be in this timezone.
=item B<Params> (none)
=item B<Returns>
A hash with a single item, C<timezone>, that is the timezone as a
string in (+/-)XXXX (RFC 2822) format.
=back
=back

View File

@@ -1,115 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::WebService::Constants;
use strict;
use base qw(Exporter);
@Bugzilla::WebService::Constants::EXPORT = qw(
WS_ERROR_CODE
ERROR_UNKNOWN_FATAL
ERROR_UNKNOWN_TRANSIENT
ERROR_AUTH_NODATA
ERROR_UNIMPLEMENTED
LOGIN_EXEMPT
);
# This maps the error names in global/*-error.html.tmpl to numbers.
# Generally, transient errors should have a number above 0, and
# fatal errors should have a number below 0.
#
# This hash should generally contain any error that could be thrown
# by the WebService interface. If it's extremely unlikely that the
# error could be thrown (like some CodeErrors), it doesn't have to
# be listed here.
#
# "Transient" means "If you resubmit that request with different data,
# it may work."
#
# "Fatal" means, "There's something wrong with Bugzilla, probably
# something an administrator would have to fix."
#
# NOTE: Numbers must never be recycled. If you remove a number, leave a
# comment that it was retired. Also, if an error changes its name, you'll
# have to fix it here.
use constant WS_ERROR_CODE => {
# Bug errors usually occupy the 100-200 range.
invalid_bug_id_or_alias => 100,
invalid_bug_id_non_existent => 101,
bug_access_denied => 102,
bug_access_query => 102,
invalid_field_name => 108,
# These all mean "invalid alias"
alias_not_defined => 103,
alias_too_long => 103,
alias_in_use => 103,
alias_is_numeric => 103,
alias_has_comma_or_space => 103,
# Misc. bug field errors
illegal_field => 104,
freetext_too_long => 104,
# Component errors
require_component => 105,
component_name_too_long => 105,
component_not_valid => 105,
# Invalid Product
no_products => 106,
entry_access_denied => 106,
product_access_denied => 106,
product_disabled => 106,
# Invalid Summary
require_summary => 107,
# Authentication errors are usually 300-400.
invalid_username_or_password => 300,
account_disabled => 301,
auth_invalid_email => 302,
extern_id_conflict => -303,
# User errors are 500-600.
account_exists => 500,
illegal_email_address => 501,
account_creation_disabled => 501,
password_too_short => 502,
password_too_long => 503,
invalid_username => 504,
# This is from strict_isolation, but it also basically means
# "invalid user."
invalid_user_group => 504,
};
# These are the fallback defaults for errors not in ERROR_CODE.
use constant ERROR_UNKNOWN_FATAL => -32000;
use constant ERROR_UNKNOWN_TRANSIENT => 32000;
use constant ERROR_AUTH_NODATA => 410;
use constant ERROR_UNIMPLEMENTED => 910;
use constant ERROR_GENERAL => 999;
# For some methods, we shouldn't call Bugzilla->login before we call them.
# This is a hash--package names pointing to an arrayref of method names.
use constant LOGIN_EXEMPT => {
# Callers may have to know the Bugzilla version before logging in,
# even on a requirelogin installation.
Bugzilla => ['version', 'timezone'],
User => ['offer_account_by_email', 'login'],
};
1;

View File

@@ -1,182 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Mads Bondo Dydensborg <mbd@dbc.dk>
package Bugzilla::WebService::Product;
use strict;
use base qw(Bugzilla::WebService);
use Bugzilla::Product;
use Bugzilla::User;
import SOAP::Data qw(type);
# Get the ids of the products the user can search
sub get_selectable_products {
return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]};
}
# Get the ids of the products the user can enter bugs against
sub get_enterable_products {
return {ids => [map {$_->id} @{Bugzilla->user->get_enterable_products}]};
}
# Get the union of the products the user can search and enter bugs against.
sub get_accessible_products {
return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
}
# Get a list of actual products, based on list of ids
sub get_products {
my ($self, $params) = @_;
# Only products that are in the users accessible products,
# can be allowed to be returned
my $accessible_products = Bugzilla->user->get_accessible_products;
# Create a hash with the ids the user wants
my %ids = map { $_ => 1 } @{$params->{ids}};
# Return the intersection of this, by grepping the ids from
# accessible products.
my @requested_accessible = grep { $ids{$_->id} } @$accessible_products;
# Now create a result entry for each.
my @products =
map {{
internals => $_,
id => type('int')->value($_->id),
name => type('string')->value($_->name),
description => type('string')->value($_->description),
}
} @requested_accessible;
return { products => \@products };
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::Product - The Product API
=head1 DESCRIPTION
This part of the Bugzilla API allows you to list the available Products and
get information about them.
=head1 METHODS
See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>,
and B<EXPERIMENTAL> mean, and for more information about error codes.
=head2 List Products
=over
=item C<get_selectable_products> B<UNSTABLE>
=over
=item B<Description>
Returns a list of the ids of the products the user can search on.
=item B<Params> (none)
=item B<Returns>
A hash containing one item, C<ids>, that contains an array of product
ids.
=item B<Errors> (none)
=back
=item C<get_enterable_products> B<UNSTABLE>
=over
=item B<Description>
Returns a list of the ids of the products the user can enter bugs
against.
=item B<Params> (none)
=item B<Returns>
A hash containing one item, C<ids>, that contains an array of product
ids.
=item B<Errors> (none)
=back
=item C<get_accessible_products> B<UNSTABLE>
=over
=item B<Description>
Returns a list of the ids of the products the user can search or enter
bugs against.
=item B<Params> (none)
=item B<Returns>
A hash containing one item, C<ids>, that contains an array of product
ids.
=item B<Errors> (none)
=back
=item C<get_products> B<UNSTABLE>
=over
=item B<Description>
Returns a list of information about the products passed to it.
=item B<Params>
A hash containing one item, C<ids>, that is an array of product ids.
=item B<Returns>
A hash containing one item, C<products>, that is an array of
hashes. Each hash describes a product, and has the following items:
C<id>, C<name>, C<description>, and C<internals>. The C<id> item is
the id of the product. The C<name> item is the name of the
product. The C<description> is the description of the
product. Finally, the C<internals> is an internal representation of
the product.
Note, that if the user tries to access a product that is not in the
list of accessible products for the user, or a product that does not
exist, that is silently ignored, and no information about that product
is returned.
=item B<Errors> (none)
=back
=back

View File

@@ -1,303 +0,0 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Mads Bondo Dydensborg <mbd@dbc.dk>
package Bugzilla::WebService::User;
use strict;
use base qw(Bugzilla::WebService);
import SOAP::Data qw(type);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Util qw(trim);
use Bugzilla::Token;
##############
# User Login #
##############
sub login {
my ($self, $params) = @_;
my $remember = $params->{remember};
# Convert $remember from a boolean 0/1 value to a CGI-compatible one.
if (defined($remember)) {
$remember = $remember? 'on': '';
}
else {
# Use Bugzilla's default if $remember is not supplied.
$remember =
Bugzilla->params->{'rememberlogin'} eq 'defaulton'? 'on': '';
}
# Make sure the CGI user info class works if necessary.
my $cgi = Bugzilla->cgi;
$cgi->param('Bugzilla_login', $params->{login});
$cgi->param('Bugzilla_password', $params->{password});
$cgi->param('Bugzilla_remember', $remember);
Bugzilla->login;
return { id => type('int')->value(Bugzilla->user->id) };
}
sub logout {
my $self = shift;
Bugzilla->logout;
return undef;
}
#################
# User Creation #
#################
sub offer_account_by_email {
my $self = shift;
my ($params) = @_;
my $email = trim($params->{email})
|| ThrowCodeError('param_required', { param => 'email' });
my $createexp = Bugzilla->params->{'createemailregexp'};
if (!$createexp || $email !~ /$createexp/) {
ThrowUserError("account_creation_disabled");
}
$email = Bugzilla::User->check_login_name_for_creation($email);
# Create and send a token for this new account.
Bugzilla::Token::issue_new_user_account_token($email);
return undef;
}
sub create {
my $self = shift;
my ($params) = @_;
Bugzilla->user->in_group('editusers')
|| ThrowUserError("auth_failure", { group => "editusers",
action => "add",
object => "users"});
my $email = trim($params->{email})
|| ThrowCodeError('param_required', { param => 'email' });
my $realname = trim($params->{full_name});
my $password = trim($params->{password}) || '*';
my $user = Bugzilla::User->create({
login_name => $email,
realname => $realname,
cryptpassword => $password
});
return { id => type('int')->value($user->id) };
}
1;
__END__
=head1 NAME
Bugzilla::Webservice::User - The User Account and Login API
=head1 DESCRIPTION
This part of the Bugzilla API allows you to create User Accounts and
log in/out using an existing account.
=head1 METHODS
See L<Bugzilla::WebService> for a description of what B<STABLE>, B<UNSTABLE>,
and B<EXPERIMENTAL> mean, and for more information about error codes.
=head2 Logging In and Out
=over
=item C<login> B<EXPERIMENTAL>
=over
=item B<Description>
Logging in, with a username and password, is required for many
Bugzilla installations, in order to search for bugs, post new bugs,
etc. This method logs in an user.
=item B<Params>
=over
=item C<login> (string) - The user's login name.
=item C<password> (string) - The user's password.
=item C<remember> (bool) B<Optional> - if the cookies returned by the
call to login should expire with the session or not. In order for
this option to have effect the Bugzilla server must be configured to
allow the user to set this option - the Bugzilla parameter
I<rememberlogin> must be set to "defaulton" or
"defaultoff". Addionally, the client application must implement
management of cookies across sessions.
=back
=item B<Returns>
On success, a hash containing one item, C<id>, the numeric id of the
user that was logged in. A set of http cookies is also sent with the
response. These cookies must be sent along with any future requests
to the webservice, for the duration of the session.
=item B<Errors>
=over
=item 300 (Invalid Username or Password)
The username does not exist, or the password is wrong.
=item 301 (Account Disabled)
The account has been disabled. A reason may be specified with the
error.
=back
=back
=item C<logout> B<EXPERIMENTAL>
=over
=item B<Description>
Log out the user. Does nothing if there is no user logged in.
=item B<Params> (none)
=item B<Returns> (nothing)
=item B<Errors> (none)
=back
=back
=head2 Account Creation
=over
=item C<offer_account_by_email> B<EXPERIMENTAL>
=over
=item B<Description>
Sends an email to the user, offering to create an account. The user
will have to click on a URL in the email, and choose their password
and real name.
This is the recommended way to create a Bugzilla account.
=item B<Param>
=over
=item C<email> (string) - the email to send the offer to.
=back
=item B<Returns> (nothing)
=item B<Errors>
=over
=item 500 (Illegal Email Address)
This Bugzilla does not allow you to create accounts with the format of
email address you specified. Account creation may be entirely disabled.
=item 501 (Account Already Exists)
An account with that email address already exists in Bugzilla.
=back
=back
=item C<create> B<EXPERIMENTAL>
=over
=item B<Description>
Creates a user account directly in Bugzilla, password and all.
Instead of this, you should use L</offer_account_by_email> when
possible, because that makes sure that the email address specified can
actually receive an email. This function does not check that.
=item B<Params>
=over
=item C<email> (string) - The email address for the new user.
=item C<full_name> (string) B<Optional> - The user's full name. Will
be set to empty if not specified.
=item C<password> (string) B<Optional> - The password for the new user
account, in plain text. It will be stripped of leading and trailing
whitespace. If blank or not specified, the newly created account will
exist in Bugzilla, but will not be allowed to log in using DB
authentication until a password is set either by the user (through
resetting their password) or by the administrator.
=back
=item B<Returns>
A hash containing one item, C<id>, the numeric id of the user that was
created.
=item B<Errors>
The same as L</offer_account_by_email>. If a password is specified,
the function may also throw:
=over
=item 502 (Password Too Short)
The password specified is too short. (Usually, this means the
password is under three characters.)
=item 503 (Password Too Long)
The password specified is too long. (Usually, this means the
password is over ten characters.)
=back
=back
=back

View File

@@ -1,84 +0,0 @@
Bugzilla Quick Start Guide
==========================
(or, how to get Bugzilla up and running in 10 steps)
Christian Reis <kiko@async.com.br>
This express installation guide is for "normal" Bugzilla installations,
which means a Linux or Unix system on which Apache, Perl, MySQL or PostgreSQL
and a Sendmail compatible MTA are available. For other configurations, please
see Section 4 of the Bugzilla Guide in the docs/ directory.
1. Decide from which URL and directory under your webserver root you
will be serving the Bugzilla webpages.
2. Unpack the distribution into the chosen directory (there is no copying or
installation involved).
3. Run ./checksetup.pl, look for unsolved requirements, and install them.
You can run checksetup as many times as necessary to check if
everything required has been installed.
These will usually include assorted Perl modules, MySQL or PostgreSQL,
and a MTA.
After a successful dependency check, checksetup should complain that
localconfig needs to be edited.
4. Edit the localconfig file, in particular the $webservergroup and
$db_* variables. In particular, $db_name and $db_user will define
your database setup in step 5.
5. Using the name you provided as $db_name above, create a MySQL database
for Bugzilla. You should also create a user permission for the name
supplied as $db_user with read/write access to that database.
If you are not familiar with MySQL permissions, it's a good idea to
use the mysql_setpermission script that is installed with the MySQL
distribution, and be sure to read Bugzilla Security - MySQL section
in the Bugzilla Guide or PostgreSQL documentation.
6. Run checksetup.pl once more; if all goes well, it should set up the
Bugzilla database for you. If not, return to step 5.
checksetup.pl should ask you, this time, for the administrator's
email address and password. These will be used for the initial
Bugzilla administrator account.
7. Configure Apache (or install and configure, if you don't have it up
yet) to point to the Bugzilla directory. You should enable and
activate mod_cgi, and add the configuration entries
Options +ExecCGI
AllowOverride Limit
DirectoryIndex index.cgi
to your Bugzilla <Directory> block. You may also need
AddHandler cgi-script .cgi
if you don't have that in your Apache configuration file yet.
8. Visit the URL you chose for Bugzilla. Your browser should display the
default Bugzilla home page. You should then log in as the
administrator by following the "Log in" link and supplying the
account information you provided in step 6.
9. Scroll to the bottom of the page after logging in, and select
"Parameters". Set up the relevant parameters for your local setup.
See section 4.2 of the Bugzilla Guide for a in-depth description of
some of the configuration parameters available.
10. That's it. If anything unexpected comes up:
- read the error message carefully,
- backtrack through the steps above,
- check the official installation guide, which is section 4 in the
Bugzilla Guide, included in the docs/ directory in various
formats.
Support and installation questions should be directed to the
mozilla-webtools@mozilla.org mailing list -- don't write to the
developer mailing list: your post *will* be ignored if you do.
Further support information is at http://www.bugzilla.org/support/

View File

@@ -1,19 +0,0 @@
* This README is no longer used to house installation instructions. Instead,
it contains pointers to where you may find the information you need.
* A quick installation guide is provided in the QUICKSTART file.
* Complete installation instructions are found in docs/, with a
variety of document types available. Please refer to these documents
when installing, configuring, and maintaining your Bugzilla
installation. A helpful starting point is docs/txt/Bugzilla-Guide.txt,
or with a web browser at docs/html/index.html.
* Release notes for people upgrading to a new version of Bugzilla are
available at docs/rel_notes.txt.
* If you wish to contribute to the documentation, please read docs/README.docs.
* The Bugzilla web site is at "http://www.bugzilla.org/". This site will
contain the latest Bugzilla information, including how to report bugs and how
to get help with Bugzilla.

View File

@@ -1,3 +0,0 @@
Please consult The Bugzilla Guide for instructions on how to upgrade
Bugzilla from an older version. The Guide can be found with this
distribution, in docs/html, docs/txt, and docs/sgml.

View File

@@ -1,412 +0,0 @@
This file contains only important changes made to Bugzilla before release
2.8. If you are upgrading from version older than 2.8, please read this file.
If you are upgrading from 2.8 or newer, please read the Installation and
Upgrade instructions in The Bugzilla Guide, found with this distribution in
docs/html, docs/txt, and docs/sgml.
Please note that the period in our version numbers is a place separator, not
a decimal point. The 14 in version 2.14 is newer than the 8 in 2.8, for
example. You should only be using this file if you have a single digit
after the period in the version 2.x Bugzilla you are upgrading from.
For a complete list of what changes, use Bonsai
(http://cvs-mirror.mozilla.org/webtools/bonsai/cvsqueryform.cgi) to
query the CVS tree. For example,
http://cvs-mirror.mozilla.org/webtools/bonsai/cvsquery.cgi?module=all&branch=HEAD&branchtype=match&dir=mozilla%2Fwebtools%2Fbugzilla&file=&filetype=match&who=&whotype=match&sortby=Date&hours=2&date=week&mindate=&maxdate=&cvsroot=%2Fcvsroot
will tell you what has been changed in the last week.
10/12/99 The CHANGES file is now obsolete! There is a new file called
checksetup.pl. You should get in the habit of running that file every time
you update your installation of Bugzilla. That file will be constantly
updated to automatically update your installation to match any code changes.
If you're curious as to what is going on, changes are commented in that file,
at the end.
Many thanks to Holger Schurig <holgerschurig@nikocity.de> for writing this
script!
10/11/99 Restructured voting database to add a cached value in each
bug recording how many total votes that bug has. While I'm at it, I
removed the unused "area" field from the bugs database. It is
distressing to realize that the bugs table has reached the maximum
number of indices allowed by MySQL (16), which may make future
enhancements awkward.
You must feed the following to MySQL:
alter table bugs drop column area;
alter table bugs add column votes mediumint not null, add index (votes);
You then *must* delete the data/versioncache file when you make this
change, as it contains references to the "area" field. Deleting it is safe,
bugzilla will correctly regenerate it.
If you have been using the voting feature at all, then you will then
need to update the voting cache. You can do this by visiting the
sanitycheck.cgi page, and taking it up on its offer to rebuild the
votes stuff.
10/7/99 Added voting ability. You must run the new script
"makevotestable.sh". You must also feed the following to mysql:
alter table products add column votesperuser smallint not null;
9/15/99 Apparently, newer alphas of MySQL won't allow you to have
"when" as a column name. So, I have had to rename a column in the
bugs_activity table. You must feed the below to mysql or you won't
work at all.
alter table bugs_activity change column when bug_when datetime not null;
8/16/99 Added "OpenVMS" to the list of OS's. Feed this to mysql:
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "OpenVMS", "other") not null;
6/22/99 Added an entry to the attachments table to record who the submitter
was. Nothing uses this yet, but it still should be recorded.
alter table attachments add column submitter_id mediumint not null;
You should also run this script to populate the new field:
#!/usr/bin/perl -w
use diagnostics;
use strict;
require "globals.pl";
$|=1;
ConnectToDatabase();
SendSQL("select bug_id, attach_id from attachments order by bug_id");
my @list;
while (MoreSQLData()) {
my @row = FetchSQLData();
push(@list, \@row);
}
foreach my $ref (@list) {
my ($bug, $attach) = (@$ref);
SendSQL("select long_desc from bugs where bug_id = $bug");
my $comment = FetchOneColumn() . "Created an attachment (id=$attach)";
if ($comment =~ m@-* Additional Comments From ([^ ]*)[- 0-9/:]*\nCreated an attachment \(id=$attach\)@) {
print "Found $1\n";
SendSQL("select userid from profiles where login_name=" .
SqlQuote($1));
my $userid = FetchOneColumn();
if (defined $userid && $userid > 0) {
SendSQL("update attachments set submitter_id=$userid where attach_id = $attach");
}
} else {
print "Bug $bug can't find comment for attachment $attach\n";
}
}
6/14/99 Added "BeOS" to the list of OS's. Feed this to mysql:
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "BeOS", "other") not null;
5/27/99 Added support for dependency information. You must run the new
"makedependenciestable.sh" script. You can turn off dependencies with the new
"usedependencies" param, but it defaults to being on. Also, read very
carefully the description for the new "webdotbase" param; you will almost
certainly need to tweak it.
5/24/99 Added "Mac System 8.6" and "Neutrino" to the list of OS's.
Feed this to mysql:
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "Mac System 8.6", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "Neutrino", "OS/2", "other") not null;
5/12/99 Added a pref to control how much email you get. This needs a new
column in the profiles table, so feed the following to mysql:
alter table profiles add column emailnotification enum("ExcludeSelfChanges", "CConly", "All") not null default "ExcludeSelfChanges";
5/5/99 Added the ability to search by creation date. To make this perform
well, you ought to do the following:
alter table bugs change column creation_ts creation_ts datetime not null, add index (creation_ts);
4/30/99 Added a new severity, "blocker". To get this into your running
Bugzilla, do the following:
alter table bugs change column bug_severity bug_severity enum("blocker", "critical", "major", "normal", "minor", "trivial", "enhancement") not null;
4/22/99 There was a bug where the long descriptions of bugs had a variety of
newline characters at the end, depending on the operating system of the browser
that submitted the text. This bug has been fixed, so that no further changes
like that will happen. But to fix problems that have already crept into your
database, you can run the following perl script (which is slow and ugly, but
does work:)
#!/usr/bin/perl -w
use diagnostics;
use strict;
require "globals.pl";
$|=1;
ConnectToDatabase();
SendSQL("select bug_id from bugs order by bug_id");
my @list;
while (MoreSQLData()) {
push(@list, FetchOneColumn());
}
foreach my $id (@list) {
if ($id % 50 == 0) {
print "\n$id ";
}
SendSQL("select long_desc from bugs where bug_id = $id");
my $comment = FetchOneColumn();
my $orig = $comment;
$comment =~ s/\r\n/\n/g; # Get rid of windows-style line endings.
$comment =~ s/\r/\n/g; # Get rid of mac-style line endings.
if ($comment ne $orig) {
SendSQL("update bugs set long_desc = " . SqlQuote($comment) .
" where bug_id = $id");
print ".";
} else {
print "-";
}
}
4/8/99 Added ability to store patches with bugs. This requires a new table
to store the data, so you will need to run the "makeattachmenttable.sh" script.
3/25/99 Unfortunately, the HTML::FromText CPAN module had too many bugs, and
so I had to roll my own. We no longer use the HTML::FromText CPAN module.
3/24/99 (This entry has been removed. It used to say that we required the
HTML::FromText CPAN module, but that's no longer true.)
3/22/99 Added the ability to query by fields which have changed within a date
range. To make this perform a bit better, we need a new index:
alter table bugs_activity add index (field);
3/10/99 Added 'groups' stuff, where we have different group bits that we can
put on a person or on a bug. Some of the group bits control access to bugzilla
features. And a person can't access a bug unless he has every group bit set
that is also set on the bug. See the comments in makegroupstable.sh for a bit
more info.
The 'maintainer' param is now used only as an email address for people to send
complaints to. The groups table is what is now used to determine permissions.
You will need to run the new script "makegroupstable.sh". And then you need to
feed the following lines to MySQL (replace XXX with the login name of the
maintainer, the person you wish to be all-powerful).
alter table bugs add column groupset bigint not null;
alter table profiles add column groupset bigint not null;
update profiles set groupset=0x7fffffffffffffff where login_name = XXX;
3/8/99 Added params to control how priorities are set in a new bug. You can
now choose whether to let submitters of new bugs choose a priority, or whether
they should just accept the default priority (which is now no longer hardcoded
to "P2", but is instead a param.) The default value of the params will cause
the same behavior as before.
3/3/99 Added a "disallownew" field to the products table. If non-zero, then
don't let people file new bugs against this product. (This is for when a
product is retired, but you want to keep the bug reports around for posterity.)
Feed this to MySQL:
alter table products add column disallownew tinyint not null;
2/8/99 Added FreeBSD to the list of OS's. Feed this to MySQL:
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "FreeBSD", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
2/4/99 Added a new column "description" to the components table, and added
links to a new page which will use this to describe the components of a
given product. Feed this to MySQL:
alter table components add column description mediumtext not null;
2/3/99 Added a new column "initialqacontact" to the components table that gives
an initial QA contact field. It may be empty if you wish the initial qa
contact to be empty. If you're not using the QA contact field, you don't need
to add this column, but you might as well be safe and add it anyway:
alter table components add column initialqacontact tinytext not null;
2/2/99 Added a new column "milestoneurl" to the products table that gives a URL
which is to describe the currently defined milestones for a product. If you
don't use target milestone, you might be able to get away without adding this
column, but you might as well be safe and add it anyway:
alter table products add column milestoneurl tinytext not null;
1/29/99 Whoops; had a misspelled op_sys. It was "Mac System 7.1.6"; it should
be "Mac System 7.6.1". It turns out I had no bugs with this value set, so I
could just do the below simple command. If you have bugs with this value, you
may need to do something more complicated.
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.6.1", "Mac System 8.0", "Mac System 8.5", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "OS/2", "other") not null;
1/20/99 Added new fields: Target Milestone, QA Contact, and Status Whiteboard.
These fields are all optional in the UI; there are parameters to turn them on.
However, whether or not you use them, the fields need to be in the DB. There
is some code that needs them, even if you don't.
To update your DB to have these fields, send the following to MySQL:
alter table bugs add column target_milestone varchar(20) not null,
add column qa_contact mediumint not null,
add column status_whiteboard mediumtext not null,
add index (target_milestone), add index (qa_contact);
1/18/99 You can now query by CC. To make this perform reasonably, the CC table
needs some indices. The following MySQL does the necessary stuff:
alter table cc add index (bug_id), add index (who);
1/15/99 The op_sys field can now be queried by (and more easily tweaked).
To make this perform reasonably, it needs an index. The following MySQL
command will create the necessary index:
alter table bugs add index (op_sys);
12/2/98 The op_sys and rep_platform fields have been tweaked. op_sys
is now an enum, rather than having the legal values all hard-coded in
perl. rep_platform now no longer allows a value of "X-Windows".
Here's how I ported to the new world. This ought to work for you too.
Actually, it's probably overkill. I had a lot of illegal values for op_sys
in my tables, from importing bugs from strange places. If you haven't done
anything funky, then much of the below will be a no-op.
First, send the following commands to MySQL to make sure all your values for
rep_platform and op_sys are legal in the new world..
update bugs set rep_platform="Sun" where rep_platform="X-Windows" and op_sys like "Solaris%";
update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "IRIX";
update bugs set rep_platform="SGI" where rep_platform="X-Windows" and op_sys = "HP-UX";
update bugs set rep_platform="DEC" where rep_platform="X-Windows" and op_sys = "OSF/1";
update bugs set rep_platform="PC" where rep_platform="X-Windows" and op_sys = "Linux";
update bugs set rep_platform="other" where rep_platform="X-Windows";
update bugs set rep_platform="other" where rep_platform="";
update bugs set op_sys="Mac System 7" where op_sys="System 7";
update bugs set op_sys="Mac System 7.5" where op_sys="System 7.5";
update bugs set op_sys="Mac System 8.0" where op_sys="8.0";
update bugs set op_sys="OSF/1" where op_sys="Digital Unix 4.0";
update bugs set op_sys="IRIX" where op_sys like "IRIX %";
update bugs set op_sys="HP-UX" where op_sys like "HP-UX %";
update bugs set op_sys="Windows NT" where op_sys like "NT %";
update bugs set op_sys="OSF/1" where op_sys like "OSF/1 %";
update bugs set op_sys="Solaris" where op_sys like "Solaris %";
update bugs set op_sys="SunOS" where op_sys like "SunOS%";
update bugs set op_sys="other" where op_sys = "Motif";
update bugs set op_sys="other" where op_sys = "Other";
Next, send the following commands to make sure you now have only legal
entries in your table. If either of the queries do not come up empty, then
you have to do more stuff like the above.
select bug_id,op_sys,rep_platform from bugs where rep_platform not regexp "^(All|DEC|HP|Macintosh|PC|SGI|Sun|X-Windows|Other)$";
select bug_id,op_sys,rep_platform from bugs where op_sys not regexp "^(All|Windows 3.1|Windows 95|Windows 98|Windows NT|Mac System 7|Mac System 7.5|Mac System 7.1.6|Mac System 8.0|AIX|BSDI|HP-UX|IRIX|Linux|OSF/1|Solaris|SunOS|other)$";
Finally, once that's all clear, alter the table to make enforce the new legal
entries:
alter table bugs change column op_sys op_sys enum("All", "Windows 3.1", "Windows 95", "Windows 98", "Windows NT", "Mac System 7", "Mac System 7.5", "Mac System 7.1.6", "Mac System 8.0", "AIX", "BSDI", "HP-UX", "IRIX", "Linux", "OSF/1", "Solaris", "SunOS", "other") not null, change column rep_platform rep_platform enum("All", "DEC", "HP", "Macintosh", "PC", "SGI", "Sun", "Other");
11/20/98 Added searching of CC field. To better support this, added
some indexes to the CC table. You probably want to execute the following
mysql commands:
alter table cc add index (bug_id);
alter table cc add index (who);
10/27/98 security check for legal products in place. bug charts are not
available as an option if collectstats.pl has never been run. all products
get daily stats collected now. README updated: Chart::Base is listed as
a requirement, instructions for using collectstats.pl included as
an optional step. also got silly and added optional quips to bug
reports.
10/17/98 modified README installation instructions slightly.
10/7/98 Added a new table called "products". Right now, this is used
only to have a description for each product, and that description is
only used when initially adding a new bug. Anyway, you *must* create
the new table (which you can do by running the new makeproducttable.sh
script). If you just leave it empty, things will work much as they
did before, or you can add descriptions for some or all of your
products.
9/15/98 Everything has been ported to Perl. NO MORE TCL. This
transition should be relatively painless, except for the "params"
file. This is the file that contains parameters you've set up on the
editparams.cgi page. Before changing to Perl, this was a tcl-syntax
file, stored in the same directory as the code; after the change to
Perl, it becomes a perl-syntax file, stored in a subdirectory named
"data". See the README file for more details on what version of Perl
you need.
So, if updating from an older version of Bugzilla, you will need to
edit data/param, change the email address listed for
$::param{'maintainer'}, and then go revisit the editparams.cgi page
and reset all the parameters to your taste. Fortunately, your old
params file will still be around, and so you ought to be able to
cut&paste important bits from there.
Also, note that the "whineatnews" script has changed name (it now has
an extension of .pl instead of .tcl), so you'll need to change your
cron job.
And the "comments" file has been moved to the data directory. Just do
"cat comments >> data/comments" to restore any old comments that may
have been lost.
9/2/98 Changed the way password validation works. We now keep a
crypt'd version of the password in the database, and check against
that. (This is silly, because we're also keeping the plaintext
version there, but I have plans...) Stop passing the plaintext
password around as a cookie; instead, we have a cookie that references
a record in a new database table, logincookies.
IMPORTANT: if updating from an older version of Bugzilla, you must run
the following commands to keep things working:
./makelogincookiestable.sh
echo "alter table profiles add column cryptpassword varchar(64);" | mysql bugs
echo "update profiles set cryptpassword = encrypt(password,substring(rand(),3, 4));" | mysql bugs

View File

@@ -1,839 +0,0 @@
#!/usr/bin/perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
# Myk Melez <myk@mozilla.org>
# Daniel Raichle <draichle@gmx.net>
# Dave Miller <justdave@syndicomm.com>
# Alexander J. Vincent <ajvincent@juno.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Greg Hendricks <ghendricks@novell.com>
# Frédéric Buclin <LpSolit@gmail.com>
# Marc Schumann <wurblzap@gmail.com>
################################################################################
# Script Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use strict;
use lib qw(.);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Flag;
use Bugzilla::FlagType;
use Bugzilla::User;
use Bugzilla::Util;
use Bugzilla::Bug;
use Bugzilla::Field;
use Bugzilla::Attachment;
use Bugzilla::Attachment::PatchReader;
use Bugzilla::Token;
Bugzilla->login();
# For most scripts we don't make $cgi and $template global variables. But
# when preparing Bugzilla for mod_perl, this script used these
# variables in so many subroutines that it was easier to just
# make them globals.
local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
local our $vars = {};
################################################################################
# Main Body Execution
################################################################################
# All calls to this script should contain an "action" variable whose
# value determines what the user wants to do. The code below checks
# the value of that variable and runs the appropriate code. If none is
# supplied, we default to 'view'.
# Determine whether to use the action specified by the user or the default.
my $action = $cgi->param('action') || 'view';
if ($action eq "view")
{
view();
}
elsif ($action eq "interdiff")
{
interdiff();
}
elsif ($action eq "diff")
{
diff();
}
elsif ($action eq "viewall")
{
viewall();
}
elsif ($action eq "enter")
{
Bugzilla->login(LOGIN_REQUIRED);
enter();
}
elsif ($action eq "insert")
{
Bugzilla->login(LOGIN_REQUIRED);
insert();
}
elsif ($action eq "edit")
{
edit();
}
elsif ($action eq "update")
{
Bugzilla->login(LOGIN_REQUIRED);
update();
}
elsif ($action eq "delete") {
delete_attachment();
}
else
{
ThrowCodeError("unknown_action", { action => $action });
}
exit;
################################################################################
# Data Validation / Security Authorization
################################################################################
# Validates an attachment ID. Optionally takes a parameter of a form
# variable name that contains the ID to be validated. If not specified,
# uses 'id'.
#
# Will throw an error if 1) attachment ID is not a valid number,
# 2) attachment does not exist, or 3) user isn't allowed to access the
# attachment.
#
# Returns a list, where the first item is the validated, detainted
# attachment id, and the 2nd item is the bug id corresponding to the
# attachment.
#
sub validateID
{
my $param = @_ ? $_[0] : 'id';
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
# If we're not doing interdiffs, check if id wasn't specified and
# prompt them with a page that allows them to choose an attachment.
# Happens when calling plain attachment.cgi from the urlbar directly
if ($param eq 'id' && !$cgi->param('id')) {
print $cgi->header();
$template->process("attachment/choose.html.tmpl", $vars) ||
ThrowTemplateError($template->error());
exit;
}
my $attach_id = $cgi->param($param);
# Validate the specified attachment id. detaint kills $attach_id if
# non-natural, so use the original value from $cgi in our exception
# message here.
detaint_natural($attach_id)
|| ThrowUserError("invalid_attach_id", { attach_id => $cgi->param($param) });
# Make sure the attachment exists in the database.
my ($bugid, $isprivate, $submitter_id) = $dbh->selectrow_array(
"SELECT bug_id, isprivate, submitter_id
FROM attachments
WHERE attach_id = ?",
undef, $attach_id);
ThrowUserError("invalid_attach_id", { attach_id => $attach_id })
unless $bugid;
# Make sure the user is authorized to access this attachment's bug.
ValidateBugID($bugid);
if ($isprivate && $user->id != $submitter_id && !$user->is_insider) {
ThrowUserError('auth_failure', {action => 'access',
object => 'attachment'});
}
return ($attach_id, $bugid);
}
# Validates format of a diff/interdiff. Takes a list as an parameter, which
# defines the valid format values. Will throw an error if the format is not
# in the list. Returns either the user selected or default format.
sub validateFormat
{
# receives a list of legal formats; first item is a default
my $format = $cgi->param('format') || $_[0];
if ( lsearch(\@_, $format) == -1)
{
ThrowUserError("invalid_format", { format => $format, formats => \@_ });
}
return $format;
}
# Validates context of a diff/interdiff. Will throw an error if the context
# is not number, "file" or "patch". Returns the validated, detainted context.
sub validateContext
{
my $context = $cgi->param('context') || "patch";
if ($context ne "file" && $context ne "patch") {
detaint_natural($context)
|| ThrowUserError("invalid_context", { context => $cgi->param('context') });
}
return $context;
}
sub validateCanChangeAttachment
{
my ($attachid) = @_;
my $dbh = Bugzilla->dbh;
my ($productid) = $dbh->selectrow_array(
"SELECT product_id
FROM attachments
INNER JOIN bugs
ON bugs.bug_id = attachments.bug_id
WHERE attach_id = ?", undef, $attachid);
Bugzilla->user->can_edit_product($productid)
|| ThrowUserError("illegal_attachment_edit",
{ attach_id => $attachid });
}
sub validateCanChangeBug
{
my ($bugid) = @_;
my $dbh = Bugzilla->dbh;
my ($productid) = $dbh->selectrow_array(
"SELECT product_id
FROM bugs
WHERE bug_id = ?", undef, $bugid);
Bugzilla->user->can_edit_product($productid)
|| ThrowUserError("illegal_attachment_edit_bug",
{ bug_id => $bugid });
}
sub validateIsObsolete
{
# Set the isobsolete flag to zero if it is undefined, since the UI uses
# an HTML checkbox to represent this flag, and unchecked HTML checkboxes
# do not get sent in HTML requests.
$cgi->param('isobsolete', $cgi->param('isobsolete') ? 1 : 0);
}
sub validatePrivate
{
# Set the isprivate flag to zero if it is undefined, since the UI uses
# an HTML checkbox to represent this flag, and unchecked HTML checkboxes
# do not get sent in HTML requests.
$cgi->param('isprivate', $cgi->param('isprivate') ? 1 : 0);
}
# Returns 1 if the parameter is a content-type viewable in this browser
# Note that we don't use $cgi->Accept()'s ability to check if a content-type
# matches, because this will return a value even if it's matched by the generic
# */* which most browsers add to the end of their Accept: headers.
sub isViewable
{
my $contenttype = trim(shift);
# We assume we can view all text and image types
if ($contenttype =~ /^(text|image)\//) {
return 1;
}
# Mozilla can view XUL. Note the trailing slash on the Gecko detection to
# avoid sending XUL to Safari.
if (($contenttype =~ /^application\/vnd\.mozilla\./) &&
($cgi->user_agent() =~ /Gecko\//))
{
return 1;
}
# If it's not one of the above types, we check the Accept: header for any
# types mentioned explicitly.
my $accept = join(",", $cgi->Accept());
if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/) {
return 1;
}
return 0;
}
################################################################################
# Functions
################################################################################
# Display an attachment.
sub view
{
# Retrieve and validate parameters
my ($attach_id) = validateID();
my $dbh = Bugzilla->dbh;
# Retrieve the attachment content and its content type from the database.
my ($contenttype, $filename, $thedata) = $dbh->selectrow_array(
"SELECT mimetype, filename, thedata FROM attachments " .
"INNER JOIN attach_data ON id = attach_id " .
"WHERE attach_id = ?", undef, $attach_id);
# Bug 111522: allow overriding content-type manually in the posted form
# params.
if (defined $cgi->param('content_type'))
{
$cgi->param('contenttypemethod', 'manual');
$cgi->param('contenttypeentry', $cgi->param('content_type'));
Bugzilla::Attachment->validate_content_type(THROW_ERROR);
$contenttype = $cgi->param('content_type');
}
# Return the appropriate HTTP response headers.
$filename =~ s/^.*[\/\\]//;
my $filesize = length($thedata);
# A zero length attachment in the database means the attachment is
# stored in a local file
if ($filesize == 0)
{
my $hash = ($attach_id % 100) + 100;
$hash =~ s/.*(\d\d)$/group.$1/;
if (open(AH, bz_locations()->{'attachdir'} . "/$hash/attachment.$attach_id")) {
binmode AH;
$filesize = (stat(AH))[7];
}
}
if ($filesize == 0)
{
ThrowUserError("attachment_removed");
}
# escape quotes and backslashes in the filename, per RFCs 2045/822
$filename =~ s/\\/\\\\/g; # escape backslashes
$filename =~ s/"/\\"/g; # escape quotes
print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
-content_disposition=> "inline; filename=\"$filename\"",
-content_length => $filesize);
if ($thedata) {
print $thedata;
} else {
while (<AH>) {
print $_;
}
close(AH);
}
}
sub interdiff {
# Retrieve and validate parameters
my ($old_id) = validateID('oldid');
my ($new_id) = validateID('newid');
my $format = validateFormat('html', 'raw');
my $context = validateContext();
# XXX - validateID should be replaced by Attachment::check_attachment()
# and should return an attachment object. This would save us a lot of
# trouble.
my $old_attachment = Bugzilla::Attachment->get($old_id);
my $new_attachment = Bugzilla::Attachment->get($new_id);
Bugzilla::Attachment::PatchReader::process_interdiff(
$old_attachment, $new_attachment, $format, $context);
}
sub diff {
# Retrieve and validate parameters
my ($attach_id) = validateID();
my $format = validateFormat('html', 'raw');
my $context = validateContext();
my $attachment = Bugzilla::Attachment->get($attach_id);
# If it is not a patch, view normally.
if (!$attachment->ispatch) {
view();
return;
}
Bugzilla::Attachment::PatchReader::process_diff($attachment, $format, $context);
}
# Display all attachments for a given bug in a series of IFRAMEs within one
# HTML page.
sub viewall {
# Retrieve and validate parameters
my $bugid = $cgi->param('bugid');
ValidateBugID($bugid);
my $bug = new Bugzilla::Bug($bugid);
my $attachments = Bugzilla::Attachment->get_attachments_by_bug($bugid);
foreach my $a (@$attachments) {
$a->{'isviewable'} = isViewable($a->contenttype);
}
# Define the variables and functions that will be passed to the UI template.
$vars->{'bug'} = $bug;
$vars->{'attachments'} = $attachments;
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("attachment/show-multiple.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
# Display a form for entering a new attachment.
sub enter
{
# Retrieve and validate parameters
my $bugid = $cgi->param('bugid');
ValidateBugID($bugid);
validateCanChangeBug($bugid);
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
my $bug = new Bugzilla::Bug($bugid, $user->id);
# Retrieve the attachments the user can edit from the database and write
# them into an array of hashes where each hash represents one attachment.
my $canEdit = "";
if (!$user->in_group('editbugs', $bug->product_id)) {
$canEdit = "AND submitter_id = " . $user->id;
}
my $attachments = $dbh->selectall_arrayref(
"SELECT attach_id AS id, description, isprivate
FROM attachments
WHERE bug_id = ?
AND isobsolete = 0 $canEdit
ORDER BY attach_id",{'Slice' =>{}}, $bugid);
# Define the variables and functions that will be passed to the UI template.
$vars->{'bug'} = $bug;
$vars->{'attachments'} = $attachments;
my $flag_types = Bugzilla::FlagType::match({'target_type' => 'attachment',
'product_id' => $bug->product_id,
'component_id' => $bug->component_id});
$vars->{'flag_types'} = $flag_types;
$vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types);
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("attachment/create.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
# Insert a new attachment into the database.
sub insert
{
my $dbh = Bugzilla->dbh;
my $user = Bugzilla->user;
# Retrieve and validate parameters
my $bugid = $cgi->param('bugid');
ValidateBugID($bugid);
validateCanChangeBug($bugid);
ValidateComment(scalar $cgi->param('comment'));
my ($timestamp) = Bugzilla->dbh->selectrow_array("SELECT NOW()");
my $bug = new Bugzilla::Bug($bugid);
my $attachid =
Bugzilla::Attachment->insert_attachment_for_bug(THROW_ERROR, $bug, $user,
$timestamp, \$vars);
# Insert a comment about the new attachment into the database.
my $comment = "Created an attachment (id=$attachid)\n" .
$cgi->param('description') . "\n";
$comment .= ("\n" . $cgi->param('comment')) if defined $cgi->param('comment');
my $isprivate = $cgi->param('isprivate') ? 1 : 0;
AppendComment($bugid, $user->id, $comment, $isprivate, $timestamp);
# Assign the bug to the user, if they are allowed to take it
my $owner = "";
if ($cgi->param('takebug') && $user->in_group('editbugs', $bug->product_id)) {
my @fields = ("assigned_to", "bug_status", "resolution", "everconfirmed",
"login_name");
# Get the old values, for the bugs_activity table
my @oldvalues = $dbh->selectrow_array(
"SELECT " . join(", ", @fields) . " " .
"FROM bugs " .
"INNER JOIN profiles " .
"ON profiles.userid = bugs.assigned_to " .
"WHERE bugs.bug_id = ?", undef, $bugid);
my @newvalues = ($user->id, "ASSIGNED", "", 1, $user->login);
# Make sure the person we are taking the bug from gets mail.
$owner = $oldvalues[4];
# Update the bug record. Note that this doesn't involve login_name.
$dbh->do('UPDATE bugs SET delta_ts = ?, ' .
join(', ', map("$fields[$_] = ?", (0..3))) . ' WHERE bug_id = ?',
undef, ($timestamp, map($newvalues[$_], (0..3)) , $bugid));
# If the bug was a dupe, we have to remove its entry from the
# 'duplicates' table.
$dbh->do('DELETE FROM duplicates WHERE dupe = ?', undef, $bugid);
# We store email addresses in the bugs_activity table rather than IDs.
$oldvalues[0] = $oldvalues[4];
$newvalues[0] = $newvalues[4];
for (my $i = 0; $i < 4; $i++) {
if ($oldvalues[$i] ne $newvalues[$i]) {
LogActivityEntry($bugid, $fields[$i], $oldvalues[$i],
$newvalues[$i], $user->id, $timestamp);
}
}
}
# Define the variables and functions that will be passed to the UI template.
$vars->{'mailrecipients'} = { 'changer' => $user->login,
'owner' => $owner };
$vars->{'bugid'} = $bugid;
$vars->{'attachid'} = $attachid;
$vars->{'description'} = $cgi->param('description');
$vars->{'contenttypemethod'} = $cgi->param('contenttypemethod');
$vars->{'contenttype'} = $cgi->param('contenttype');
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("attachment/created.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
# Displays a form for editing attachment properties.
# Any user is allowed to access this page, unless the attachment
# is private and the user does not belong to the insider group.
# Validations are done later when the user submits changes.
sub edit {
my ($attach_id) = validateID();
my $dbh = Bugzilla->dbh;
my $attachment = Bugzilla::Attachment->get($attach_id);
my $isviewable = !$attachment->isurl && isViewable($attachment->contenttype);
# Retrieve a list of attachments for this bug as well as a summary of the bug
# to use in a navigation bar across the top of the screen.
my $bugattachments =
Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id);
# We only want attachment IDs.
@$bugattachments = map { $_->id } @$bugattachments;
my ($bugsummary, $product_id, $component_id) =
$dbh->selectrow_array('SELECT short_desc, product_id, component_id
FROM bugs
WHERE bug_id = ?', undef, $attachment->bug_id);
# Get a list of flag types that can be set for this attachment.
my $flag_types = Bugzilla::FlagType::match({ 'target_type' => 'attachment' ,
'product_id' => $product_id ,
'component_id' => $component_id });
foreach my $flag_type (@$flag_types) {
$flag_type->{'flags'} = Bugzilla::Flag::match({ 'type_id' => $flag_type->id,
'attach_id' => $attachment->id });
}
$vars->{'flag_types'} = $flag_types;
$vars->{'any_flags_requesteeble'} = grep($_->is_requesteeble, @$flag_types);
$vars->{'attachment'} = $attachment;
$vars->{'bugsummary'} = $bugsummary;
$vars->{'isviewable'} = $isviewable;
$vars->{'attachments'} = $bugattachments;
# Determine if PatchReader is installed
eval {
require PatchReader;
$vars->{'patchviewerinstalled'} = 1;
};
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("attachment/edit.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
# Updates an attachment record. Users with "editbugs" privileges, (or the
# original attachment's submitter) can edit the attachment's description,
# content type, ispatch and isobsolete flags, and statuses, and they can
# also submit a comment that appears in the bug.
# Users cannot edit the content of the attachment itself.
sub update
{
my $user = Bugzilla->user;
my $userid = $user->id;
my $dbh = Bugzilla->dbh;
# Retrieve and validate parameters
ValidateComment(scalar $cgi->param('comment'));
my ($attach_id, $bugid) = validateID();
my $bug = new Bugzilla::Bug($bugid);
my $attachment = Bugzilla::Attachment->get($attach_id);
$attachment->validate_can_edit($bug->product_id);
validateCanChangeAttachment($attach_id);
Bugzilla::Attachment->validate_description(THROW_ERROR);
Bugzilla::Attachment->validate_is_patch(THROW_ERROR);
Bugzilla::Attachment->validate_content_type(THROW_ERROR) unless $cgi->param('ispatch');
validateIsObsolete();
validatePrivate();
# If the submitter of the attachment is not in the insidergroup,
# be sure that he cannot overwrite the private bit.
# This check must be done before calling Bugzilla::Flag*::validate(),
# because they will look at the private bit when checking permissions.
# XXX - This is a ugly hack. Ideally, we shouldn't have to look at the
# old private bit twice (first here, and then below again), but this is
# the less risky change.
unless ($user->is_insider) {
my $oldisprivate = $dbh->selectrow_array('SELECT isprivate FROM attachments
WHERE attach_id = ?', undef, $attach_id);
$cgi->param('isprivate', $oldisprivate);
}
# The order of these function calls is important, as Flag::validate
# assumes User::match_field has ensured that the values in the
# requestee fields are legitimate user email addresses.
Bugzilla::User::match_field($cgi, {
'^requestee(_type)?-(\d+)$' => { 'type' => 'multi' }
});
Bugzilla::Flag::validate($cgi, $bugid, $attach_id);
# Lock database tables in preparation for updating the attachment.
$dbh->bz_lock_tables('attachments WRITE', 'flags WRITE' ,
'flagtypes READ', 'fielddefs READ', 'bugs_activity WRITE',
'flaginclusions AS i READ', 'flagexclusions AS e READ',
# cc, bug_group_map, user_group_map, and groups are in here so we
# can check the permissions of flag requestees and email addresses
# on the flag type cc: lists via the CanSeeBug
# function call in Flag::notify. group_group_map is in here si
# Bugzilla::User can flatten groups.
'bugs WRITE', 'profiles READ', 'email_setting READ',
'setting READ', 'profile_setting READ',
'cc READ', 'bug_group_map READ', 'user_group_map READ',
'group_group_map READ', 'groups READ', 'group_control_map READ');
# Get a copy of the attachment record before we make changes
# so we can record those changes in the activity table.
my ($olddescription, $oldcontenttype, $oldfilename, $oldispatch,
$oldisobsolete, $oldisprivate) = $dbh->selectrow_array(
"SELECT description, mimetype, filename, ispatch, isobsolete, isprivate
FROM attachments WHERE attach_id = ?", undef, $attach_id);
# Quote the description and content type for use in the SQL UPDATE statement.
my $description = $cgi->param('description');
my $contenttype = $cgi->param('contenttype');
my $filename = $cgi->param('filename');
# we can detaint this way thanks to placeholders
trick_taint($description);
trick_taint($contenttype);
trick_taint($filename);
# Figure out when the changes were made.
my ($timestamp) = $dbh->selectrow_array("SELECT NOW()");
# Update flags. We have to do this before committing changes
# to attachments so that we can delete pending requests if the user
# is obsoleting this attachment without deleting any requests
# the user submits at the same time.
Bugzilla::Flag::process($bug, $attachment, $timestamp, $cgi);
# Update the attachment record in the database.
$dbh->do("UPDATE attachments
SET description = ?,
mimetype = ?,
filename = ?,
ispatch = ?,
isobsolete = ?,
isprivate = ?
WHERE attach_id = ?",
undef, ($description, $contenttype, $filename,
$cgi->param('ispatch'), $cgi->param('isobsolete'),
$cgi->param('isprivate'), $attach_id));
# Record changes in the activity table.
if ($olddescription ne $cgi->param('description')) {
my $fieldid = get_field_id('attachments.description');
$dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)",
undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
$olddescription, $description));
}
if ($oldcontenttype ne $cgi->param('contenttype')) {
my $fieldid = get_field_id('attachments.mimetype');
$dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)",
undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
$oldcontenttype, $contenttype));
}
if ($oldfilename ne $cgi->param('filename')) {
my $fieldid = get_field_id('attachments.filename');
$dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)",
undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
$oldfilename, $filename));
}
if ($oldispatch ne $cgi->param('ispatch')) {
my $fieldid = get_field_id('attachments.ispatch');
$dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)",
undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
$oldispatch, $cgi->param('ispatch')));
}
if ($oldisobsolete ne $cgi->param('isobsolete')) {
my $fieldid = get_field_id('attachments.isobsolete');
$dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)",
undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
$oldisobsolete, $cgi->param('isobsolete')));
}
if ($oldisprivate ne $cgi->param('isprivate')) {
my $fieldid = get_field_id('attachments.isprivate');
$dbh->do("INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
fieldid, removed, added)
VALUES (?,?,?,?,?,?,?)",
undef, ($bugid, $attach_id, $userid, $timestamp, $fieldid,
$oldisprivate, $cgi->param('isprivate')));
}
# Unlock all database tables now that we are finished updating the database.
$dbh->bz_unlock_tables();
# If the user submitted a comment while editing the attachment,
# add the comment to the bug.
if ($cgi->param('comment'))
{
# Prepend a string to the comment to let users know that the comment came
# from the "edit attachment" screen.
my $comment = qq|(From update of attachment $attach_id)\n| .
$cgi->param('comment');
# Append the comment to the list of comments in the database.
AppendComment($bugid, $userid, $comment, $cgi->param('isprivate'), $timestamp);
}
# Define the variables and functions that will be passed to the UI template.
$vars->{'mailrecipients'} = { 'changer' => Bugzilla->user->login };
$vars->{'attachid'} = $attach_id;
$vars->{'bugid'} = $bugid;
print $cgi->header();
# Generate and return the UI (HTML page) from the appropriate template.
$template->process("attachment/updated.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
# Only administrators can delete attachments.
sub delete_attachment {
my $user = Bugzilla->login(LOGIN_REQUIRED);
my $dbh = Bugzilla->dbh;
print $cgi->header();
$user->in_group('admin')
|| ThrowUserError('auth_failure', {group => 'admin',
action => 'delete',
object => 'attachment'});
Bugzilla->params->{'allow_attachment_deletion'}
|| ThrowUserError('attachment_deletion_disabled');
# Make sure the administrator is allowed to edit this attachment.
my ($attach_id, $bug_id) = validateID();
my $attachment = Bugzilla::Attachment->get($attach_id);
validateCanChangeAttachment($attach_id);
$attachment->datasize || ThrowUserError('attachment_removed');
# We don't want to let a malicious URL accidentally delete an attachment.
my $token = trim($cgi->param('token'));
if ($token) {
my ($creator_id, $date, $event) = Bugzilla::Token::GetTokenData($token);
unless ($creator_id
&& ($creator_id == $user->id)
&& ($event eq "attachment$attach_id"))
{
# The token is invalid.
ThrowUserError('token_inexistent');
}
# The token is valid. Delete the content of the attachment.
my $msg;
$vars->{'attachid'} = $attach_id;
$vars->{'bugid'} = $bug_id;
$vars->{'date'} = $date;
$vars->{'reason'} = clean_text($cgi->param('reason') || '');
$vars->{'mailrecipients'} = { 'changer' => $user->login };
$template->process("attachment/delete_reason.txt.tmpl", $vars, \$msg)
|| ThrowTemplateError($template->error());
$dbh->bz_lock_tables('attachments WRITE', 'attach_data WRITE', 'flags WRITE');
$dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $attach_id);
$dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?,
isobsolete = ?
WHERE attach_id = ?', undef,
('text/plain', 0, 0, 1, $attach_id));
$dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $attach_id);
$dbh->bz_unlock_tables;
# If the attachment is stored locally, remove it.
if (-e $attachment->_get_local_filename) {
unlink $attachment->_get_local_filename;
}
# Now delete the token.
delete_token($token);
# Paste the reason provided by the admin into a comment.
AppendComment($bug_id, $user->id, $msg);
$template->process("attachment/updated.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
else {
# Create a token.
$token = issue_session_token('attachment' . $attach_id);
$vars->{'a'} = $attachment;
$vars->{'token'} = $token;
$template->process("attachment/confirm-delete.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,81 +0,0 @@
<!ELEMENT bugzilla (bug+)>
<!ATTLIST bugzilla
version CDATA #REQUIRED
urlbase CDATA #REQUIRED
maintainer CDATA #REQUIRED
exporter CDATA #IMPLIED
>
<!ELEMENT bug (bug_id, (alias?, creation_ts, short_desc, delta_ts, reporter_accessible, cclist_accessible, classification_id, classification, product, component, version, rep_platform, op_sys, bug_status, resolution?, dup_id?, bug_file_loc?, status_whiteboard?, keywords*, priority, bug_severity, target_milestone?, dependson*, blocked*, votes?, everconfirmed, reporter, assigned_to, qa_contact?, cc*, (estimated_time, remaining_time, actual_time, deadline)?, group*, flag*, long_desc*, attachment*)?)>
<!ATTLIST bug
error (NotFound | NotPermitted | InvalidBugId) #IMPLIED
>
<!ELEMENT bug_id (#PCDATA)>
<!ELEMENT alias (#PCDATA)>
<!ELEMENT reporter_accessible (#PCDATA)>
<!ELEMENT cclist_accessible (#PCDATA)>
<!ELEMENT exporter (#PCDATA)>
<!ELEMENT urlbase (#PCDATA)>
<!ELEMENT bug_status (#PCDATA)>
<!ELEMENT classification_id (#PCDATA)>
<!ELEMENT classification (#PCDATA)>
<!ELEMENT product (#PCDATA)>
<!ELEMENT priority (#PCDATA)>
<!ELEMENT version (#PCDATA)>
<!ELEMENT rep_platform (#PCDATA)>
<!ELEMENT assigned_to (#PCDATA)>
<!ELEMENT delta_ts (#PCDATA)>
<!ELEMENT component (#PCDATA)>
<!ELEMENT reporter (#PCDATA)>
<!ELEMENT target_milestone (#PCDATA)>
<!ELEMENT bug_severity (#PCDATA)>
<!ELEMENT creation_ts (#PCDATA)>
<!ELEMENT qa_contact (#PCDATA)>
<!ELEMENT status_whiteboard (#PCDATA)>
<!ELEMENT op_sys (#PCDATA)>
<!ELEMENT resolution (#PCDATA)>
<!ELEMENT dup_id (#PCDATA)>
<!ELEMENT bug_file_loc (#PCDATA)>
<!ELEMENT short_desc (#PCDATA)>
<!ELEMENT keywords (#PCDATA)>
<!ELEMENT dependson (#PCDATA)>
<!ELEMENT blocked (#PCDATA)>
<!ELEMENT votes (#PCDATA)>
<!ELEMENT everconfirmed (#PCDATA)>
<!ELEMENT cc (#PCDATA)>
<!ELEMENT group (#PCDATA)>
<!ELEMENT estimated_time (#PCDATA)>
<!ELEMENT remaining_time (#PCDATA)>
<!ELEMENT actual_time (#PCDATA)>
<!ELEMENT deadline (#PCDATA)>
<!ELEMENT long_desc (who, bug_when, work_time?, thetext)>
<!ATTLIST long_desc
encoding (base64) #IMPLIED
isprivate (0|1) #IMPLIED
>
<!ELEMENT who (#PCDATA)>
<!ELEMENT bug_when (#PCDATA)>
<!ELEMENT work_time (#PCDATA)>
<!ELEMENT thetext (#PCDATA)>
<!ELEMENT attachment (attachid, date, desc, filename?, type?, size?, data?, flag*)>
<!ATTLIST attachment
isobsolete (0|1) #IMPLIED
ispatch (0|1) #IMPLIED
isprivate (0|1) #IMPLIED
>
<!ELEMENT attachid (#PCDATA)>
<!ELEMENT date (#PCDATA)>
<!ELEMENT desc (#PCDATA)>
<!ELEMENT filename (#PCDATA)>
<!ELEMENT type (#PCDATA)>
<!ELEMENT size (#PCDATA)>
<!ELEMENT data (#PCDATA)>
<!ATTLIST data
encoding (base64) #IMPLIED
>
<!ELEMENT flag EMPTY>
<!ATTLIST flag
name CDATA #REQUIRED
status CDATA #REQUIRED
setter CDATA #IMPLIED
requestee CDATA #IMPLIED
>

View File

@@ -1,324 +0,0 @@
#!/usr/bin/perl -wT
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Gervase Markham <gerv@gerv.net>
# Lance Larsh <lance.larsh@oracle.com>
# Glossary:
# series: An individual, defined set of data plotted over time.
# data set: What a series is called in the UI.
# line: A set of one or more series, to be summed and drawn as a single
# line when the series is plotted.
# chart: A set of lines
#
# So when you select rows in the UI, you are selecting one or more lines, not
# series.
# Generic Charting TODO:
#
# JS-less chart creation - hard.
# Broken image on error or no data - need to do much better.
# Centralise permission checking, so Bugzilla->user->in_group('editbugs')
# not scattered everywhere.
# User documentation :-)
#
# Bonus:
# Offer subscription when you get a "series already exists" error?
use strict;
use lib qw(.);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Chart;
use Bugzilla::Series;
use Bugzilla::User;
# For most scripts we don't make $cgi and $template global variables. But
# when preparing Bugzilla for mod_perl, this script used these
# variables in so many subroutines that it was easier to just
# make them globals.
local our $cgi = Bugzilla->cgi;
local our $template = Bugzilla->template;
local our $vars = {};
# Go back to query.cgi if we are adding a boolean chart parameter.
if (grep(/^cmd-/, $cgi->param())) {
my $params = $cgi->canonicalise_query("format", "ctype", "action");
print "Location: query.cgi?format=" . $cgi->param('query_format') .
($params ? "&$params" : "") . "\n\n";
exit;
}
my $action = $cgi->param('action');
my $series_id = $cgi->param('series_id');
# Because some actions are chosen by buttons, we can't encode them as the value
# of the action param, because that value is localization-dependent. So, we
# encode it in the name, as "action-<action>". Some params even contain the
# series_id they apply to (e.g. subscribe, unsubscribe).
my @actions = grep(/^action-/, $cgi->param());
if ($actions[0] && $actions[0] =~ /^action-([^\d]+)(\d*)$/) {
$action = $1;
$series_id = $2 if $2;
}
$action ||= "assemble";
# Go to buglist.cgi if we are doing a search.
if ($action eq "search") {
my $params = $cgi->canonicalise_query("format", "ctype", "action");
print "Location: buglist.cgi" . ($params ? "?$params" : "") . "\n\n";
exit;
}
my $user = Bugzilla->login(LOGIN_REQUIRED);
Bugzilla->user->in_group(Bugzilla->params->{"chartgroup"})
|| ThrowUserError("auth_failure", {group => Bugzilla->params->{"chartgroup"},
action => "use",
object => "charts"});
# Only admins may create public queries
Bugzilla->user->in_group('admin') || $cgi->delete('public');
# All these actions relate to chart construction.
if ($action =~ /^(assemble|add|remove|sum|subscribe|unsubscribe)$/) {
# These two need to be done before the creation of the Chart object, so
# that the changes they make will be reflected in it.
if ($action =~ /^subscribe|unsubscribe$/) {
detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
my $series = new Bugzilla::Series($series_id);
$series->$action($user->id);
}
my $chart = new Bugzilla::Chart($cgi);
if ($action =~ /^remove|sum$/) {
$chart->$action(getSelectedLines());
}
elsif ($action eq "add") {
my @series_ids = getAndValidateSeriesIDs();
$chart->add(@series_ids);
}
view($chart);
}
elsif ($action eq "plot") {
plot();
}
elsif ($action eq "wrap") {
# For CSV "wrap", we go straight to "plot".
if ($cgi->param('ctype') && $cgi->param('ctype') eq "csv") {
plot();
}
else {
wrap();
}
}
elsif ($action eq "create") {
assertCanCreate($cgi);
my $series = new Bugzilla::Series($cgi);
if (!$series->existsInDatabase()) {
$series->writeToDatabase();
$vars->{'message'} = "series_created";
}
else {
ThrowUserError("series_already_exists", {'series' => $series});
}
$vars->{'series'} = $series;
print $cgi->header();
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
elsif ($action eq "edit") {
detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
assertCanEdit($series_id);
my $series = new Bugzilla::Series($series_id);
edit($series);
}
elsif ($action eq "alter") {
# This is the "commit" action for editing a series
detaint_natural($series_id) || ThrowCodeError("invalid_series_id");
assertCanEdit($series_id);
my $series = new Bugzilla::Series($cgi);
# We need to check if there is _another_ series in the database with
# our (potentially new) name. So we call existsInDatabase() to see if
# the return value is us or some other series we need to avoid stomping
# on.
my $id_of_series_in_db = $series->existsInDatabase();
if (defined($id_of_series_in_db) &&
$id_of_series_in_db != $series->{'series_id'})
{
ThrowUserError("series_already_exists", {'series' => $series});
}
$series->writeToDatabase();
$vars->{'changes_saved'} = 1;
edit($series);
}
else {
ThrowCodeError("unknown_action");
}
exit;
# Find any selected series and return either the first or all of them.
sub getAndValidateSeriesIDs {
my @series_ids = grep(/^\d+$/, $cgi->param("name"));
return wantarray ? @series_ids : $series_ids[0];
}
# Return a list of IDs of all the lines selected in the UI.
sub getSelectedLines {
my @ids = map { /^select(\d+)$/ ? $1 : () } $cgi->param();
return @ids;
}
# Check if the user is the owner of series_id or is an admin.
sub assertCanEdit {
my ($series_id) = @_;
my $user = Bugzilla->user;
return if $user->in_group('admin');
my $dbh = Bugzilla->dbh;
my $iscreator = $dbh->selectrow_array("SELECT CASE WHEN creator = ? " .
"THEN 1 ELSE 0 END FROM series " .
"WHERE series_id = ?", undef,
$user->id, $series_id);
$iscreator || ThrowUserError("illegal_series_edit");
}
# Check if the user is permitted to create this series with these parameters.
sub assertCanCreate {
my ($cgi) = shift;
Bugzilla->user->in_group("editbugs") || ThrowUserError("illegal_series_creation");
# Check permission for frequency
my $min_freq = 7;
if ($cgi->param('frequency') < $min_freq && !Bugzilla->user->in_group("admin")) {
ThrowUserError("illegal_frequency", { 'minimum' => $min_freq });
}
}
sub validateWidthAndHeight {
$vars->{'width'} = $cgi->param('width');
$vars->{'height'} = $cgi->param('height');
if (defined($vars->{'width'})) {
(detaint_natural($vars->{'width'}) && $vars->{'width'} > 0)
|| ThrowCodeError("invalid_dimensions");
}
if (defined($vars->{'height'})) {
(detaint_natural($vars->{'height'}) && $vars->{'height'} > 0)
|| ThrowCodeError("invalid_dimensions");
}
# The equivalent of 2000 square seems like a very reasonable maximum size.
# This is merely meant to prevent accidental or deliberate DOS, and should
# have no effect in practice.
if ($vars->{'width'} && $vars->{'height'}) {
(($vars->{'width'} * $vars->{'height'}) <= 4000000)
|| ThrowUserError("chart_too_large");
}
}
sub edit {
my $series = shift;
$vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
$vars->{'creator'} = new Bugzilla::User($series->{'creator'});
$vars->{'default'} = $series;
print $cgi->header();
$template->process("reports/edit-series.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
sub plot {
validateWidthAndHeight();
$vars->{'chart'} = new Bugzilla::Chart($cgi);
my $format = $template->get_format("reports/chart", "", scalar($cgi->param('ctype')));
# Debugging PNGs is a pain; we need to be able to see the error messages
if ($cgi->param('debug')) {
print $cgi->header();
$vars->{'chart'}->dump();
}
print $cgi->header($format->{'ctype'});
$template->process($format->{'template'}, $vars)
|| ThrowTemplateError($template->error());
}
sub wrap {
validateWidthAndHeight();
# We create a Chart object so we can validate the parameters
my $chart = new Bugzilla::Chart($cgi);
$vars->{'time'} = time();
$vars->{'imagebase'} = $cgi->canonicalise_query(
"action", "action-wrap", "ctype", "format", "width", "height");
print $cgi->header();
$template->process("reports/chart.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}
sub view {
my $chart = shift;
# Set defaults
foreach my $field ('category', 'subcategory', 'name', 'ctype') {
$vars->{'default'}{$field} = $cgi->param($field) || 0;
}
# Pass the state object to the display UI.
$vars->{'chart'} = $chart;
$vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
print $cgi->header();
# If we have having problems with bad data, we can set debug=1 to dump
# the data structure.
$chart->dump() if $cgi->param('debug');
$template->process("reports/create-chart.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
}

View File

@@ -1,495 +0,0 @@
#!/usr/bin/perl -w
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is Holger
# Schurig. Portions created by Holger Schurig are
# Copyright (C) 1999 Holger Schurig. All
# Rights Reserved.
#
# Contributor(s): Holger Schurig <holgerschurig@nikocity.de>
# Terry Weissman <terry@mozilla.org>
# Dan Mosedale <dmose@mozilla.org>
# Dave Miller <justdave@syndicomm.com>
# Zach Lipton <zach@zachlipton.com>
# Jacob Steenhagen <jake@bugzilla.org>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Tobias Burnus <burnus@net-b.de>
# Shane H. W. Travis <travis@sedsystems.ca>
# Gervase Markham <gerv@gerv.net>
# Erik Stambaugh <erik@dasbistro.com>
# Dave Lawrence <dkl@redhat.com>
# Max Kanat-Alexander <mkanat@bugzilla.org>
# Joel Peshkin <bugreport@peshkin.net>
# Lance Larsh <lance.larsh@oracle.com>
# A. Karl Kornel <karl@kornel.name>
# Marc Schumann <wurblzap@gmail.com>
# This file has detailed POD docs, do "perldoc checksetup.pl" to see them.
######################################################################
# Initialization
######################################################################
use strict;
use 5.008;
use File::Basename;
use Getopt::Long qw(:config bundling);
use Pod::Usage;
use POSIX qw(setlocale LC_CTYPE);
use Safe;
BEGIN { chdir dirname($0); }
use lib ".";
use Bugzilla::Constants;
use Bugzilla::Install::Requirements;
require 5.008001 if ON_WINDOWS; # for CGI 2.93 or higher
######################################################################
# Live Code
######################################################################
my %switch;
GetOptions(\%switch, 'help|h|?', 'check-modules', 'no-templates|t',
'verbose|v|no-silent', 'make-admin=s');
# Print the help message if that switch was selected.
pod2usage({-verbose => 1, -exitval => 1}) if $switch{'help'};
# Read in the "answers" file if it exists, for running in
# non-interactive mode.
my $answers_file = $ARGV[0];
my $silent = $answers_file && !$switch{'verbose'};
display_version_and_os() unless $silent;
# Check required --MODULES--
my $module_results = check_requirements(!$silent);
Bugzilla::Install::Requirements::print_module_instructions(
$module_results, !$silent);
exit if !$module_results->{pass};
# Break out if checking the modules is all we have been asked to do.
exit if $switch{'check-modules'};
###########################################################################
# Load Bugzilla Modules
###########################################################################
# It's never safe to "use" a Bugzilla module in checksetup. If a module
# prerequisite is missing, and you "use" a module that requires it,
# then instead of our nice normal checksetup message, the user would
# get a cryptic perl error about the missing module.
# We need $::ENV{'PATH'} to remain defined.
my $env = $::ENV{'PATH'};
require Bugzilla;
$::ENV{'PATH'} = $env;
require Bugzilla::Config;
import Bugzilla::Config qw(:admin);
require Bugzilla::Install::Localconfig;
import Bugzilla::Install::Localconfig qw(update_localconfig);
require Bugzilla::Install::Filesystem;
import Bugzilla::Install::Filesystem qw(update_filesystem create_htaccess
fix_all_file_permissions);
require Bugzilla::Install::DB;
require Bugzilla::DB;
require Bugzilla::Template;
require Bugzilla::Field;
require Bugzilla::Install;
Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
Bugzilla->installation_mode(INSTALLATION_MODE_NON_INTERACTIVE) if $answers_file;
Bugzilla->installation_answers($answers_file);
# When we're running at the command line, we need to pick the right
# language before ever creating a template object.
$ENV{'HTTP_ACCEPT_LANGUAGE'} ||= setlocale(LC_CTYPE);
###########################################################################
# Check and update --LOCAL-- configuration
###########################################################################
print "Reading " . bz_locations()->{'localconfig'} . "...\n" unless $silent;
update_localconfig({ output => !$silent });
my $lc_hash = Bugzilla->localconfig;
###########################################################################
# Check --DATABASE-- setup
###########################################################################
# At this point, localconfig is defined and is readable. So we know
# everything we need to create the DB. We have to create it early,
# because some data required to populate data/params is stored in the DB.
Bugzilla::DB::bz_check_requirements(!$silent);
Bugzilla::DB::bz_create_database() if $lc_hash->{'db_check'};
# now get a handle to the database:
my $dbh = Bugzilla->dbh;
# Create the tables, and do any database-specific schema changes.
$dbh->bz_setup_database();
# Populate the tables that hold the values for the <select> fields.
$dbh->bz_populate_enum_tables();
###########################################################################
# Check --DATA-- directory
###########################################################################
update_filesystem({ index_html => $lc_hash->{'index_html'} });
create_htaccess() if $lc_hash->{'create_htaccess'};
# Remove parameters from the params file that no longer exist in Bugzilla,
# and set the defaults for new ones
update_params();
###########################################################################
# Pre-compile --TEMPLATE-- code
###########################################################################
Bugzilla::Template::precompile_templates(!$silent)
unless $switch{'no-templates'};
###########################################################################
# Set proper rights (--CHMOD--)
###########################################################################
fix_all_file_permissions(!$silent);
###########################################################################
# Check GraphViz setup
###########################################################################
# If we are using a local 'dot' binary, verify the specified binary exists
# and that the generated images are accessible.
check_graphviz(!$silent) if Bugzilla->params->{'webdotbase'};
###########################################################################
# Changes to the fielddefs --TABLE--
###########################################################################
# Using Bugzilla::Field's create() or update() depends on the
# fielddefs table having a modern definition. So, we have to make
# these particular schema changes before we make any other schema changes.
Bugzilla::Install::DB::update_fielddefs_definition();
Bugzilla::Field::populate_field_definitions();
###########################################################################
# Update the tables to the current definition --TABLE--
###########################################################################
Bugzilla::Install::DB::update_table_definitions();
###########################################################################
# Bugzilla uses --GROUPS-- to assign various rights to its users.
###########################################################################
Bugzilla::Install::update_system_groups();
###########################################################################
# Create --SETTINGS-- users can adjust
###########################################################################
Bugzilla::Install::update_settings();
###########################################################################
# Create Administrator --ADMIN--
###########################################################################
Bugzilla::Install::make_admin($switch{'make-admin'}) if $switch{'make-admin'};
Bugzilla::Install::create_admin();
###########################################################################
# Create default Product and Classification
###########################################################################
Bugzilla::Install::create_default_product();
###########################################################################
# Final checks
###########################################################################
# Check if the default parameter for urlbase is still set, and if so, give
# notification that they should go and visit editparams.cgi
if (Bugzilla->params->{'urlbase'} eq '') {
print "\n" . Bugzilla::Install::get_text('install_urlbase_default') . "\n"
unless $silent;
}
__END__
=head1 NAME
checksetup.pl - A do-it-all upgrade and installation script for Bugzilla.
=head1 SYNOPSIS
./checksetup.pl [--help|--check-modules]
./checksetup.pl [SCRIPT [--verbose]] [--no-templates|-t]
[--make-admin=user@domain.com]
=head1 OPTIONS
=over
=item F<SCRIPT>
Name of script to drive non-interactive mode. This script should
define an C<%answer> hash whose keys are variable names and the
values answers to all the questions checksetup.pl asks. For details
on the format of this script, do C<perldoc checksetup.pl> and look for
the L</"RUNNING CHECKSETUP NON-INTERACTIVELY"> section.
=item B<--help>
Display this help text
=item B<--check-modules>
Only check for correct module dependencies and quit afterward.
=item B<--make-admin>=username@domain.com
Makes the specified user into a Bugzilla administrator. This is
in case you accidentally lock yourself out of the Bugzilla administrative
interface.
=item B<--no-templates> (B<-t>)
Don't compile the templates at all. Existing compiled templates will
remain; missing compiled templates will not be created. (Used primarily
by developers to speed up checksetup.) Use this switch at your own risk.
=item B<--verbose>
Output results of SCRIPT being processed.
=back
=head1 DESCRIPTION
Hey, what's this?
F<checksetup.pl> is a script that is supposed to run during
installation time and also after every upgrade.
The goal of this script is to make the installation even easier.
It does this by doing things for you as well as testing for problems
in advance.
You can run the script whenever you like. You MUST run it after
you update Bugzilla, because it will then update your SQL table
definitions to resync them with the code.
You can see all the details of what the script does at
L</How Checksetup Works>.
=head1 MODIFYING CHECKSETUP
There should be no need for Bugzilla Administrators to modify
this script; all user-configurable stuff has been moved
into a local configuration file called F<localconfig>. When that file
in changed and F<checksetup.pl> is run, then the user's changes
will be reflected back into the database.
However, developers often need to modify the installation process.
This section explains how F<checksetup.pl> works, so that you
know the right part to modify.
=head2 How Checksetup Works
F<checksetup.pl> runs through several stages during installation:
=over
=item 1
Checks if the required and optional perl modules are installed,
using L<Bugzilla::Install::Requirements/check_requirements>.
=item 2
Creates or updates the F<localconfig> file, using
L<Bugzilla::Install::Localconfig/update_localconfig>.
=item 3
Checks the DBD and database version, using
L<Bugzilla::DB/bz_check_requirements>.
=item 4
Creates the Bugzilla database if it doesn't exist, using
L<Bugzilla::DB/bz_create_database>.
=item 5
Creates all of the tables in the Bugzilla database, using
L<Bugzilla::DB/bz_setup_database>.
Note that all the table definitions are stored in
L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>.
=item 6
Puts the values into the enum tables (like C<resolution>, C<bug_status>,
etc.) using L<Bugzilla::DB/bz_populate_enum_tables>.
=item 7
Creates any files that Bugzilla needs but doesn't ship with, using
L<Bugzilla::Install::Filesystem/update_filesystem>.
=item 8
Creates the F<.htaccess> files if you haven't specified not to
in F<localconfig>. It does this with
L<Bugzilla::Install::Filesystem/create_htaccess>.
=item 9
Updates the system parameters (stored in F<data/params>), using
L<Bugzilla::Config/update_params>.
=item 10
Pre-compiles all templates, to improve the speed of Bugzilla.
It uses L<Bugzilla::Template/precompile_templates> to do this.
=item 11
Fixes all file permissions to be secure. It does this differently depending
on whether or not you've specified C<$webservergroup> in F<localconfig>.
The function that does this is
L<Bugzilla::Install::Filesystem/fix_all_file_permissions>.
=item 12
Populates the C<fielddefs> table, using
L<Bugzilla::Field/populate_field_definitions>.
=item 13
This is the major part of checksetup--updating the table definitions
from one version of Bugzilla to another.
The code for this is in L<Bugzilla::Install::DB/update_table_definitions>.
=item 14
Creates the system groups--the ones like C<editbugs>, C<admin>, and so on.
This is L<Bugzilla::Install/update_system_groups>.
=item 15
Creates all of the user-adjustable preferences that appear on the
"General Preferences" screen. This is L<Bugzilla::Install/update_settings>.
=item 16
Creates an administrator, if one doesn't already exist, using
L<Bugzilla::Install/create_admin>.
We also can make somebody an admin at this step, if the user specified
the C<--make-admin> switch.
=item 17
Creates the default Classification, Product, and Component, using
L<Bugzilla::Install/create_default_product>.
=back
=head2 Modifying the Database
Sometimes you'll want to modify the database. In fact, that's mostly
what checksetup does, is upgrade old Bugzilla databases to the modern
format.
If you'd like to know how to make changes to the datbase, see
the information in the Bugzilla Developer's Guide, at:
L<http://www.bugzilla.org/docs/developer.html#sql-schema>
Also see L<Bugzilla::DB/"Schema Modification Methods"> and
L<Bugzilla::DB/"Schema Information Methods">.
=head1 RUNNING CHECKSETUP NON-INTERACTIVELY
To operate checksetup non-interactively, run it with a single argument
specifying a filename that contains the information usually obtained by
prompting the user or by editing localconfig.
The format of that file is as follows:
$answer{'db_host'} = 'localhost';
$answer{'db_driver'} = 'mydbdriver';
$answer{'db_port'} = 0;
$answer{'db_name'} = 'mydbname';
$answer{'db_user'} = 'mydbuser';
$answer{'db_pass'} = 'mydbpass';
$answer{'urlbase'} = 'http://bugzilla.mydomain.com/';
(Any localconfig variable or parameter can be specified as above.)
$answer{'ADMIN_EMAIL'} = 'myadmin@mydomain.net';
$answer{'ADMIN_PASSWORD'} = 'fooey';
$answer{'ADMIN_REALNAME'} = 'Joel Peshkin';
$answer{'SMTP_SERVER'} = 'mail.mydomain.net';
$answer{'NO_PAUSE'} = 1
C<NO_PAUSE> means "never stop and prompt the user to hit Enter to continue,
just go ahead and do things, even if they are potentially dangerous."
Don't set this to 1 unless you know what you are doing.
=head1 SEE ALSO
=over
=item *
L<Bugzilla::Install::Requirements>
=item *
L<Bugzilla::Install::Localconfig>
=item *
L<Bugzilla::Install::Filesystem>
=item *
L<Bugzilla::Install::DB>
=item *
L<Bugzilla::Install>
=item *
L<Bugzilla::Config/update_params>
=item *
L<Bugzilla::DB/CONNECTION>
=back

View File

@@ -1,155 +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>
# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
use lib qw(.);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::User;
use Bugzilla::Keyword;
Bugzilla->login();
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
my $vars = {};
# 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 (Bugzilla->params->{"useclassification"}) {
push(@masterlist, "classification");
}
push(@masterlist, ("product", "component", "version", "op_sys"));
if (Bugzilla->params->{"usevotes"}) {
push (@masterlist, "votes");
}
if (Bugzilla->params->{"usebugaliases"}) {
unshift(@masterlist, "alias");
}
if (Bugzilla->params->{"usetargetmilestone"}) {
push(@masterlist, "target_milestone");
}
if (Bugzilla->params->{"useqacontact"}) {
push(@masterlist, "qa_contact");
push(@masterlist, "qa_contact_realname");
}
if (Bugzilla->params->{"usestatuswhiteboard"}) {
push(@masterlist, "status_whiteboard");
}
if (Bugzilla::Keyword::keyword_count()) {
push(@masterlist, "keywords");
}
if (Bugzilla->user->in_group(Bugzilla->params->{"timetrackinggroup"})) {
push(@masterlist, ("estimated_time", "remaining_time", "actual_time",
"percentage_complete", "deadline"));
}
push(@masterlist, ("short_desc", "short_short_desc"));
push(@masterlist, Bugzilla->custom_field_names);
$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 = Bugzilla->params->{"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'} = $cgi->query_string();
# Generate and return the UI (HTML page) from the appropriate template.
print $cgi->header();
$template->process("list/change-columns.html.tmpl", $vars)
|| ThrowTemplateError($template->error());

View File

@@ -1,605 +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>
# Frédéric Buclin <LpSolit@gmail.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 Cwd;
use lib ".";
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Search;
use Bugzilla::User;
use Bugzilla::Product;
use Bugzilla::Field;
# Turn off output buffering (probably needed when displaying output feedback
# in the regenerate mode).
$| = 1;
# Tidy up after graphing module
my $cwd = Cwd::getcwd();
if (chdir("graphs")) {
unlink <./*.gif>;
unlink <./*.png>;
# chdir("..") doesn't work if graphs is a symlink, see bug 429378
chdir($cwd);
}
# This is a pure command line script.
Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
my $dbh = 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 $datadir = bz_locations()->{'datadir'};
my @myproducts = map {$_->name} Bugzilla::Product->get_all;
unshift(@myproducts, "-All-");
# As we can now customize the list of resolutions, looking at the actual list
# of available resolutions only is not enough as some now removed resolutions
# may have existed in the past, or have been renamed. We want them all.
my @resolutions = @{get_legal_field_values('resolution')};
my $old_resolutions =
$dbh->selectcol_arrayref('SELECT bugs_activity.added
FROM bugs_activity
INNER JOIN fielddefs
ON fielddefs.id = bugs_activity.fieldid
LEFT JOIN resolution
ON resolution.value = bugs_activity.added
WHERE fielddefs.name = ?
AND resolution.id IS NULL
UNION
SELECT bugs_activity.removed
FROM bugs_activity
INNER JOIN fielddefs
ON fielddefs.id = bugs_activity.fieldid
LEFT JOIN resolution
ON resolution.value = bugs_activity.removed
WHERE fielddefs.name = ?
AND resolution.id IS NULL',
undef, ('resolution', 'resolution'));
push(@resolutions, @$old_resolutions);
# Exclude "" from the resolution list.
@resolutions = grep {$_} @resolutions;
# Actually, the list of statuses is predefined. This will change in the near future.
my @statuses = qw(NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED VERIFIED CLOSED);
my $tstart = time;
foreach (@myproducts) {
my $dir = "$datadir/mining";
&check_data_dir ($dir);
if ($regenerate) {
&regenerate_stats($dir, $_);
} else {
&collect_stats($dir, $_);
}
}
my $tend = time;
# Uncomment the following line for performance testing.
#print "Total time taken " . delta_time($tstart, $tend) . "\n";
&calculate_dupes();
CollectSeriesData();
{
local $ENV{'GATEWAY_INTERFACE'} = 'cmdline';
local $ENV{'REQUEST_METHOD'} = 'GET';
local $ENV{'QUERY_STRING'} = 'ctype=rdf';
my $perl = $^X;
trick_taint($perl);
# Generate a static RDF file containing the default view of the duplicates data.
open(CGI, "$perl -T duplicates.cgi |")
|| die "can't fork duplicates.cgi: $!";
open(RDF, ">$datadir/duplicates.tmp")
|| die "can't write to $datadir/duplicates.tmp: $!";
my $headers_done = 0;
while (<CGI>) {
print RDF if $headers_done;
$headers_done = 1 if $_ eq "\r\n";
}
close CGI;
close RDF;
}
if (-s "$datadir/duplicates.tmp") {
rename("$datadir/duplicates.rdf", "$datadir/duplicates-old.rdf");
rename("$datadir/duplicates.tmp", "$datadir/duplicates.rdf");
}
sub check_data_dir {
my $dir = shift;
if (! -d $dir) {
mkdir $dir, 0755;
chmod 0755, $dir;
}
}
sub collect_stats {
my $dir = shift;
my $product = shift;
my $when = localtime (time);
my $dbh = Bugzilla->dbh;
my $product_id;
if ($product ne '-All-') {
my $prod = Bugzilla::Product::check_product($product);
$product_id = $prod->id;
}
# 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 the file exists, get the old status and resolution list for that product.
my @data;
@data = get_old_data($file) if $exists;
# If @data is not empty, then we have to recreate the data file.
if (scalar(@data)) {
open(DATA, '>', $file)
|| ThrowCodeError('chart_file_open_fail', {'filename' => $file});
}
else {
open(DATA, '>>', $file)
|| ThrowCodeError('chart_file_open_fail', {'filename' => $file});
}
# Now collect current data.
my @row = (today());
my $status_sql = q{SELECT COUNT(*) FROM bugs WHERE bug_status = ?};
my $reso_sql = q{SELECT COUNT(*) FROM bugs WHERE resolution = ?};
if ($product ne '-All-') {
$status_sql .= q{ AND product_id = ?};
$reso_sql .= q{ AND product_id = ?};
}
my $sth_status = $dbh->prepare($status_sql);
my $sth_reso = $dbh->prepare($reso_sql);
my @values ;
foreach my $status (@statuses) {
@values = ($status);
push (@values, $product_id) if ($product ne '-All-');
my $count = $dbh->selectrow_array($sth_status, undef, @values);
push(@row, $count);
}
foreach my $resolution (@resolutions) {
@values = ($resolution);
push (@values, $product_id) if ($product ne '-All-');
my $count = $dbh->selectrow_array($sth_reso, undef, @values);
push(@row, $count);
}
if (!$exists || scalar(@data)) {
my $fields = join('|', ('DATE', @statuses, @resolutions));
print DATA <<FIN;
# Bugzilla Daily Bug Stats
#
# Do not edit me! This file is generated.
#
# fields: $fields
# Product: $product
# Created: $when
FIN
}
# Add existing data, if needed. Note that no count is not treated
# the same way as a count with 0 bug.
foreach my $data (@data) {
print DATA join('|', map {defined $data->{$_} ? $data->{$_} : ''}
('DATE', @statuses, @resolutions)) . "\n";
}
print DATA (join '|', @row) . "\n";
close DATA;
chmod 0644, $file;
}
sub get_old_data {
my $file = shift;
open(DATA, '<', $file)
|| ThrowCodeError('chart_file_open_fail', {'filename' => $file});
my @data;
my @columns;
my $recreate = 0;
while (<DATA>) {
chomp;
next unless $_;
if (/^# fields?:\s*(.+)\s*$/) {
@columns = split(/\|/, $1);
# Compare this list with @statuses and @resolutions.
# If they are identical, then we can safely append new data
# to the end of the file; else we have to recreate it.
$recreate = 1;
my @new_cols = ($columns[0], @statuses, @resolutions);
if (scalar(@columns) == scalar(@new_cols)) {
my $identical = 1;
for (0 .. $#columns) {
$identical = 0 if ($columns[$_] ne $new_cols[$_]);
}
last if $identical;
}
}
next unless $recreate;
next if (/^#/); # Ignore comments.
# If we have to recreate the file, we have to load all existing
# data first.
my @line = split /\|/;
my %data;
foreach my $column (@columns) {
$data{$column} = shift @line;
}
push(@data, \%data);
}
close(DATA);
return @data;
}
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
my $datadir = bz_locations()->{'datadir'};
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 = "";
my @values = ();
if ($product ne '-All-') {
$and_product = q{ AND products.name = ?};
$from_product = q{ INNER JOIN products
ON bugs.product_id = products.id};
push (@values, $product);
}
# 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.
my $query = q{SELECT } .
$dbh->sql_to_days('creation_ts') . q{ AS start, } .
$dbh->sql_to_days('current_date') . q{ AS end, } .
$dbh->sql_to_days("'1970-01-01'") .
qq{ FROM bugs $from_product
WHERE } . $dbh->sql_to_days('creation_ts') .
qq{ IS NOT NULL $and_product
ORDER BY start } . $dbh->sql_limit(1);
my ($start, $end, $base) = $dbh->selectrow_array($query, undef, @values);
if (!defined $start) {
return;
}
if (open DATA, ">$file") {
DATA->autoflush(1);
my $fields = join('|', ('DATE', @statuses, @resolutions));
print DATA <<FIN;
# Bugzilla Daily Bug Stats
#
# Do not edit me! This file is generated.
#
# fields: $fields
# 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.
$query = qq{SELECT bug_id
FROM bugs $from_product
WHERE bugs.creation_ts < } .
$dbh->sql_from_days($day - 1) .
q{ AND bugs.creation_ts >= } .
$dbh->sql_from_days($day - 2) .
$and_product . q{ ORDER BY bug_id};
my $bug_ids = $dbh->selectcol_arrayref($query, undef, @values);
push(@bugs, @$bug_ids);
# 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;
foreach (@statuses) { $bugcount{$_} = 0; }
foreach (@resolutions) { $bugcount{$_} = 0; }
# Get information on bug states and resolutions.
$query = qq{SELECT bugs_activity.removed
FROM bugs_activity
INNER JOIN fielddefs
ON bugs_activity.fieldid = fielddefs.id
WHERE fielddefs.name = ?
AND bugs_activity.bug_id = ?
AND bugs_activity.bug_when >= } .
$dbh->sql_from_days($day) .
" ORDER BY bugs_activity.bug_when " .
$dbh->sql_limit(1);
my $sth_bug = $dbh->prepare($query);
my $sth_status = $dbh->prepare(q{SELECT bug_status
FROM bugs
WHERE bug_id = ?});
my $sth_reso = $dbh->prepare(q{SELECT resolution
FROM bugs
WHERE bug_id = ?});
for my $bug (@bugs) {
my $status = $dbh->selectrow_array($sth_bug, undef,
'bug_status', $bug);
unless ($status) {
$status = $dbh->selectrow_array($sth_status, undef, $bug);
}
if (defined $bugcount{$status}) {
$bugcount{$status}++;
}
my $resolution = $dbh->selectrow_array($sth_bug, undef,
'resolution', $bug);
unless ($resolution) {
$resolution = $dbh->selectrow_array($sth_reso, undef, $bug);
}
if (defined $bugcount{$resolution}) {
$bugcount{$resolution}++;
}
}
# Generate a line of output containing the date and counts
# of bugs in each state.
my $date = sqlday($day, $base);
print DATA "$date";
foreach (@statuses) { print DATA "|$bugcount{$_}"; }
foreach (@resolutions) { 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.
my $dbh = Bugzilla->switch_to_main_db();
my $shadow_dbh = Bugzilla->switch_to_shadow_db();
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 $data;
# Do not die if Search->new() detects invalid data, such as an obsolete
# login name or a renamed product or component, etc.
eval {
my $search = new Bugzilla::Search('params' => $cgi,
'fields' => ["bugs.bug_id"],
'user' => $user);
my $sql = $search->getSQL();
$data = $shadow_dbh->selectall_arrayref($sql);
};
if (!$@) {
# We need to count the returned rows. Without subselects, we can't
# do this directly in the SQL for all queries. So we do it by hand.
my $count = scalar(@$data) || 0;
$deletesth->execute($series_id);
$sth->execute($series_id, $count);
}
}
}

View File

@@ -1,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>
# Myk Melez <myk@mozilla.org>
################################################################################
# Script Initialization
################################################################################
# Make it harder for us to do dangerous things in Perl.
use strict;
use lib qw(.);
use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Keyword;
use Bugzilla::Bug;
use Bugzilla::Field;
my $user = Bugzilla->login(LOGIN_OPTIONAL);
my $cgi = Bugzilla->cgi;
# If the 'requirelogin' parameter is on and the user is not
# authenticated, return empty fields.
if (Bugzilla->params->{'requirelogin'} && !$user->id) {
display_data();
}
# Pass a bunch of Bugzilla configuration to the templates.
my $vars = {};
$vars->{'priority'} = get_legal_field_values('priority');
$vars->{'severity'} = get_legal_field_values('bug_severity');
$vars->{'platform'} = get_legal_field_values('rep_platform');
$vars->{'op_sys'} = get_legal_field_values('op_sys');
$vars->{'keyword'} = [map($_->name, Bugzilla::Keyword->get_all)];
$vars->{'resolution'} = get_legal_field_values('resolution');
$vars->{'status'} = get_legal_field_values('bug_status');
$vars->{'custom_fields'} =
[Bugzilla->get_fields({custom => 1, obsolete => 0, type => FIELD_TYPE_SINGLE_SELECT})];
# Include a list of product objects.
if ($cgi->param('product')) {
my @products = $cgi->param('product');
foreach my $product_name (@products) {
# We don't use check_product because config.cgi outputs mostly
# in XML and JS and we don't want to display an HTML error
# instead of that.
my $product = new Bugzilla::Product({ name => $product_name });
if ($product && $user->can_see_product($product->name)) {
push (@{$vars->{'products'}}, $product);
}
}
} else {
$vars->{'products'} = $user->get_selectable_products;
}
# 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 (@{$vars->{'status'}}) {
is_open_state($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;
my $template = Bugzilla->template;
# Determine how the user would like to receive the output;
# default is JavaScript.
my $format = $template->get_format("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;
}

View File

@@ -1,73 +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 tread carefully. But it may still
be useful to you. Read the files themselves for detailed usage information
on any specific script.
This file is encoded in UTF8 for purposes of contributor names.
This directory includes:
bugzilla_ldapsync.rb -- Script that can be 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>.
bugzilla-submit/ -- A standalone bug submission program.
bzdbcopy.pl -- A script to copy data from an installation running
on one DB platform to an installation running on
another DB platform.
bz_webservice_demo.p -- An example script that demonstrates how to talk to
Bugzilla via XMLRPC.
cmdline/ -- Various commands for querying your Bugzilla
installation.
cvs-update.pl -- Script to keep a record of all CVS updates made
from a given directory. The log is useful when
changes need to be backed out.
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>.
jb2bz.py -- Script to import bugs from JitterBug to Bugzilla.
merge-users.pl -- Script to merge two user accounts. The activities
from one account are moved to the another. Specify
both accounts on the command line. The new account
must already exist.
mysqld-watcher.pl -- This script can be installed as a frequent Cron
job to clean up stalled/dead queries.
recode.pl -- Script to convert a database from one encoding
(or multiple encodings) to UTF-8.
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. This script can
be used if 'processmail' was previously called
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.
sendunsentbugmail.pl -- Script to find bugs with un-sent mail and to
send all unsent messages.
syncLDAP.pl -- Script that can be 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>.
yp_nomail.sh -- Script that can be run via Cron that regularly updates
the nomail file for terminated employees.

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